Files
LehrerApp/LehrerApp.Desktop/AppBootstrapper.cs
2026-03-29 23:47:31 +02:00

182 lines
8.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Services;
using LehrerApp.Data;
using LehrerApp.Data.Repositories;
using LehrerApp.Desktop.ViewModels;
using LehrerApp.Desktop.ViewModels.Groups;
using LehrerApp.Desktop.ViewModels.Students;
using LehrerApp.Sync;
using LehrerApp.Sync.Crypto;
using LehrerApp.Sync.Models;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop;
public static class AppBootstrapper
{
public static IServiceProvider BuildServices()
{
var services = new ServiceCollection();
// ── Pfade ─────────────────────────────────────────────────────────────
var appData = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"LehrerApp");
Directory.CreateDirectory(appData);
var dbPath = Path.Combine(appData, "lehrerapp.db");
var queuePath = Path.Combine(appData, "syncqueue.db");
var keyPath = Path.Combine(appData, "sync.key");
// ── Datenbank ─────────────────────────────────────────────────────────
services.AddSingleton(_ => new LiteDbContext(dbPath));
// ── Repositories ──────────────────────────────────────────────────────
services.AddSingleton<IStudentRepository, StudentRepository>();
services.AddSingleton<IGroupRepository, GroupRepository>();
services.AddSingleton<IEnrollmentRepository, EnrollmentRepository>();
services.AddSingleton<IExamRepository, ExamRepository>();
services.AddSingleton<IExamResultRepository, ExamResultRepository>();
services.AddSingleton<IGradeRepository, GradeRepository>();
services.AddSingleton<IUnitRepository, UnitRepository>();
services.AddSingleton<ILessonRepository, LessonRepository>();
services.AddSingleton<IDocumentationRepository, DocumentationRepository>();
services.AddSingleton<IWorkTaskRepository, WorkTaskRepository>();
services.AddSingleton<ITimeEntryRepository, TimeEntryRepository>();
// ── Core Services ─────────────────────────────────────────────────────
services.AddSingleton<GradingService>();
services.AddSingleton<SchoolYearService>();
// ── Sync ──────────────────────────────────────────────────────────────
services.AddSingleton(_ => new EventQueue(queuePath));
services.AddSingleton(sp =>
new ConflictResolver(sp.GetRequiredService<EventQueue>()));
// Schlüssel laden oder neu generieren
services.AddSingleton<byte[]>(_ =>
{
var key = SyncCrypto.LoadKey(keyPath) ?? SyncCrypto.GenerateKey();
SyncCrypto.SaveKey(key, keyPath);
return key;
});
// EventApplier wendet gezogene Events auf LiteDB an
services.AddSingleton<EventApplier>(sp => new EventApplier(
key: sp.GetRequiredService<byte[]>(),
students: sp.GetRequiredService<IStudentRepository>(),
groups: sp.GetRequiredService<IGroupRepository>(),
enrollments: sp.GetRequiredService<IEnrollmentRepository>(),
exams: sp.GetRequiredService<IExamRepository>(),
examResults: sp.GetRequiredService<IExamResultRepository>(),
grades: sp.GetRequiredService<IGradeRepository>(),
units: sp.GetRequiredService<IUnitRepository>(),
lessons: sp.GetRequiredService<ILessonRepository>(),
docs: sp.GetRequiredService<IDocumentationRepository>(),
tasks: sp.GetRequiredService<IWorkTaskRepository>(),
timeEntries: sp.GetRequiredService<ITimeEntryRepository>()));
// SyncEngine + ReadableSnapshotService nur wenn Server konfiguriert
var serverUrl = LoadServerUrl(appData);
var deviceId = LoadOrCreateDeviceId(appData);
services.AddSingleton<SyncEngine?>(sp =>
{
if (string.IsNullOrEmpty(serverUrl)) return null;
var http = BuildHttpClient(serverUrl, appData);
var config = new SyncConfig
{
ServerUrl = serverUrl,
DeviceId = deviceId,
DeviceType = DeviceType.Desktop,
AutoSyncIntervalMinutes = 5,
};
return new SyncEngine(
sp.GetRequiredService<EventQueue>(),
sp.GetRequiredService<ConflictResolver>(),
http,
config);
});
services.AddSingleton<ReadableSnapshotService?>(sp =>
{
if (string.IsNullOrEmpty(serverUrl)) return null;
return new ReadableSnapshotService(
http: BuildHttpClient(serverUrl, appData),
students: sp.GetRequiredService<IStudentRepository>(),
groups: sp.GetRequiredService<IGroupRepository>(),
enrollments: sp.GetRequiredService<IEnrollmentRepository>(),
exams: sp.GetRequiredService<IExamRepository>(),
examResults: sp.GetRequiredService<IExamResultRepository>(),
grades: sp.GetRequiredService<IGradeRepository>(),
units: sp.GetRequiredService<IUnitRepository>(),
lessons: sp.GetRequiredService<ILessonRepository>(),
tasks: sp.GetRequiredService<IWorkTaskRepository>(),
schoolYear: sp.GetRequiredService<SchoolYearService>(),
deviceId: deviceId);
});
services.AddSingleton<SnapshotService?>(sp =>
{
if (string.IsNullOrEmpty(serverUrl)) return null;
return new SnapshotService(
http: BuildHttpClient(serverUrl, appData),
db: sp.GetRequiredService<LiteDbContext>(),
syncKey: sp.GetRequiredService<byte[]>(),
deviceType: DeviceType.Desktop,
dbPath: dbPath,
keyPath: keyPath);
});
// ── ViewModels ────────────────────────────────────────────────────────
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<DashboardViewModel>();
services.AddTransient<GroupListViewModel>();
services.AddTransient<GroupDetailViewModel>();
services.AddTransient<StudentListViewModel>();
services.AddTransient<StudentDetailViewModel>();
services.AddSingleton<SyncStatusViewModel>();
services.AddTransient<DevicePairingViewModel>(sp =>
new DevicePairingViewModel(
sp.GetRequiredService<SnapshotService?>()
?? throw new InvalidOperationException(
"Kein Server konfiguriert.")));
return services.BuildServiceProvider();
}
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
private static HttpClient BuildHttpClient(string serverUrl, string appData)
{
var http = new HttpClient { BaseAddress = new Uri(serverUrl) };
var tokenPath = Path.Combine(appData, "auth.token");
if (File.Exists(tokenPath))
{
var token = File.ReadAllText(tokenPath).Trim();
http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", token);
}
return http;
}
private static string LoadServerUrl(string appData)
{
var path = Path.Combine(appData, "server.txt");
return File.Exists(path) ? File.ReadAllText(path).Trim() : "";
}
private static string LoadOrCreateDeviceId(string appData)
{
var path = Path.Combine(appData, "device.id");
if (File.Exists(path)) return File.ReadAllText(path).Trim();
var id = Guid.NewGuid().ToString();
File.WriteAllText(path, id);
return id;
}
}