namespace LehrerApp.Api; /// /// Rohdaten-Ablage für verschlüsselte Datei-Anhänge, getrennt vom Ereignis-/Snapshot-Speicher. /// Server sieht nur verschlüsselte Bytes, kein LiteDB nötig - einfache Dateien pro Nutzer/Id. /// public class AttachmentStore(string dataPath) { private readonly string _root = Path.Combine(dataPath, "attachments"); public async Task StoreAsync(string userId, string storageId, Stream content) { var dir = Path.Combine(_root, Safe(userId)); Directory.CreateDirectory(dir); await using var file = File.Create(Path.Combine(dir, Safe(storageId))); await content.CopyToAsync(file); } public Stream? OpenRead(string userId, string storageId) { var path = Path.Combine(_root, Safe(userId), Safe(storageId)); return File.Exists(path) ? File.OpenRead(path) : null; } // storageId kommt als Routen-Parameter vom Client - nie ungeprüft in einen Dateipfad // übernehmen (Path-Traversal). private static string Safe(string value) => string.Concat(value.Where(c => char.IsLetterOrDigit(c) || c == '-')); }