using System.Text; using System.Text.Json; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Sync.Crypto; using LehrerApp.Sync.Models; namespace LehrerApp.Sync; /// /// Wendet vom Server gezogene Events auf die lokale LiteDB an. /// /// Unterscheidet zwischen: /// - Verschlüsselten Desktop-Events → erst entschlüsseln, dann anwenden /// - Klartext-Events von WebApp/Companion → direkt anwenden /// public class EventApplier { private readonly byte[] _key; // Repositories für alle Entity-Typen private readonly IStudentRepository _students; private readonly IGroupRepository _groups; private readonly IEnrollmentRepository _enrollments; private readonly IExamRepository _exams; private readonly IExamResultRepository _examResults; private readonly IGradeRepository _grades; private readonly IUnitRepository _units; private readonly ILessonRepository _lessons; private readonly IDocumentationRepository _docs; private readonly IWorkTaskRepository _tasks; private readonly ITimeEntryRepository _timeEntries; public EventApplier( byte[] key, IStudentRepository students, IGroupRepository groups, IEnrollmentRepository enrollments, IExamRepository exams, IExamResultRepository examResults, IGradeRepository grades, IUnitRepository units, ILessonRepository lessons, IDocumentationRepository docs, IWorkTaskRepository tasks, ITimeEntryRepository timeEntries) { _key = key; _students = students; _groups = groups; _enrollments = enrollments; _exams = exams; _examResults = examResults; _grades = grades; _units = units; _lessons = lessons; _docs = docs; _tasks = tasks; _timeEntries = timeEntries; } // ── Hauptmethode ────────────────────────────────────────────────────────── public void Apply(SyncEvent evt) { var json = ResolvePayload(evt); if (string.IsNullOrWhiteSpace(json)) return; try { ApplyJson(evt.EntityType, evt.Operation, json); } catch (Exception ex) { // Einzelne fehlerhafte Events nicht die ganze Sync-Session abbrechen Console.Error.WriteLine( $"EventApplier: Fehler bei {evt.EntityType}/{evt.Operation} " + $"({evt.EventId}): {ex.Message}"); } } public void ApplyAll(IEnumerable events) { // Chronologisch anwenden foreach (var evt in events.OrderBy(e => e.Timestamp)) Apply(evt); } // ── Payload auflösen ────────────────────────────────────────────────────── private string ResolvePayload(SyncEvent evt) { // Klartext-Events von WebApp/Companion if (evt.DeviceType == DeviceType.Companion) return evt.Payload; // Verschlüsselte Events vom Desktop entschlüsseln try { var encrypted = Convert.FromBase64String(evt.Payload); var decrypted = SyncCrypto.Decrypt(encrypted, _key); return Encoding.UTF8.GetString(decrypted); } catch (Exception ex) { Console.Error.WriteLine( $"EventApplier: Entschlüsselung fehlgeschlagen " + $"({evt.EventId}): {ex.Message}"); return ""; } } // ── Entity-spezifische Anwendung ────────────────────────────────────────── private void ApplyJson(string entityType, string operation, string json) { switch (entityType) { case "Student": ApplyEntity(operation, json, e => _students.Save(e), id => _students.Delete(id)); break; case "LearningGroup": ApplyEntity(operation, json, e => _groups.Save(e), id => _groups.Delete(id)); break; case "Enrollment": ApplyEntity(operation, json, e => _enrollments.Save(e), id => _enrollments.Delete(id)); break; case "Exam": ApplyEntity(operation, json, e => _exams.Save(e), id => _exams.Delete(id)); break; case "ExamResult": ApplyEntity(operation, json, e => _examResults.Save(e), _ => { }); // ExamResults werden nicht einzeln gelöscht break; case "Grade": ApplyEntity(operation, json, e => _grades.Save(e), id => _grades.Delete(id)); break; case "Unit": ApplyEntity(operation, json, e => _units.Save(e), id => _units.Delete(id)); break; case "Lesson": ApplyEntity(operation, json, e => _lessons.Save(e), id => _lessons.Delete(id)); break; case "Documentation": ApplyEntity(operation, json, e => _docs.Save(e), id => _docs.Delete(id)); break; case "WorkTask": ApplyEntity(operation, json, e => _tasks.Save(e), id => _tasks.Delete(id)); break; case "TimeEntry": ApplyEntity(operation, json, e => _timeEntries.Save(e), id => _timeEntries.Delete(id)); break; default: Console.Error.WriteLine( $"EventApplier: Unbekannter EntityType '{entityType}'"); break; } } private static void ApplyEntity( string operation, string json, Action save, Action delete) where T : class { switch (operation) { case "Upsert": case "Create": case "Update": var entity = JsonSerializer.Deserialize(json); if (entity is not null) save(entity); break; case "Delete": // Payload ist bei Delete nur die ID if (Guid.TryParse(json.Trim('"'), out var id)) delete(id); break; } } }