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; /// Нарушение формата пакета program-import.json (spec 03 §2). public class ImportPackageException(string message) : Exception(message); /// /// Импорт пакета программы (spec 03 m06, формат program-import-format.md): /// program по external_id, версия через ProgramStore (повтор пакета — /// новая версия max+1), отсутствующие упражнения создаются. 🛡 /// Существующие версии не переписываются; статус draft. /// 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 GroupByFocus() => groups.Select().ToDictionary(g => g.Code, g => g.Id, StringComparer.Ordinal); private void EnsureExercises(int athleteId, JsonElement weeks, Dictionary 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())) { 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 groupIds) { foreach (var (code, id) in groupIds) if (focus.Contains(code, StringComparison.OrdinalIgnoreCase)) return id; return groupIds.GetValueOrDefault("core", 4); } }