121 lines
5.2 KiB
C#
121 lines
5.2 KiB
C#
using System.Text.Json;
|
||
using Server.WatchBody.Core.Dal.Ref.Repository;
|
||
using Server.WatchBody.Core.Dal.Src.Domain;
|
||
using Server.WatchBody.Core.Dal.Src.Repository;
|
||
using Server.WatchBody.Core.Programs;
|
||
|
||
namespace Server.WatchBody.Import;
|
||
|
||
/// <summary>Нарушение формата пакета program-import.json (spec 03 §2).</summary>
|
||
public class ImportPackageException(string message) : Exception(message);
|
||
|
||
/// <summary>
|
||
/// Импорт пакета программы (spec 03 m06, формат program-import-format.md):
|
||
/// program по external_id, версия через ProgramStore (повтор пакета —
|
||
/// новая версия max+1), отсутствующие упражнения создаются. 🛡
|
||
/// Существующие версии не переписываются; статус draft.
|
||
/// </summary>
|
||
public class ProgramPackageImporter(
|
||
IProgramRepository programs,
|
||
IExerciseRepository exercises,
|
||
IExerciseGroupRepository groups,
|
||
ProgramStore store)
|
||
{
|
||
public Guid Import(int athleteId, string packageJson)
|
||
{
|
||
JsonDocument doc;
|
||
try
|
||
{
|
||
doc = JsonDocument.Parse(packageJson);
|
||
}
|
||
catch (JsonException e)
|
||
{
|
||
throw new ImportPackageException($"пакет не JSON: {e.Message}");
|
||
}
|
||
|
||
using (doc)
|
||
{
|
||
var root = doc.RootElement;
|
||
if (root.ValueKind != JsonValueKind.Object ||
|
||
!root.TryGetProperty("weeks", out var weeks) ||
|
||
weeks.ValueKind != JsonValueKind.Array || weeks.GetArrayLength() == 0)
|
||
throw new ImportPackageException("пакет без weeks — это не программа");
|
||
if (!root.TryGetProperty("program_id", out var programIdEl) ||
|
||
programIdEl.GetString() is not { Length: > 0 } externalId)
|
||
throw new ImportPackageException("пакет без program_id");
|
||
|
||
var author = root.TryGetProperty("author", out var authorEl)
|
||
? authorEl.GetString() : null;
|
||
if (author is not ("user" or "admin"))
|
||
throw new ImportPackageException($"author '{author}' не допускается (user|admin)");
|
||
|
||
// программа по external_id (З-35) или создание
|
||
var program = programs.Select(athleteId).FirstOrDefault(p => p.ExternalId == externalId)
|
||
?? CreateProgram(athleteId, externalId);
|
||
|
||
// отсутствующие упражнения библиотеки (format разд. 2)
|
||
var groupByFocus = GroupByFocus();
|
||
EnsureExercises(athleteId, weeks, groupByFocus);
|
||
|
||
var rationale = root.TryGetProperty("rationale", out var rationaleEl) &&
|
||
rationaleEl.ValueKind == JsonValueKind.String
|
||
? rationaleEl.GetString() ?? string.Empty
|
||
: string.Empty;
|
||
var version = store.CreateDraft(athleteId, root.GetRawText(), author, rationale);
|
||
return version.Id;
|
||
}
|
||
}
|
||
|
||
private Program CreateProgram(int athleteId, string externalId)
|
||
{
|
||
var id = programs.Insert(new Program
|
||
{
|
||
AthleteId = athleteId,
|
||
Title = externalId,
|
||
ExternalId = externalId,
|
||
});
|
||
return new Program { Id = id, AthleteId = athleteId, Title = externalId, ExternalId = externalId };
|
||
}
|
||
|
||
private Dictionary<string, int> GroupByFocus() =>
|
||
groups.Select().ToDictionary(g => g.Code, g => g.Id, StringComparer.Ordinal);
|
||
|
||
private void EnsureExercises(int athleteId, JsonElement weeks, Dictionary<string, int> groupIds)
|
||
{
|
||
var known = exercises.Select(new ExerciseFilter { AthleteId = athleteId })
|
||
.Select(e => e.Name).ToHashSet(StringComparer.Ordinal);
|
||
foreach (var session in weeks.EnumerateArray()
|
||
.SelectMany(w => w.TryGetProperty("sessions", out var s)
|
||
? s.EnumerateArray() : Enumerable.Empty<JsonElement>()))
|
||
{
|
||
var focus = session.TryGetProperty("focus", out var f) ? f.GetString() ?? "" : "";
|
||
var groupId = FocusGroup(focus, groupIds);
|
||
if (!session.TryGetProperty("exercises", out var exerciseNodes)) continue;
|
||
foreach (var exercise in exerciseNodes.EnumerateArray())
|
||
{
|
||
if (!exercise.TryGetProperty("exercise_id", out var idEl) ||
|
||
idEl.GetString() is not { Length: > 0 } name || known.Contains(name)) continue;
|
||
exercises.Insert(new Exercise
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
AthleteId = athleteId,
|
||
Name = name,
|
||
GroupId = groupId,
|
||
IsBase = true,
|
||
IsActive = true,
|
||
ReplacementIds = [],
|
||
});
|
||
known.Add(name);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static int FocusGroup(string focus, Dictionary<string, int> groupIds)
|
||
{
|
||
foreach (var (code, id) in groupIds)
|
||
if (focus.Contains(code, StringComparison.OrdinalIgnoreCase))
|
||
return id;
|
||
return groupIds.GetValueOrDefault("core", 4);
|
||
}
|
||
}
|