Add project files.
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
using JSMR.Application.Circles.Ports;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Commands.UpdateCircleStatus;
|
||||||
|
|
||||||
|
public class UpdateCircleStatusHandler(ICircleWriter writer)
|
||||||
|
{
|
||||||
|
public Task<UpdateCircleStatusResponse> HandleAsync(UpdateCircleStatusRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return writer.UpdateStatusAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Circles.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Commands.UpdateCircleStatus;
|
||||||
|
|
||||||
|
public sealed record UpdateCircleStatusRequest(int CircleId, CircleStatus CircleStatus);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Circles.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Commands.UpdateCircleStatus;
|
||||||
|
|
||||||
|
public sealed record UpdateCircleStatusResponse(int CircleId, CircleStatus CircleStatus);
|
||||||
9
JSMR.Application/Circles/Contracts/CircleStatus.cs
Normal file
9
JSMR.Application/Circles/Contracts/CircleStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.Circles.Contracts;
|
||||||
|
|
||||||
|
public enum CircleStatus
|
||||||
|
{
|
||||||
|
Neutral,
|
||||||
|
Favorite,
|
||||||
|
Blacklisted,
|
||||||
|
Spam
|
||||||
|
}
|
||||||
6
JSMR.Application/Circles/Ports/ICircleReader.cs
Normal file
6
JSMR.Application/Circles/Ports/ICircleReader.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Circles.Ports;
|
||||||
|
|
||||||
|
public interface ICircleReader
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
8
JSMR.Application/Circles/Ports/ICircleWriter.cs
Normal file
8
JSMR.Application/Circles/Ports/ICircleWriter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Circles.Commands.UpdateCircleStatus;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Ports;
|
||||||
|
|
||||||
|
public interface ICircleWriter
|
||||||
|
{
|
||||||
|
Task<UpdateCircleStatusResponse> UpdateStatusAsync(UpdateCircleStatusRequest request, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetCreators;
|
||||||
|
|
||||||
|
public sealed record CircleCreatorItem(
|
||||||
|
string CreatorName,
|
||||||
|
DateTime? FirstCollaborationDate,
|
||||||
|
DateTime? LastCollaborationDate,
|
||||||
|
int Downloads,
|
||||||
|
int Count)
|
||||||
|
{
|
||||||
|
public readonly int AverageDownloads = Downloads / Count;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetCreators;
|
||||||
|
|
||||||
|
public sealed class GetCircleCreatorsHandler(ICircleCreatorsProvider provider)
|
||||||
|
{
|
||||||
|
public Task<GetCircleCreatorsResponse> HandleAsync(GetCircleCreatorsRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return provider.HandleAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetCreators;
|
||||||
|
|
||||||
|
public sealed record GetCircleCreatorsRequest(int? CircleId, string? NameOrMakerId);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetCreators;
|
||||||
|
|
||||||
|
public sealed record GetCircleCreatorsResponse(IReadOnlyList<CircleCreatorItem> Items);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetCreators;
|
||||||
|
|
||||||
|
public interface ICircleCreatorsProvider
|
||||||
|
{
|
||||||
|
Task<GetCircleCreatorsResponse> HandleAsync(GetCircleCreatorsRequest request, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetTags;
|
||||||
|
|
||||||
|
public sealed record CircleTagItem(string TagName, string? EnglishTagName, int Count);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetTags;
|
||||||
|
|
||||||
|
public sealed class GetCircleTagsHandler(ICircleTagsProvider provider)
|
||||||
|
{
|
||||||
|
public Task<GetCircleTagsResponse> HandleAsync(GetCircleTagsRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return provider.HandleAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetTags;
|
||||||
|
|
||||||
|
public sealed record GetCircleTagsRequest(int? CircleId, string? NameOrMakerId);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetTags;
|
||||||
|
|
||||||
|
public sealed record GetCircleTagsResponse(IReadOnlyList<CircleTagItem> Items);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.GetTags;
|
||||||
|
|
||||||
|
public interface ICircleTagsProvider
|
||||||
|
{
|
||||||
|
Task<GetCircleTagsResponse> HandleAsync(GetCircleTagsRequest request, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public class CircleSearchCriteria
|
||||||
|
{
|
||||||
|
public string? Name { get; init; }
|
||||||
|
}
|
||||||
19
JSMR.Application/Circles/Queries/Search/CircleSearchItem.cs
Normal file
19
JSMR.Application/Circles/Queries/Search/CircleSearchItem.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public record CircleSearchItem
|
||||||
|
{
|
||||||
|
public int CircleId { get; init; }
|
||||||
|
public string Name { get; init; } = string.Empty;
|
||||||
|
public string MakerId { get; init; } = string.Empty;
|
||||||
|
public bool Blacklisted { get; init; }
|
||||||
|
public bool Favorite { get; init; }
|
||||||
|
public bool Spam { get; init; }
|
||||||
|
public int Downloads { get; init; }
|
||||||
|
public int Releases { get; init; }
|
||||||
|
public int Pending { get; init; }
|
||||||
|
public DateTime? FirstReleaseDate { get; init; }
|
||||||
|
public DateTime? LatestReleaseDate { get; init; }
|
||||||
|
public string? LatestProductId { get; init; }
|
||||||
|
public bool? LatestVoiceWorkHasImage { get; init; }
|
||||||
|
public DateTime? LatestVoiceWorkSalesDate { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
//public record TagSearchOptions : SearchOptions<TagSearchCriteria, TagSortField>
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public record CircleSearchResults : SearchResult<CircleSearchItem>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
10
JSMR.Application/Circles/Queries/Search/CircleSortField.cs
Normal file
10
JSMR.Application/Circles/Queries/Search/CircleSortField.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public enum CircleSortField
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
Blacklisted,
|
||||||
|
Favorite,
|
||||||
|
Spam,
|
||||||
|
VoiceWorkCount
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public interface ICircleSearchProvider : ISearchProvider<CircleSearchItem, CircleSearchCriteria, CircleSortField>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public sealed class SearchCirclesHandler(ICircleSearchProvider searchProvider)
|
||||||
|
{
|
||||||
|
public async Task<SearchCirclesResponse> HandleAsync(SearchCirclesRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
SearchOptions<CircleSearchCriteria, CircleSortField> searchOptions = request.Options;
|
||||||
|
|
||||||
|
SearchResult<CircleSearchItem> results = await searchProvider.SearchAsync(searchOptions, cancellationToken);
|
||||||
|
|
||||||
|
return new SearchCirclesResponse(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchCirclesRequest(SearchOptions<CircleSearchCriteria, CircleSortField> Options);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchCirclesResponse(SearchResult<CircleSearchItem> Results);
|
||||||
69
JSMR.Application/Common/Caching/CacheEntryOptions.cs
Normal file
69
JSMR.Application/Common/Caching/CacheEntryOptions.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
namespace JSMR.Application.Common.Caching;
|
||||||
|
|
||||||
|
public class CacheEntryOptions
|
||||||
|
{
|
||||||
|
private DateTimeOffset? _absoluteExpiration;
|
||||||
|
private TimeSpan? _absoluteExpirationRelativeToNow;
|
||||||
|
private TimeSpan? _slidingExpiration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an absolute expiration date for the cache entry.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset? AbsoluteExpiration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _absoluteExpiration;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_absoluteExpiration = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an absolute expiration time, relative to now.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan? AbsoluteExpirationRelativeToNow
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _absoluteExpirationRelativeToNow;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(AbsoluteExpirationRelativeToNow),
|
||||||
|
value,
|
||||||
|
"The relative expiration value must be positive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_absoluteExpirationRelativeToNow = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed.
|
||||||
|
/// This will not extend the entry lifetime beyond the absolute expiration (if set).
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan? SlidingExpiration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _slidingExpiration;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(SlidingExpiration),
|
||||||
|
value,
|
||||||
|
"The sliding expiration value must be positive.");
|
||||||
|
}
|
||||||
|
_slidingExpiration = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
JSMR.Application/Common/Caching/ICache.cs
Normal file
7
JSMR.Application/Common/Caching/ICache.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.Common.Caching;
|
||||||
|
|
||||||
|
public interface ICache
|
||||||
|
{
|
||||||
|
ValueTask<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
|
||||||
|
ValueTask SetAsync<T>(string key, T value, CacheEntryOptions options, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
11
JSMR.Application/Common/Language.cs
Normal file
11
JSMR.Application/Common/Language.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Application.Common;
|
||||||
|
|
||||||
|
public enum Language
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Japanese,
|
||||||
|
English,
|
||||||
|
ChineseSimplified,
|
||||||
|
ChineseTraditional,
|
||||||
|
Korean
|
||||||
|
}
|
||||||
8
JSMR.Application/Common/Search/ISearchProvider.cs
Normal file
8
JSMR.Application/Common/Search/ISearchProvider.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
public interface ISearchProvider<TItem, TCriteria, TSortField>
|
||||||
|
where TCriteria : new()
|
||||||
|
where TSortField : struct, Enum
|
||||||
|
{
|
||||||
|
Task<SearchResult<TItem>> SearchAsync(SearchOptions<TCriteria, TSortField> options, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
8
JSMR.Application/Common/Search/ISearchQueryHandler.cs
Normal file
8
JSMR.Application/Common/Search/ISearchQueryHandler.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
public interface ISearchQueryHandler<TItem, TCriteria, TSortField>
|
||||||
|
where TCriteria : new()
|
||||||
|
where TSortField : struct, Enum
|
||||||
|
{
|
||||||
|
Task<SearchResult<TItem>> HandleAsync(SearchOptions<TCriteria, TSortField> options, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
11
JSMR.Application/Common/Search/SearchOptions.cs
Normal file
11
JSMR.Application/Common/Search/SearchOptions.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
public sealed record SearchOptions<TCriteria, TSortField>
|
||||||
|
where TCriteria : new()
|
||||||
|
where TSortField : struct, Enum
|
||||||
|
{
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 100;
|
||||||
|
public TCriteria Criteria { get; set; } = new();
|
||||||
|
public SortOption<TSortField>[] SortOptions { get; set; } = [];
|
||||||
|
}
|
||||||
71
JSMR.Application/Common/Search/SearchReader.cs
Normal file
71
JSMR.Application/Common/Search/SearchReader.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//public abstract class SearchReader2<TItem, TCriteria, TSortField> : ISearchReader<TItem, TCriteria, TSortField>
|
||||||
|
// where TSortField : struct, Enum
|
||||||
|
//{
|
||||||
|
// public Task<SearchResult<TItem>> SearchAsync(SearchOptions<TCriteria, TSortField> options, CancellationToken cancellationToken = default)
|
||||||
|
// {
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public abstract class SearchReader<TSearchResultItem, TCriteria, TSortField> where TSortField : struct, Enum
|
||||||
|
//{
|
||||||
|
// public async Task<SearchResult<TSearchResultItem>> SearchAsync(SearchOptions<TCriteria, TSortField> options, CancellationToken cancellationToken = default)
|
||||||
|
// {
|
||||||
|
// IQueryable<TSearchResultItem> baseQuery = GetBaseQuery();
|
||||||
|
// IQueryable<TSearchResultItem> filteredQuery = ApplyFilters(baseQuery, options);
|
||||||
|
|
||||||
|
// int total = await filteredQuery.CountAsync(cancellationToken);
|
||||||
|
|
||||||
|
// IOrderedQueryable<TSearchResultItem> orderedQuery = ApplySorting(filteredQuery, options);
|
||||||
|
|
||||||
|
// TSearchResultItem[] items = await orderedQuery
|
||||||
|
// .Skip((options.PageNumber - 1) * options.PageSize)
|
||||||
|
// .Take(options.PageSize)
|
||||||
|
// .ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
// return new SearchResult<TSearchResultItem>()
|
||||||
|
// {
|
||||||
|
// Items = items,
|
||||||
|
// TotalItems = total
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// protected abstract IQueryable<TSearchResultItem> GetBaseQuery();
|
||||||
|
// protected abstract IQueryable<TSearchResultItem> ApplyFilters(IQueryable<TSearchResultItem> query, SearchOptions<TCriteria, TSortField> options);
|
||||||
|
|
||||||
|
// private IOrderedQueryable<TSearchResultItem> ApplySorting(IQueryable<TSearchResultItem> query, SearchOptions<TCriteria, TSortField> options)
|
||||||
|
// {
|
||||||
|
// IOrderedQueryable<TSearchResultItem>? ordered = null;
|
||||||
|
|
||||||
|
// for (int i = 0; i < options.SortOptions.Length; i++)
|
||||||
|
// {
|
||||||
|
// var (field, direction) = (options.SortOptions[i].Field, options.SortOptions[i].Direction);
|
||||||
|
// bool isDescending = direction == SortDirection.Descending;
|
||||||
|
|
||||||
|
// IOrderedQueryable<TSearchResultItem> applyFirst(Expression<Func<TSearchResultItem, object>> selector) => isDescending ? query.OrderByDescending(selector) : query.OrderBy(selector);
|
||||||
|
// IOrderedQueryable<TSearchResultItem> applyNext(Expression<Func<TSearchResultItem, object>> selector) => isDescending ? ordered!.ThenByDescending(selector) : ordered!.ThenBy(selector);
|
||||||
|
|
||||||
|
// Expression<Func<TSearchResultItem, object>> selector = GetSortExpression(field);
|
||||||
|
|
||||||
|
// ordered = (i == 0) ? applyFirst(selector) : applyNext(selector);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return ordered ?? GetDefaultSortExpression(query);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// protected abstract Expression<Func<TSearchResultItem, object>> GetSortExpression(TSortField field);
|
||||||
|
// protected abstract IOrderedQueryable<TSearchResultItem> GetDefaultSortExpression(IQueryable<TSearchResultItem> query);
|
||||||
|
//}
|
||||||
7
JSMR.Application/Common/Search/SearchResult.cs
Normal file
7
JSMR.Application/Common/Search/SearchResult.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
public record SearchResult<T>
|
||||||
|
{
|
||||||
|
public T[] Items { get; set; } = [];
|
||||||
|
public int TotalItems { get; set; }
|
||||||
|
}
|
||||||
7
JSMR.Application/Common/Search/SortDirection.cs
Normal file
7
JSMR.Application/Common/Search/SortDirection.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
public enum SortDirection
|
||||||
|
{
|
||||||
|
Ascending = 0,
|
||||||
|
Descending = 1
|
||||||
|
}
|
||||||
3
JSMR.Application/Common/Search/SortOption.cs
Normal file
3
JSMR.Application/Common/Search/SortOption.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
public record SortOption<TSortField>(TSortField Field, SortDirection Direction) where TSortField : struct, Enum;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using JSMR.Application.Creators.Ports;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Commands.UpdateCreatorStatus;
|
||||||
|
|
||||||
|
public class UpdateCreatorStatusHandler(ICreatorWriter writer)
|
||||||
|
{
|
||||||
|
public Task<UpdateCreatorStatusResponse> HandleAsync(UpdateCreatorStatusRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return writer.UpdateStatusAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Creators.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Commands.UpdateCreatorStatus;
|
||||||
|
|
||||||
|
public sealed record UpdateCreatorStatusRequest(int CreatorId, CreatorStatus CreatorStatus);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Creators.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Commands.UpdateCreatorStatus;
|
||||||
|
|
||||||
|
public sealed record UpdateCreatorStatusResponse(int CreatorId, CreatorStatus CreatorStatus);
|
||||||
8
JSMR.Application/Creators/Contracts/CreatorStatus.cs
Normal file
8
JSMR.Application/Creators/Contracts/CreatorStatus.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Creators.Contracts;
|
||||||
|
|
||||||
|
public enum CreatorStatus
|
||||||
|
{
|
||||||
|
Neutral,
|
||||||
|
Favorite,
|
||||||
|
Blacklisted
|
||||||
|
}
|
||||||
6
JSMR.Application/Creators/Ports/ICreatorReader.cs
Normal file
6
JSMR.Application/Creators/Ports/ICreatorReader.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Creators.Ports;
|
||||||
|
|
||||||
|
public interface ICircleReader
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
8
JSMR.Application/Creators/Ports/ICreatorWriter.cs
Normal file
8
JSMR.Application/Creators/Ports/ICreatorWriter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Creators.Commands.UpdateCreatorStatus;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Ports;
|
||||||
|
|
||||||
|
public interface ICreatorWriter
|
||||||
|
{
|
||||||
|
Task<UpdateCreatorStatusResponse> UpdateStatusAsync(UpdateCreatorStatusRequest request, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public class CreatorSearchCriteria
|
||||||
|
{
|
||||||
|
public string? Name { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public record CreatorSearchItem
|
||||||
|
{
|
||||||
|
public int CreatorId { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public bool Favorite { get; init; }
|
||||||
|
public bool Blacklisted { get; init; }
|
||||||
|
public int VoiceWorkCount { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
//public record TagSearchOptions : SearchOptions<TagSearchCriteria, TagSortField>
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public record CreatorSearchResults : SearchResult<CreatorSearchItem>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public enum CreatorSortField
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
Blacklisted,
|
||||||
|
Favorite,
|
||||||
|
VoiceWorkCount
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Queries.Search.Ports;
|
||||||
|
|
||||||
|
public interface ICreatorSearchProvider : ISearchProvider<CreatorSearchItem, CreatorSearchCriteria, CreatorSortField>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using JSMR.Application.Common.Caching;
|
||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
using JSMR.Application.Creators.Queries.Search.Ports;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Queries.Search;
|
||||||
|
|
||||||
|
public sealed class SearchCreatorsHandler(ICreatorSearchProvider searchProvider, ICache cache)
|
||||||
|
{
|
||||||
|
public async Task<SearchCreatorsResponse> HandleAsync(SearchCreatorsRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
SearchOptions<CreatorSearchCriteria, CreatorSortField> searchOptions = request.Options;
|
||||||
|
|
||||||
|
string cacheKey = $"creator:{searchOptions.GetHashCode()}";
|
||||||
|
|
||||||
|
CreatorSearchResults? cachedResults = await cache.GetAsync<CreatorSearchResults>(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (cachedResults != null)
|
||||||
|
return new SearchCreatorsResponse(cachedResults);
|
||||||
|
|
||||||
|
SearchResult<CreatorSearchItem> results = await searchProvider.SearchAsync(searchOptions, cancellationToken);
|
||||||
|
|
||||||
|
CacheEntryOptions cacheEntryOptions = new()
|
||||||
|
{
|
||||||
|
SlidingExpiration = TimeSpan.FromMinutes(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
await cache.SetAsync(cacheKey, results, cacheEntryOptions, cancellationToken);
|
||||||
|
|
||||||
|
return new SearchCreatorsResponse(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchCreatorsRequest(SearchOptions<CreatorSearchCriteria, CreatorSortField> Options);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Creators.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Creators.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchCreatorsResponse(SearchResult<CreatorSearchItem> Results);
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using JSMR.Application.Tags.Commands.SetEnglishName;
|
||||||
|
using JSMR.Application.Tags.Commands.UpdateTagStatus;
|
||||||
|
using JSMR.Application.VoiceWorks.Search;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace JSMR.Application.DI;
|
||||||
|
|
||||||
|
public static class ApplicationServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddApplication(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Handlers / Use-cases
|
||||||
|
//services.AddScoped<SearchVoiceWorksHandler>();
|
||||||
|
//services.AddScoped<ScanVoiceWorksHandler>();
|
||||||
|
|
||||||
|
services.AddScoped<SetTagEnglishNameHandler>();
|
||||||
|
services.AddScoped<UpdateTagStatusHandler>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.Integrations.Chobit.Models;
|
||||||
|
|
||||||
|
public class ChobitResult
|
||||||
|
{
|
||||||
|
public int Count { get; set; }
|
||||||
|
public ChobitWork[] Works { get; set; } = [];
|
||||||
|
}
|
||||||
16
JSMR.Application/Integrations/Chobit/Models/ChobitWork.cs
Normal file
16
JSMR.Application/Integrations/Chobit/Models/ChobitWork.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace JSMR.Application.Integrations.Chobit.Models;
|
||||||
|
|
||||||
|
public class ChobitWork
|
||||||
|
{
|
||||||
|
public string? WorkId { get; set; }
|
||||||
|
public string? DLSiteWorkId { get; set; }
|
||||||
|
public string? WorkName { get; set; }
|
||||||
|
public string? WorkNameKana { get; set; }
|
||||||
|
public string? URL { get; set; }
|
||||||
|
public string? EmbedURL { get; set; }
|
||||||
|
public string? Thumb { get; set; }
|
||||||
|
public string? MiniThumb { get; set; }
|
||||||
|
public string? FileType { get; set; }
|
||||||
|
public decimal EmbedWidth { get; set; }
|
||||||
|
public decimal EmbedHeight { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Integrations.Chobit.Models;
|
||||||
|
|
||||||
|
public class ChobitWorkResult : Dictionary<string, ChobitResult> { }
|
||||||
8
JSMR.Application/Integrations/Cien/Models/CienArticle.cs
Normal file
8
JSMR.Application/Integrations/Cien/Models/CienArticle.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Integrations.Cien.Models;
|
||||||
|
|
||||||
|
public class CienArticle
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public required string Title { get; set; }
|
||||||
|
public required DateTime ReleasedAt { get; set; }
|
||||||
|
}
|
||||||
10
JSMR.Application/Integrations/Cien/Models/CienCreator.cs
Normal file
10
JSMR.Application/Integrations/Cien/Models/CienCreator.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Application.Integrations.Cien.Models;
|
||||||
|
|
||||||
|
public class CienCreator
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public string? Rating { get; set; }
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
public CienArticle[] Articles { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
|
||||||
|
public class VoiceWorkDetailCollection : Dictionary<string, VoiceWorkDetails>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
|
||||||
|
public class VoiceWorkDetails
|
||||||
|
{
|
||||||
|
public VoiceWorkSeries? Series { get; init; }
|
||||||
|
public VoiceWorkTranslation? Translation { get; init; }
|
||||||
|
public int WishlistCount { get; init; }
|
||||||
|
public int DownloadCount { get; init; }
|
||||||
|
public DateTime? RegistrationDate { get; init; }
|
||||||
|
public Language[] SupportedLanguages { get; init; } = [];
|
||||||
|
//public AIGeneration AI { get; init; }
|
||||||
|
public bool HasTrial { get; init; }
|
||||||
|
public bool HasReviews { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
|
||||||
|
public class VoiceWorkSeries
|
||||||
|
{
|
||||||
|
public required string Identifier { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
|
||||||
|
public class VoiceWorkTranslation
|
||||||
|
{
|
||||||
|
public required string OriginalProductId { get; init; }
|
||||||
|
public bool IsOfficialTranslation { get; init; }
|
||||||
|
public required Language Language { get; init; }
|
||||||
|
}
|
||||||
8
JSMR.Application/Integrations/Ports/IChobitClient.cs
Normal file
8
JSMR.Application/Integrations/Ports/IChobitClient.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Integrations.Chobit.Models;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Integrations.Ports;
|
||||||
|
|
||||||
|
public interface IChobitClient
|
||||||
|
{
|
||||||
|
Task<ChobitWorkResult> GetSampleInfoAsync(string[] productIds, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
8
JSMR.Application/Integrations/Ports/ICienClient.cs
Normal file
8
JSMR.Application/Integrations/Ports/ICienClient.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Integrations.Cien.Models;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Integrations.Ports;
|
||||||
|
|
||||||
|
public interface ICienClient
|
||||||
|
{
|
||||||
|
Task<CienCreator[]> GetCienCreatorAsync(string makerId, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
8
JSMR.Application/Integrations/Ports/IDLSiteClient.cs
Normal file
8
JSMR.Application/Integrations/Ports/IDLSiteClient.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Integrations.Ports;
|
||||||
|
|
||||||
|
public interface IDLSiteClient
|
||||||
|
{
|
||||||
|
Task<VoiceWorkDetailCollection> GetVoiceWorkDetailsAsync(string[] productIds, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
17
JSMR.Application/JSMR.Application.csproj
Normal file
17
JSMR.Application/JSMR.Application.csproj
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.8" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Tags\Queries\Ports\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using JSMR.Application.Tags.Ports;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Commands.SetEnglishName;
|
||||||
|
|
||||||
|
public sealed class SetTagEnglishNameHandler(ITagWriter tagWriter)
|
||||||
|
{
|
||||||
|
public async Task<SetTagEnglishNameResponse> HandleAsync(SetTagEnglishNameRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.EnglishName))
|
||||||
|
throw new ArgumentException("English name is required.", nameof(request));
|
||||||
|
|
||||||
|
string trimmed = request.EnglishName.Trim();
|
||||||
|
SetTagEnglishNameResponse result = await tagWriter.SetEnglishNameAsync(request with { EnglishName = trimmed }, cancellationToken);
|
||||||
|
|
||||||
|
// Optional: refresh search materialization for all voice works that use this tag
|
||||||
|
//await _searchUpdater.UpdateForTagAsync(request.TagId, cancellationToken);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Tags.Commands.SetEnglishName;
|
||||||
|
|
||||||
|
public sealed record SetTagEnglishNameRequest(int TagId, string EnglishName);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.Tags.Commands.SetEnglishName;
|
||||||
|
|
||||||
|
public sealed record SetTagEnglishNameResponse(int TagId, string EnglishName);
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using JSMR.Application.Tags.Ports;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Commands.UpdateTagStatus;
|
||||||
|
|
||||||
|
public sealed class UpdateTagStatusHandler(ITagWriter writer)
|
||||||
|
{
|
||||||
|
public Task<UpdateTagStatusResponse> HandleAsync(UpdateTagStatusRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return writer.UpdateStatusAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Tags.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Commands.UpdateTagStatus;
|
||||||
|
|
||||||
|
public sealed record UpdateTagStatusRequest(int TagId, TagStatus TagStatus);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.Tags.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Commands.UpdateTagStatus;
|
||||||
|
|
||||||
|
public sealed record UpdateTagStatusResponse(int TagId, TagStatus TagStatus);
|
||||||
8
JSMR.Application/Tags/Contracts/TagStatus.cs
Normal file
8
JSMR.Application/Tags/Contracts/TagStatus.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Tags.Contracts;
|
||||||
|
|
||||||
|
public enum TagStatus
|
||||||
|
{
|
||||||
|
Neutral,
|
||||||
|
Favorite,
|
||||||
|
Blacklisted
|
||||||
|
}
|
||||||
8
JSMR.Application/Tags/Ports/ITagReader.cs
Normal file
8
JSMR.Application/Tags/Ports/ITagReader.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Ports;
|
||||||
|
|
||||||
|
public interface ITagReader
|
||||||
|
{
|
||||||
|
//Task<TagSearchResults> SearchAsync(TagSearchOptions options, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
10
JSMR.Application/Tags/Ports/ITagWriter.cs
Normal file
10
JSMR.Application/Tags/Ports/ITagWriter.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using JSMR.Application.Tags.Commands.SetEnglishName;
|
||||||
|
using JSMR.Application.Tags.Commands.UpdateTagStatus;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Ports;
|
||||||
|
|
||||||
|
public interface ITagWriter
|
||||||
|
{
|
||||||
|
Task<SetTagEnglishNameResponse> SetEnglishNameAsync(SetTagEnglishNameRequest request, CancellationToken cancellationToken = default);
|
||||||
|
Task<UpdateTagStatusResponse> UpdateStatusAsync(UpdateTagStatusRequest request, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public class TagSearchCriteria
|
||||||
|
{
|
||||||
|
public string? Name { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public record TagSearchItem
|
||||||
|
{
|
||||||
|
public int TagId { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public bool Favorite { get; init; }
|
||||||
|
public bool Blacklisted { get; init; }
|
||||||
|
public string? EnglishName { get; init; }
|
||||||
|
public int VoiceWorkCount { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
//public record TagSearchOptions : SearchOptions<TagSearchCriteria, TagSortField>
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public record TagSearchResults : SearchResult<TagSearchItem>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
public enum TagSortField
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
EnglishName,
|
||||||
|
Blacklisted,
|
||||||
|
Favorite,
|
||||||
|
VoiceWorkCount
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Queries.Search.Ports;
|
||||||
|
|
||||||
|
public interface ITagSearchProvider : ISearchProvider<TagSearchItem, TagSearchCriteria, TagSortField>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
32
JSMR.Application/Tags/Queries/Search/SearchTagsHandler.cs
Normal file
32
JSMR.Application/Tags/Queries/Search/SearchTagsHandler.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using JSMR.Application.Common.Caching;
|
||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
using JSMR.Application.Tags.Queries.Search.Ports;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Queries.Search;
|
||||||
|
|
||||||
|
public sealed class SearchTagsHandler(ITagSearchProvider searchProvider, ICache cache)
|
||||||
|
{
|
||||||
|
public async Task<SearchTagsResponse> HandleAsync(SearchTagsRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
SearchOptions<TagSearchCriteria, TagSortField> searchOptions = request.Options;
|
||||||
|
|
||||||
|
string cacheKey = $"tag:{searchOptions.GetHashCode()}";
|
||||||
|
|
||||||
|
TagSearchResults? cachedResults = await cache.GetAsync<TagSearchResults>(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (cachedResults != null)
|
||||||
|
return new SearchTagsResponse(cachedResults);
|
||||||
|
|
||||||
|
SearchResult<TagSearchItem> results = await searchProvider.SearchAsync(searchOptions, cancellationToken);
|
||||||
|
|
||||||
|
CacheEntryOptions cacheEntryOptions = new()
|
||||||
|
{
|
||||||
|
SlidingExpiration = TimeSpan.FromMinutes(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
await cache.SetAsync(cacheKey, results, cacheEntryOptions, cancellationToken);
|
||||||
|
|
||||||
|
return new SearchTagsResponse(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchTagsRequest(SearchOptions<TagSearchCriteria, TagSortField> Options);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Tags.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchTagsResponse(SearchResult<TagSearchItem> Results);
|
||||||
9
JSMR.Application/VoiceWorks/Ports/IVoiceWorkReader.cs
Normal file
9
JSMR.Application/VoiceWorks/Ports/IVoiceWorkReader.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Ports;
|
||||||
|
|
||||||
|
public interface IVoiceWorkReader
|
||||||
|
{
|
||||||
|
//Task<VoiceWorkSearchResults> SearchAsync(VoiceWorkSearchOptions options, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
public class VoiceWorkSearchCriteria
|
||||||
|
{
|
||||||
|
public string? Keywords { get; init; }
|
||||||
|
public string? Title { get; init; }
|
||||||
|
public string? Circle { get; init; }
|
||||||
|
//public SaleStatus SaleStatus { get; init; }
|
||||||
|
//public CircleStatus CircleStatus { get; init; }
|
||||||
|
//public TagStatus TagStatus { get; init; }
|
||||||
|
//public CreatorStatus CreatorStatus { get; init; }
|
||||||
|
public int[] TagIds { get; init; } = [];
|
||||||
|
public bool IncludeAllTags { get; init; }
|
||||||
|
public int[] CreatorIds { get; init; } = [];
|
||||||
|
public bool IncludeAllCreators { get; init; }
|
||||||
|
//public VoiceWorkSort Sort { get; init; }
|
||||||
|
//public VoiceWorkLanguage Language { get; init; }
|
||||||
|
public DateTime? ReleaseDateStart { get; init; }
|
||||||
|
public DateTime? ReleaseDateEnd { get; init; }
|
||||||
|
//public List<AgeRating> AgeRatings { get; init; }
|
||||||
|
public Language[] SupportedLanguages { get; init; } = [];
|
||||||
|
//public List<AIGeneration> AIGenerationOptions { get; init; }
|
||||||
|
public bool ShowFavoriteVoiceWorks { get; init; }
|
||||||
|
public bool ShowInvalidVoiceWorks { get; init; }
|
||||||
|
public int? MinDownloads { get; init; }
|
||||||
|
public int? MaxDownloads { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
//public record VoiceWorkSearchOptions : SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
public record VoiceWorkSearchResults : SearchResult<object>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
public enum VoiceWorkSortField
|
||||||
|
{
|
||||||
|
ReleaseDateNewToOld,
|
||||||
|
ReleaseDateOldToNew,
|
||||||
|
BestSelling,
|
||||||
|
MostWishedFor,
|
||||||
|
SalesToWishlistRatio,
|
||||||
|
StarRating
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using JSMR.Application.Common.Caching;
|
||||||
|
using JSMR.Application.VoiceWorks.Ports;
|
||||||
|
using JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Search;
|
||||||
|
|
||||||
|
//public sealed class SearchVoiceWorksHandler(IVoiceWorkReader reader, ICache cache)
|
||||||
|
//{
|
||||||
|
// //public async Task<SearchVoiceWorksResponse> HandleAsync(SearchVoiceWorksRequest request, CancellationToken cancellationToken)
|
||||||
|
// //{
|
||||||
|
// // VoiceWorkSearchOptions searchOptions = request.Options;
|
||||||
|
|
||||||
|
// // string cacheKey = $"vw:{searchOptions.GetHashCode()}";
|
||||||
|
|
||||||
|
// // VoiceWorkSearchResults? cachedResults = await cache.GetAsync<VoiceWorkSearchResults>(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
// // if (cachedResults != null)
|
||||||
|
// // return new SearchVoiceWorksResponse(cachedResults);
|
||||||
|
|
||||||
|
// // VoiceWorkSearchResults results = await reader.SearchAsync(searchOptions, cancellationToken);
|
||||||
|
|
||||||
|
// // CacheEntryOptions cacheEntryOptions = new()
|
||||||
|
// // {
|
||||||
|
// // SlidingExpiration = TimeSpan.FromMinutes(10)
|
||||||
|
// // };
|
||||||
|
|
||||||
|
// // await cache.SetAsync(cacheKey, results, cacheEntryOptions, cancellationToken);
|
||||||
|
|
||||||
|
// // return new SearchVoiceWorksResponse(results);
|
||||||
|
// //}
|
||||||
|
//}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Search;
|
||||||
|
|
||||||
|
public sealed record SearchVoiceWorksRequest(SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField> Options);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using JSMR.Application.VoiceWorks.Search.Contracts;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Search;
|
||||||
|
|
||||||
|
public sealed record SearchVoiceWorksResponse(VoiceWorkSearchResults Results);
|
||||||
11
JSMR.Domain/Entities/Circle.cs
Normal file
11
JSMR.Domain/Entities/Circle.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class Circle
|
||||||
|
{
|
||||||
|
public int CircleId { get; set; }
|
||||||
|
public required string MakerId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public bool Blacklisted { get; set; }
|
||||||
|
public bool Favorite { get; set; }
|
||||||
|
public bool Spam { get; set; }
|
||||||
|
}
|
||||||
9
JSMR.Domain/Entities/Creator.cs
Normal file
9
JSMR.Domain/Entities/Creator.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class Creator
|
||||||
|
{
|
||||||
|
public int CreatorId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public bool Favorite { get; set; }
|
||||||
|
public bool Blacklisted { get; set; }
|
||||||
|
}
|
||||||
11
JSMR.Domain/Entities/EnglishTag.cs
Normal file
11
JSMR.Domain/Entities/EnglishTag.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class EnglishTag
|
||||||
|
{
|
||||||
|
public int EnglishTagId { get; set; }
|
||||||
|
|
||||||
|
public int TagId { get; set; }
|
||||||
|
public Tag? Tag { get; set; }
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
12
JSMR.Domain/Entities/EnglishVoiceWork.cs
Normal file
12
JSMR.Domain/Entities/EnglishVoiceWork.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class EnglishVoiceWork
|
||||||
|
{
|
||||||
|
public int EnglishVoiceWorkId { get; set; }
|
||||||
|
public required string ProductName { get; set; }
|
||||||
|
public required string Description { get; set; }
|
||||||
|
public bool? IsValid { get; set; }
|
||||||
|
|
||||||
|
public int VoiceWorkId { get; set; }
|
||||||
|
public VoiceWork? VoiceWork { get; set; }
|
||||||
|
}
|
||||||
12
JSMR.Domain/Entities/Series.cs
Normal file
12
JSMR.Domain/Entities/Series.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class Series
|
||||||
|
{
|
||||||
|
public int SeriesId { get; set; }
|
||||||
|
|
||||||
|
public int CircleId { get; set; }
|
||||||
|
public Circle? Circle { get; set; }
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string Identifier { get; set; }
|
||||||
|
}
|
||||||
9
JSMR.Domain/Entities/Tag.cs
Normal file
9
JSMR.Domain/Entities/Tag.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class Tag
|
||||||
|
{
|
||||||
|
public int TagId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public bool Favorite { get; set; }
|
||||||
|
public bool Blacklisted { get; set; }
|
||||||
|
}
|
||||||
42
JSMR.Domain/Entities/VoiceWork.cs
Normal file
42
JSMR.Domain/Entities/VoiceWork.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class VoiceWork
|
||||||
|
{
|
||||||
|
public int VoiceWorkId { get; set; }
|
||||||
|
public required string ProductId { get; set; }
|
||||||
|
public string? OriginalProductId { get; set; }
|
||||||
|
public required string ProductName { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public int Rating { get; set; }
|
||||||
|
public bool HasImage { get; set; }
|
||||||
|
public bool HasTrial { get; set; }
|
||||||
|
public bool Purchased { get; set; }
|
||||||
|
public bool Favorite { get; set; }
|
||||||
|
public DateTime? ExpectedDate { get; set; }
|
||||||
|
public DateTime? SalesDate { get; set; }
|
||||||
|
public int? Downloads { get; set; }
|
||||||
|
public byte? StarRating { get; set; }
|
||||||
|
public int? Votes { get; set; }
|
||||||
|
public bool? IsValid { get; set; }
|
||||||
|
public DateTime? LastScannedDate { get; set; }
|
||||||
|
public byte Status { get; set; }
|
||||||
|
public byte SubtitleLanguage { get; set; }
|
||||||
|
public bool HasChobit { get; set; }
|
||||||
|
public bool IsPurchased { get; set; }
|
||||||
|
public int? WishlistCount { get; set; }
|
||||||
|
public DateTime? PlannedReleaseDate { get; set; }
|
||||||
|
public byte AIGeneration { get; set; }
|
||||||
|
|
||||||
|
public int CircleId { get; set; }
|
||||||
|
public Circle? Circle { get; set; }
|
||||||
|
|
||||||
|
public int? SeriesId { get; set; }
|
||||||
|
public Series? Series { get; set; }
|
||||||
|
|
||||||
|
//public int? VoiceWorkSearchId { get; set; }
|
||||||
|
//public VoiceWorkSearch? VoiceWorkSearch { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<VoiceWorkTag> VoiceWorkTags { get; set; } = [];
|
||||||
|
public virtual ICollection<VoiceWorkCreator> VoiceWorkCreators { get; set; } = [];
|
||||||
|
public virtual ICollection<EnglishVoiceWork> EnglishVoiceWorks { get; set; } = [];
|
||||||
|
}
|
||||||
14
JSMR.Domain/Entities/VoiceWorkCreator.cs
Normal file
14
JSMR.Domain/Entities/VoiceWorkCreator.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class VoiceWorkCreator
|
||||||
|
{
|
||||||
|
public int VoiceWorkCreatorId { get; set; }
|
||||||
|
public int? Position { get; set; }
|
||||||
|
public bool? IsValid { get; set; }
|
||||||
|
|
||||||
|
public int CreatorId { get; set; }
|
||||||
|
public Creator? Creator { get; set; }
|
||||||
|
|
||||||
|
public int VoiceWorkId { get; set; }
|
||||||
|
public VoiceWork? VoiceWork { get; set; }
|
||||||
|
}
|
||||||
9
JSMR.Domain/Entities/VoiceWorkSearch.cs
Normal file
9
JSMR.Domain/Entities/VoiceWorkSearch.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class VoiceWorkSearch
|
||||||
|
{
|
||||||
|
public int VoiceWorkId { get; set; }
|
||||||
|
public VoiceWork? VoiceWork { get; set; }
|
||||||
|
|
||||||
|
public required string SearchText { get; set; }
|
||||||
|
}
|
||||||
14
JSMR.Domain/Entities/VoiceWorkTag.cs
Normal file
14
JSMR.Domain/Entities/VoiceWorkTag.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public class VoiceWorkTag
|
||||||
|
{
|
||||||
|
public int VoiceWorkTagId { get; set; }
|
||||||
|
public int? Position { get; set; }
|
||||||
|
public bool? IsValid { get; set; }
|
||||||
|
|
||||||
|
public int TagId { get; set; }
|
||||||
|
public Tag? Tag { get; set; }
|
||||||
|
|
||||||
|
public int VoiceWorkId { get; set; }
|
||||||
|
public VoiceWork? VoiceWork { get; set; }
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user