154 lines
6.9 KiB
C#
154 lines
6.9 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.Catalog;
|
|
using Tiku.Application.Content;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Infrastructure.Security;
|
|
|
|
namespace Tiku.Infrastructure.Content;
|
|
|
|
public sealed partial class ContentManagementService
|
|
{
|
|
public async Task<CatalogList<PracticeBlueprintManagementItem>> GetPracticeBlueprintsAsync(
|
|
ContentManagementActor actor,
|
|
ContentManagementFilter filter,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
var regionIds = scope.RegionIds.ToArray();
|
|
var query = dbContext.PracticeBlueprints
|
|
.AsNoTracking()
|
|
.Where(blueprint => blueprint.TenantId == actor.TenantId)
|
|
.ApplyDataScope(
|
|
scope,
|
|
blueprint => blueprint.CreatedBy == actor.UserId,
|
|
blueprint => blueprint.RegionId.HasValue && regionIds.Contains(blueprint.RegionId.Value));
|
|
|
|
if (!filter.IncludeInactive) query = query.Where(blueprint => blueprint.Status == ContentStatus.Active);
|
|
|
|
if (filter.RegionId.HasValue) query = query.Where(blueprint => blueprint.RegionId == filter.RegionId.Value);
|
|
|
|
if (filter.EntryId.HasValue) query = query.Where(blueprint => blueprint.EntryId == filter.EntryId.Value);
|
|
|
|
if (filter.NodeId.HasValue) query = query.Where(blueprint => blueprint.NodeId == filter.NodeId.Value);
|
|
|
|
if (filter.CollectionId.HasValue)
|
|
query = query.Where(blueprint => blueprint.CollectionId == filter.CollectionId.Value);
|
|
|
|
if (TryParse(filter.Mode, out PracticeMode mode)) query = query.Where(blueprint => blueprint.Mode == mode);
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
|
{
|
|
var keyword = filter.Keyword.Trim();
|
|
query = query.Where(blueprint => blueprint.Name.Contains(keyword));
|
|
}
|
|
|
|
var items = await query
|
|
.OrderBy(blueprint => blueprint.SortOrder)
|
|
.ThenBy(blueprint => blueprint.CreatedAt)
|
|
.Take(ResolveLimit(filter.Limit))
|
|
.Select(blueprint => ToBlueprintItem(blueprint))
|
|
.ToArrayAsync(cancellationToken);
|
|
|
|
return new CatalogList<PracticeBlueprintManagementItem>(items);
|
|
}
|
|
|
|
public async Task<ContentManagementResult<PracticeBlueprintManagementItem>> UpsertPracticeBlueprintAsync(
|
|
ContentManagementActor actor,
|
|
UpsertPracticeBlueprintCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
|
|
await AssertRegionAsync(actor.TenantId, command.RegionId, cancellationToken);
|
|
await AssertEntryAsync(actor, scope, command.EntryId, cancellationToken);
|
|
await AssertNodeAsync(actor, scope, command.NodeId, cancellationToken);
|
|
await AssertReferenceAsync<QuestionCollection>(actor.TenantId, command.CollectionId, "collection_not_found",
|
|
cancellationToken);
|
|
|
|
var blueprint = await ResolveEntityByIdOrLegacyAsync(
|
|
dbContext.PracticeBlueprints,
|
|
actor.TenantId,
|
|
command.Id,
|
|
command.LegacyId,
|
|
cancellationToken);
|
|
|
|
var isNew = blueprint is null;
|
|
if (command.Id.HasValue && (blueprint is null || blueprint.Id != command.Id.Value))
|
|
throw new ContentManagementException("Practice blueprint was not found.", "practice_blueprint_not_found");
|
|
|
|
if (blueprint is not null && !scope.AllowsResource(actor.UserId, blueprint.CreatedBy, blueprint.RegionId))
|
|
throw new ContentManagementException("Practice blueprint was not found.", "practice_blueprint_not_found");
|
|
|
|
if (blueprint is null && !scope.AllowsResource(actor.UserId, actor.UserId, command.RegionId))
|
|
throw new ContentManagementException("Practice blueprint was not found.", "practice_blueprint_not_found");
|
|
|
|
blueprint ??= new PracticeBlueprint
|
|
{
|
|
Id = command.Id ?? Guid.NewGuid(),
|
|
TenantId = actor.TenantId,
|
|
CreatedBy = actor.UserId
|
|
};
|
|
|
|
blueprint.RegionId = command.RegionId;
|
|
blueprint.EntryId = command.EntryId;
|
|
blueprint.NodeId = command.NodeId;
|
|
blueprint.CollectionId = command.CollectionId;
|
|
blueprint.LegacyId = Normalize(command.LegacyId);
|
|
blueprint.Name = command.Name.Trim();
|
|
blueprint.Mode = Parse(command.Mode, PracticeMode.Sequential, "practice_mode_invalid");
|
|
blueprint.AssemblyType = Parse(command.AssemblyType, PracticeAssemblyType.Collection,
|
|
"practice_assembly_type_invalid");
|
|
blueprint.QuestionLimit = command.QuestionLimit;
|
|
blueprint.DurationMinutes = command.DurationMinutes;
|
|
blueprint.TotalScore = command.TotalScore;
|
|
blueprint.PassScore = command.PassScore;
|
|
blueprint.Sections = JsonArrayOrDefault(command.Sections);
|
|
blueprint.Rules = JsonObjectOrDefault(command.Rules);
|
|
blueprint.AccessRules = JsonObjectOrDefault(command.AccessRules);
|
|
blueprint.Status = Parse(command.Status, ContentStatus.Active, "content_status_invalid");
|
|
blueprint.SortOrder = command.Order ?? 0;
|
|
blueprint.UpdatedBy = actor.UserId;
|
|
|
|
if (isNew) dbContext.PracticeBlueprints.Add(blueprint);
|
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
return new ContentManagementResult<PracticeBlueprintManagementItem>(ToBlueprintItem(blueprint));
|
|
}
|
|
|
|
public ImportFieldMappingItem GetImportFieldMapping(string importType)
|
|
{
|
|
var spec = ResolveImportSpec(importType);
|
|
return new ImportFieldMappingItem(
|
|
spec.ImportType,
|
|
spec.Title,
|
|
spec.Description,
|
|
spec.Fields,
|
|
spec.Fields.Where(field => field.Required).Select(field => field.Field).ToArray());
|
|
}
|
|
|
|
public ImportTemplateItem GetImportTemplate(string importType, string? format)
|
|
{
|
|
var spec = ResolveImportSpec(importType);
|
|
var normalizedFormat = string.IsNullOrWhiteSpace(format) ? "json" : format.Trim().ToLowerInvariant();
|
|
var content = normalizedFormat switch
|
|
{
|
|
"json" => JsonSerializer.Serialize(spec.JsonExample, new JsonSerializerOptions { WriteIndented = true }),
|
|
"csv" => string.Join(
|
|
"\n",
|
|
spec.CsvRows.Select(row => string.Join(",", row.Select(EscapeCsv)))),
|
|
_ => throw new ContentManagementException("Import template format is not supported.",
|
|
"import_template_format_invalid")
|
|
};
|
|
|
|
return new ImportTemplateItem(
|
|
spec.ImportType,
|
|
normalizedFormat,
|
|
$"{spec.ImportType}-import-template.{normalizedFormat}",
|
|
normalizedFormat == "csv" ? "text/csv" : "application/json",
|
|
Convert.ToBase64String(Encoding.UTF8.GetBytes(content)),
|
|
content,
|
|
spec.Fields);
|
|
}
|
|
} |