Initial
This commit is contained in:
214
LehrerApp.Sync/EventApplier.cs
Normal file
214
LehrerApp.Sync/EventApplier.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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<SyncEvent> 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<Student>(operation, json,
|
||||
e => _students.Save(e),
|
||||
id => _students.Delete(id));
|
||||
break;
|
||||
|
||||
case "LearningGroup":
|
||||
ApplyEntity<LearningGroup>(operation, json,
|
||||
e => _groups.Save(e),
|
||||
id => _groups.Delete(id));
|
||||
break;
|
||||
|
||||
case "Enrollment":
|
||||
ApplyEntity<Enrollment>(operation, json,
|
||||
e => _enrollments.Save(e),
|
||||
id => _enrollments.Delete(id));
|
||||
break;
|
||||
|
||||
case "Exam":
|
||||
ApplyEntity<Exam>(operation, json,
|
||||
e => _exams.Save(e),
|
||||
id => _exams.Delete(id));
|
||||
break;
|
||||
|
||||
case "ExamResult":
|
||||
ApplyEntity<ExamResult>(operation, json,
|
||||
e => _examResults.Save(e),
|
||||
_ => { }); // ExamResults werden nicht einzeln gelöscht
|
||||
break;
|
||||
|
||||
case "Grade":
|
||||
ApplyEntity<Grade>(operation, json,
|
||||
e => _grades.Save(e),
|
||||
id => _grades.Delete(id));
|
||||
break;
|
||||
|
||||
case "Unit":
|
||||
ApplyEntity<Unit>(operation, json,
|
||||
e => _units.Save(e),
|
||||
id => _units.Delete(id));
|
||||
break;
|
||||
|
||||
case "Lesson":
|
||||
ApplyEntity<Lesson>(operation, json,
|
||||
e => _lessons.Save(e),
|
||||
id => _lessons.Delete(id));
|
||||
break;
|
||||
|
||||
case "Documentation":
|
||||
ApplyEntity<Documentation>(operation, json,
|
||||
e => _docs.Save(e),
|
||||
id => _docs.Delete(id));
|
||||
break;
|
||||
|
||||
case "WorkTask":
|
||||
ApplyEntity<WorkTask>(operation, json,
|
||||
e => _tasks.Save(e),
|
||||
id => _tasks.Delete(id));
|
||||
break;
|
||||
|
||||
case "TimeEntry":
|
||||
ApplyEntity<TimeEntry>(operation, json,
|
||||
e => _timeEntries.Save(e),
|
||||
id => _timeEntries.Delete(id));
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.Error.WriteLine(
|
||||
$"EventApplier: Unbekannter EntityType '{entityType}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyEntity<T>(
|
||||
string operation,
|
||||
string json,
|
||||
Action<T> save,
|
||||
Action<Guid> delete) where T : class
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case "Upsert":
|
||||
case "Create":
|
||||
case "Update":
|
||||
var entity = JsonSerializer.Deserialize<T>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user