226 lines
11 KiB
C#
226 lines
11 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.Catalog;
|
|
using Tiku.Application.Content;
|
|
using Tiku.Domain.Catalog;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Infrastructure.Security;
|
|
|
|
namespace Tiku.Infrastructure.Content;
|
|
|
|
internal sealed class HandbookManagementService(DirectContentServiceDependencies dependencies)
|
|
: DirectContentServiceBase(dependencies), IHandbookManagementService
|
|
{
|
|
public async Task<CatalogList<HandbookSubject>> GetHandbookSubjectsAsync(
|
|
DirectContentActor actor,
|
|
AdminLimitFilter filter,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
var regionIds = scope.RegionIds.ToArray();
|
|
var query = contentAssetPersistence.HandbookSubjects.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId)
|
|
.ApplyDataScope(scope, null, item => item.RegionId.HasValue && regionIds.Contains(item.RegionId.Value));
|
|
if (filter.RegionId.HasValue) query = query.Where(item => item.RegionId == filter.RegionId.Value);
|
|
|
|
if (filter.EntryId.HasValue) query = query.Where(item => item.EntryId == filter.EntryId.Value);
|
|
|
|
if (filter.ContentNodeId.HasValue)
|
|
query = query.Where(item => item.ContentNodeId == filter.ContentNodeId.Value);
|
|
|
|
if (filter.SchoolId.HasValue) query = query.Where(item => item.SchoolId == filter.SchoolId.Value);
|
|
|
|
if (filter.MajorId.HasValue) query = query.Where(item => item.MajorId == filter.MajorId.Value);
|
|
|
|
if (!string.Equals(filter.Status, "all", StringComparison.OrdinalIgnoreCase))
|
|
query = query.Where(item => item.IsActive);
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
|
{
|
|
var keyword = filter.Keyword.Trim();
|
|
query = query.Where(item =>
|
|
item.Name.Contains(keyword) || (item.Description != null && item.Description.Contains(keyword)));
|
|
}
|
|
|
|
return new CatalogList<HandbookSubject>(await query
|
|
.OrderBy(item => item.SortOrder)
|
|
.ThenBy(item => item.Name)
|
|
.Take(ResolveLimit(filter.Limit))
|
|
.ToArrayAsync(cancellationToken));
|
|
}
|
|
|
|
public async Task<ContentManagementResult<HandbookSubject>> UpsertHandbookSubjectAsync(
|
|
DirectContentActor actor,
|
|
HandbookSubjectCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
|
|
await AssertReferenceAsync<Region>(actor.TenantId, command.RegionId, "region_not_found", cancellationToken);
|
|
await AssertReferenceAsync<School>(actor.TenantId, command.SchoolId, "school_not_found", cancellationToken);
|
|
await AssertReferenceAsync<Major>(actor.TenantId, command.MajorId, "major_not_found", cancellationToken);
|
|
await AssertReferenceAsync<ContentEntry>(actor.TenantId, command.EntryId, "entry_not_found", cancellationToken);
|
|
await AssertReferenceAsync<ContentNode>(actor.TenantId, command.ContentNodeId, "node_not_found",
|
|
cancellationToken);
|
|
|
|
var item = await ResolveByIdOrLegacyAsync(contentAssetPersistence.HandbookSubjects, actor.TenantId, command.Id,
|
|
command.LegacyId, cancellationToken);
|
|
var isNew = item is null;
|
|
EnsureRegionWriteAllowed(scope, actor, item?.RegionId, command.RegionId, isNew, "handbook_subject_not_found");
|
|
item ??= new HandbookSubject { Id = command.Id ?? Guid.NewGuid(), TenantId = actor.TenantId };
|
|
item.RegionId = command.RegionId;
|
|
item.SchoolId = command.SchoolId;
|
|
item.MajorId = command.MajorId;
|
|
item.EntryId = command.EntryId;
|
|
item.ContentNodeId = command.ContentNodeId;
|
|
item.LegacyId = Normalize(command.LegacyId);
|
|
item.Name = command.Name.Trim();
|
|
item.Type = ParseNullable<HandbookSubjectType>(command.Type, "handbook_subject_type_invalid");
|
|
item.Icon = Normalize(command.Icon);
|
|
item.Color = Normalize(command.Color);
|
|
item.Description = Normalize(command.Description);
|
|
item.SortOrder = command.Order ?? item.SortOrder;
|
|
item.IsActive = command.IsActive ?? item.IsActive;
|
|
item.Metadata = JsonObjectOrDefault(command.Metadata);
|
|
if (isNew) contentAssetPersistence.HandbookSubjects.Add(item);
|
|
|
|
await unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return new ContentManagementResult<HandbookSubject>(item);
|
|
}
|
|
|
|
public async Task<CatalogList<HandbookChapter>> GetHandbookChaptersAsync(
|
|
DirectContentActor actor,
|
|
AdminLimitFilter filter,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var query = contentAssetPersistence.HandbookChapters.AsNoTracking().Where(item => item.TenantId == actor.TenantId);
|
|
if (filter.SubjectId.HasValue) query = query.Where(item => item.SubjectId == filter.SubjectId.Value);
|
|
|
|
if (filter.EntryId.HasValue) query = query.Where(item => item.EntryId == filter.EntryId.Value);
|
|
|
|
if (filter.ContentNodeId.HasValue)
|
|
query = query.Where(item => item.ContentNodeId == filter.ContentNodeId.Value);
|
|
|
|
if (!string.Equals(filter.Status, "all", StringComparison.OrdinalIgnoreCase))
|
|
query = query.Where(item => item.IsActive);
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
|
{
|
|
var keyword = filter.Keyword.Trim();
|
|
query = query.Where(item =>
|
|
item.Name.Contains(keyword) || (item.Description != null && item.Description.Contains(keyword)));
|
|
}
|
|
|
|
return new CatalogList<HandbookChapter>(await query
|
|
.OrderBy(item => item.SortOrder)
|
|
.ThenBy(item => item.Name)
|
|
.Take(ResolveLimit(filter.Limit))
|
|
.ToArrayAsync(cancellationToken));
|
|
}
|
|
|
|
public async Task<ContentManagementResult<HandbookChapter>> UpsertHandbookChapterAsync(
|
|
DirectContentActor actor,
|
|
HandbookChapterCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
|
|
await AssertReferenceAsync<HandbookSubject>(actor.TenantId, command.SubjectId, "handbook_subject_not_found",
|
|
cancellationToken);
|
|
await AssertReferenceAsync<ContentEntry>(actor.TenantId, command.EntryId, "entry_not_found", cancellationToken);
|
|
await AssertReferenceAsync<ContentNode>(actor.TenantId, command.ContentNodeId, "node_not_found",
|
|
cancellationToken);
|
|
|
|
var item = await ResolveByIdOrLegacyAsync(contentAssetPersistence.HandbookChapters, actor.TenantId, command.Id,
|
|
command.LegacyId, cancellationToken);
|
|
var isNew = item is null;
|
|
item ??= new HandbookChapter { Id = command.Id ?? Guid.NewGuid(), TenantId = actor.TenantId };
|
|
var chapterNavigation = await ResolveHandbookSubjectNavigationAsync(
|
|
actor.TenantId,
|
|
command.SubjectId,
|
|
command.EntryId,
|
|
command.ContentNodeId,
|
|
cancellationToken);
|
|
item.SubjectId = command.SubjectId;
|
|
item.EntryId = chapterNavigation.EntryId;
|
|
item.ContentNodeId = chapterNavigation.ContentNodeId;
|
|
item.LegacyId = Normalize(command.LegacyId);
|
|
item.Name = command.Name.Trim();
|
|
item.Description = Normalize(command.Description);
|
|
item.SortOrder = command.Order ?? item.SortOrder;
|
|
item.IsActive = command.IsActive ?? item.IsActive;
|
|
item.Metadata = JsonObjectOrDefault(command.Metadata);
|
|
if (isNew) contentAssetPersistence.HandbookChapters.Add(item);
|
|
|
|
await unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return new ContentManagementResult<HandbookChapter>(item);
|
|
}
|
|
|
|
public async Task<CatalogList<HandbookEntry>> GetHandbookEntriesAsync(
|
|
DirectContentActor actor,
|
|
AdminLimitFilter filter,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var query = contentAssetPersistence.HandbookEntries.AsNoTracking().Where(item => item.TenantId == actor.TenantId);
|
|
if (filter.ChapterId.HasValue) query = query.Where(item => item.ChapterId == filter.ChapterId.Value);
|
|
|
|
if (filter.EntryId.HasValue) query = query.Where(item => item.EntryId == filter.EntryId.Value);
|
|
|
|
if (filter.ContentNodeId.HasValue)
|
|
query = query.Where(item => item.ContentNodeId == filter.ContentNodeId.Value);
|
|
|
|
if (!string.Equals(filter.Status, "all", StringComparison.OrdinalIgnoreCase))
|
|
query = query.Where(item => item.IsActive);
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
|
{
|
|
var keyword = filter.Keyword.Trim();
|
|
query = query.Where(item =>
|
|
item.Title.Contains(keyword) || (item.Content != null && item.Content.Contains(keyword)));
|
|
}
|
|
|
|
return new CatalogList<HandbookEntry>(await query
|
|
.OrderBy(item => item.SortOrder)
|
|
.ThenBy(item => item.Title)
|
|
.Take(ResolveLimit(filter.Limit))
|
|
.ToArrayAsync(cancellationToken));
|
|
}
|
|
|
|
public async Task<ContentManagementResult<HandbookEntry>> UpsertHandbookEntryAsync(
|
|
DirectContentActor actor,
|
|
HandbookEntryCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(command.Title);
|
|
await AssertReferenceAsync<HandbookChapter>(actor.TenantId, command.ChapterId, "handbook_chapter_not_found",
|
|
cancellationToken);
|
|
await AssertReferenceAsync<ContentEntry>(actor.TenantId, command.EntryId, "entry_not_found", cancellationToken);
|
|
await AssertReferenceAsync<ContentNode>(actor.TenantId, command.ContentNodeId, "node_not_found",
|
|
cancellationToken);
|
|
|
|
var item = await ResolveByIdOrLegacyAsync(contentAssetPersistence.HandbookEntries, actor.TenantId, command.Id,
|
|
command.LegacyId, cancellationToken);
|
|
var isNew = item is null;
|
|
item ??= new HandbookEntry { Id = command.Id ?? Guid.NewGuid(), TenantId = actor.TenantId };
|
|
var entryNavigation = await ResolveHandbookChapterNavigationAsync(
|
|
actor.TenantId,
|
|
command.ChapterId,
|
|
command.EntryId,
|
|
command.ContentNodeId,
|
|
cancellationToken);
|
|
item.ChapterId = command.ChapterId;
|
|
item.EntryId = entryNavigation.EntryId;
|
|
item.ContentNodeId = entryNavigation.ContentNodeId;
|
|
item.LegacyId = Normalize(command.LegacyId);
|
|
item.Title = command.Title.Trim();
|
|
item.Summary = Normalize(command.Summary);
|
|
item.Content = Normalize(command.Content);
|
|
item.Tags = JsonArrayOrDefault(command.Tags);
|
|
item.SortOrder = command.Order ?? item.SortOrder;
|
|
item.IsActive = command.IsActive ?? item.IsActive;
|
|
item.Metadata = JsonObjectOrDefault(command.Metadata);
|
|
if (isNew) contentAssetPersistence.HandbookEntries.Add(item);
|
|
|
|
await unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return new ContentManagementResult<HandbookEntry>(item);
|
|
}
|
|
}
|