forked from xiongyuxing/tiku-backend.net
feat: add asset and video readonly endpoints
This commit is contained in:
323
Tiku.Infrastructure/Assets/AssetQueryService.cs
Normal file
323
Tiku.Infrastructure/Assets/AssetQueryService.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Assets;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Domain.Content;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.Assets;
|
||||
|
||||
public sealed class AssetQueryService(TikuDbContext dbContext) : IAssetQueryService
|
||||
{
|
||||
private const int DefaultLimit = 100;
|
||||
private const int MaxLimit = 500;
|
||||
|
||||
public async Task<CatalogList<ContentAssetCatalogItem>> GetContentAssetsAsync(
|
||||
AssetFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = dbContext.ContentAssets
|
||||
.AsNoTracking()
|
||||
.Where(asset => asset.TenantId == filter.TenantId);
|
||||
|
||||
if (!filter.IncludeInactive)
|
||||
{
|
||||
query = query.Where(asset => asset.Status == ContentStatus.Active);
|
||||
}
|
||||
|
||||
if (!filter.IncludeLocked)
|
||||
{
|
||||
query = query.Where(asset => asset.Visibility == ContentVisibility.Public || asset.IsPublic);
|
||||
}
|
||||
|
||||
if (filter.RegionId.HasValue)
|
||||
{
|
||||
query = query.Where(asset => asset.RegionId == filter.RegionId.Value || asset.RegionId == null);
|
||||
}
|
||||
|
||||
if (filter.SubjectId.HasValue)
|
||||
{
|
||||
query = query.Where(asset => asset.SubjectId == filter.SubjectId.Value || asset.SubjectId == null);
|
||||
}
|
||||
|
||||
if (filter.CategoryId.HasValue)
|
||||
{
|
||||
query = query.Where(asset => asset.CategoryId == filter.CategoryId.Value || asset.CategoryId == null);
|
||||
}
|
||||
|
||||
if (filter.ContentNodeId.HasValue)
|
||||
{
|
||||
query = query.Where(asset => asset.ContentNodeId == filter.ContentNodeId.Value || asset.ContentNodeId == null);
|
||||
}
|
||||
|
||||
if (filter.AssetId.HasValue)
|
||||
{
|
||||
query = query.Where(asset => asset.Id == filter.AssetId.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.AssetType) &&
|
||||
Enum.TryParse<ContentAssetType>(filter.AssetType, ignoreCase: true, out var assetType))
|
||||
{
|
||||
query = query.Where(asset => asset.AssetType == assetType);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Category))
|
||||
{
|
||||
var category = filter.Category.Trim();
|
||||
query = query.Where(asset => asset.Category == category);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.AssetKey))
|
||||
{
|
||||
var assetKey = filter.AssetKey.Trim();
|
||||
query = query.Where(asset => asset.AssetKey == assetKey);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
var keyword = filter.Keyword.Trim();
|
||||
query = query.Where(asset =>
|
||||
(asset.Title != null && asset.Title.Contains(keyword)) ||
|
||||
(asset.FileName != null && asset.FileName.Contains(keyword)) ||
|
||||
(asset.Description != null && asset.Description.Contains(keyword)) ||
|
||||
(asset.AssetKey != null && asset.AssetKey.Contains(keyword)));
|
||||
}
|
||||
|
||||
var items = await query
|
||||
.OrderBy(asset => asset.SortOrder)
|
||||
.ThenByDescending(asset => asset.CreatedAt)
|
||||
.Take(ResolveLimit(filter.Limit))
|
||||
.Select(asset => new ContentAssetCatalogItem(
|
||||
asset.Id,
|
||||
asset.LegacyId,
|
||||
asset.RegionId,
|
||||
asset.SubjectId,
|
||||
asset.CategoryId,
|
||||
asset.ContentNodeId,
|
||||
asset.AssetKey,
|
||||
asset.Title,
|
||||
asset.Category,
|
||||
asset.Description,
|
||||
asset.FileName,
|
||||
asset.CdnUrl,
|
||||
asset.IsPublic,
|
||||
asset.AssetType,
|
||||
asset.StorageProvider,
|
||||
asset.Bucket,
|
||||
asset.ObjectKey,
|
||||
asset.MimeType,
|
||||
asset.FileSizeBytes,
|
||||
asset.ChecksumSha256,
|
||||
asset.Visibility,
|
||||
asset.PreviewUrl,
|
||||
asset.Status,
|
||||
asset.SortOrder,
|
||||
asset.UploadStatus,
|
||||
asset.VerifiedAt,
|
||||
asset.PreviewStatus,
|
||||
asset.SecurityScanStatus,
|
||||
asset.Metadata))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return new CatalogList<ContentAssetCatalogItem>(items);
|
||||
}
|
||||
|
||||
public async Task<CatalogList<ImageAssetCatalogItem>> GetImagesAsync(
|
||||
AssetFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = dbContext.Images
|
||||
.AsNoTracking()
|
||||
.Where(image => image.TenantId == filter.TenantId);
|
||||
|
||||
if (!filter.IncludeLocked)
|
||||
{
|
||||
query = query.Where(image => image.IsPublic);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Category))
|
||||
{
|
||||
var category = filter.Category.Trim();
|
||||
query = query.Where(image => image.Category == category);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
var keyword = filter.Keyword.Trim();
|
||||
query = query.Where(image =>
|
||||
(image.Title != null && image.Title.Contains(keyword)) ||
|
||||
(image.FileName != null && image.FileName.Contains(keyword)));
|
||||
}
|
||||
|
||||
var items = await query
|
||||
.OrderBy(image => image.Category)
|
||||
.ThenBy(image => image.Title)
|
||||
.Take(ResolveLimit(filter.Limit))
|
||||
.Select(image => new ImageAssetCatalogItem(
|
||||
image.Id,
|
||||
image.LegacyId,
|
||||
image.Title,
|
||||
image.Category,
|
||||
image.FileName,
|
||||
image.IsPublic,
|
||||
image.CdnUrl,
|
||||
image.Metadata))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return new CatalogList<ImageAssetCatalogItem>(items);
|
||||
}
|
||||
|
||||
public async Task<CatalogList<AppAssetCatalogItem>> GetAppAssetsAsync(
|
||||
AssetFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = dbContext.AppAssets
|
||||
.AsNoTracking()
|
||||
.Where(asset => asset.TenantId == filter.TenantId);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.AssetKey))
|
||||
{
|
||||
var assetKey = filter.AssetKey.Trim();
|
||||
query = query.Where(asset => asset.AssetKey == assetKey);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
var keyword = filter.Keyword.Trim();
|
||||
query = query.Where(asset =>
|
||||
asset.AssetKey.Contains(keyword) ||
|
||||
(asset.FileName != null && asset.FileName.Contains(keyword)) ||
|
||||
(asset.Description != null && asset.Description.Contains(keyword)));
|
||||
}
|
||||
|
||||
var items = await query
|
||||
.OrderBy(asset => asset.AssetKey)
|
||||
.Take(ResolveLimit(filter.Limit))
|
||||
.Select(asset => new AppAssetCatalogItem(
|
||||
asset.Id,
|
||||
asset.LegacyId,
|
||||
asset.AssetKey,
|
||||
asset.FileName,
|
||||
asset.Description,
|
||||
asset.Metadata))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return new CatalogList<AppAssetCatalogItem>(items);
|
||||
}
|
||||
|
||||
public async Task<CatalogList<VideoExplanationCatalogItem>> GetVideoExplanationsAsync(
|
||||
AssetFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = dbContext.VideoExplanations
|
||||
.AsNoTracking()
|
||||
.Where(video => video.TenantId == filter.TenantId);
|
||||
|
||||
if (!filter.IncludeInactive)
|
||||
{
|
||||
query = query.Where(video => video.IsActive);
|
||||
}
|
||||
|
||||
if (filter.SubjectId.HasValue)
|
||||
{
|
||||
query = query.Where(video => video.SubjectId == filter.SubjectId.Value || video.SubjectId == null);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
var keyword = filter.Keyword.Trim();
|
||||
query = query.Where(video =>
|
||||
video.Title.Contains(keyword) ||
|
||||
(video.Description != null && video.Description.Contains(keyword)));
|
||||
}
|
||||
|
||||
var items = await query
|
||||
.OrderBy(video => video.SortOrder)
|
||||
.ThenBy(video => video.Title)
|
||||
.Take(ResolveLimit(filter.Limit))
|
||||
.Select(video => new VideoExplanationCatalogItem(
|
||||
video.Id,
|
||||
video.LegacyId,
|
||||
video.SubjectId,
|
||||
video.Title,
|
||||
video.Description,
|
||||
video.VideoUrl,
|
||||
video.ThumbnailUrl,
|
||||
video.DurationSeconds,
|
||||
video.KnowledgeTags,
|
||||
video.IsGeneral,
|
||||
video.Difficulty,
|
||||
video.SortOrder,
|
||||
video.IsActive,
|
||||
video.Metadata))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return new CatalogList<VideoExplanationCatalogItem>(items);
|
||||
}
|
||||
|
||||
public async Task<CatalogList<QuestionVideoCatalogItem>> GetQuestionVideosAsync(
|
||||
AssetFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = dbContext.QuestionVideos
|
||||
.AsNoTracking()
|
||||
.Where(video => video.TenantId == filter.TenantId);
|
||||
|
||||
if (filter.QuestionId.HasValue)
|
||||
{
|
||||
query = query.Where(video => video.QuestionId == filter.QuestionId.Value);
|
||||
}
|
||||
|
||||
if (filter.AssetId.HasValue)
|
||||
{
|
||||
query = query.Where(video => video.VideoId == filter.AssetId.Value);
|
||||
}
|
||||
|
||||
var videoExplanations = dbContext.VideoExplanations.AsNoTracking();
|
||||
var items = await query
|
||||
.OrderBy(video => video.SortOrder)
|
||||
.ThenBy(video => video.CreatedAt)
|
||||
.Take(ResolveLimit(filter.Limit))
|
||||
.GroupJoin(
|
||||
videoExplanations,
|
||||
questionVideo => new { questionVideo.TenantId, Id = questionVideo.VideoId },
|
||||
video => new { video.TenantId, Id = (Guid?)video.Id },
|
||||
(questionVideo, videos) => new
|
||||
{
|
||||
QuestionVideo = questionVideo,
|
||||
Video = videos.FirstOrDefault()
|
||||
})
|
||||
.Select(row => new QuestionVideoCatalogItem(
|
||||
row.QuestionVideo.Id,
|
||||
row.QuestionVideo.LegacyId,
|
||||
row.QuestionVideo.QuestionId,
|
||||
row.QuestionVideo.VideoId,
|
||||
row.QuestionVideo.VideoType,
|
||||
row.QuestionVideo.SortOrder,
|
||||
row.QuestionVideo.Metadata,
|
||||
row.Video == null
|
||||
? null
|
||||
: new VideoExplanationCatalogItem(
|
||||
row.Video.Id,
|
||||
row.Video.LegacyId,
|
||||
row.Video.SubjectId,
|
||||
row.Video.Title,
|
||||
row.Video.Description,
|
||||
row.Video.VideoUrl,
|
||||
row.Video.ThumbnailUrl,
|
||||
row.Video.DurationSeconds,
|
||||
row.Video.KnowledgeTags,
|
||||
row.Video.IsGeneral,
|
||||
row.Video.Difficulty,
|
||||
row.Video.SortOrder,
|
||||
row.Video.IsActive,
|
||||
row.Video.Metadata)))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return new CatalogList<QuestionVideoCatalogItem>(items);
|
||||
}
|
||||
|
||||
private static int ResolveLimit(int? limit)
|
||||
{
|
||||
return Math.Clamp(limit ?? DefaultLimit, 1, MaxLimit);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user