using EngineeringSync.Domain.Constants; using EngineeringSync.Domain.Entities; using EngineeringSync.Infrastructure; using EngineeringSync.Service.Hubs; using EngineeringSync.Service.Models; using EngineeringSync.Service.Services; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; namespace EngineeringSync.Service.Api; public static class ProjectsApi { public static IEndpointRouteBuilder MapProjectsApi(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/projects"); group.MapGet("/", async (IDbContextFactory dbFactory) => { await using var db = await dbFactory.CreateDbContextAsync(); var projects = await db.Projects.OrderBy(p => p.Name).ToListAsync(); return Results.Ok(projects); }); group.MapGet("/{id:guid}", async (Guid id, IDbContextFactory dbFactory) => { await using var db = await dbFactory.CreateDbContextAsync(); var project = await db.Projects.FindAsync(id); return project is null ? Results.NotFound() : Results.Ok(project); }); group.MapPost("/", async (CreateProjectRequest req, IDbContextFactory dbFactory, WatcherService watcher, IHubContext hubContext, ILoggerFactory loggerFactory) => { var logger = loggerFactory.CreateLogger("ProjectsApi"); if (!Directory.Exists(req.EngineeringPath)) return Results.BadRequest($"Engineering-Pfad existiert nicht: {req.EngineeringPath}"); if (!Directory.Exists(req.SimulationPath)) return Results.BadRequest($"Simulations-Pfad existiert nicht: {req.SimulationPath}"); await using var db = await dbFactory.CreateDbContextAsync(); var project = new ProjectConfig { Name = req.Name, EngineeringPath = req.EngineeringPath, SimulationPath = req.SimulationPath, FileExtensions = req.FileExtensions, IsActive = req.IsActive, BackupEnabled = req.BackupEnabled, BackupPath = req.BackupPath, MaxBackupsPerFile = req.MaxBackupsPerFile }; db.Projects.Add(project); await db.SaveChangesAsync(); await watcher.StartWatchingAsync(project); _ = Task.Run(async () => { try { await watcher.ScanExistingFilesAsync(project); } catch (Exception ex) { logger.LogError(ex, "Fehler beim initialen Scan für Projekt {Id}", project.Id); } }); await hubContext.Clients.All.SendAsync(HubMethodNames.ProjectConfigChanged, CancellationToken.None); return Results.Created($"/api/projects/{project.Id}", project); }); group.MapPut("/{id:guid}", async (Guid id, UpdateProjectRequest req, IDbContextFactory dbFactory, WatcherService watcher, IHubContext hubContext, ILoggerFactory loggerFactory) => { var logger = loggerFactory.CreateLogger("ProjectsApi"); await using var db = await dbFactory.CreateDbContextAsync(); var project = await db.Projects.FindAsync(id); if (project is null) return Results.NotFound(); if (!Directory.Exists(req.EngineeringPath)) return Results.BadRequest($"Engineering-Pfad existiert nicht: {req.EngineeringPath}"); if (!Directory.Exists(req.SimulationPath)) return Results.BadRequest($"Simulations-Pfad existiert nicht: {req.SimulationPath}"); project.Name = req.Name; project.EngineeringPath = req.EngineeringPath; project.SimulationPath = req.SimulationPath; project.FileExtensions = req.FileExtensions; project.IsActive = req.IsActive; project.BackupEnabled = req.BackupEnabled; project.BackupPath = req.BackupPath; project.MaxBackupsPerFile = req.MaxBackupsPerFile; await db.SaveChangesAsync(); // Watcher neu starten (stoppt automatisch den alten) await watcher.StartWatchingAsync(project); _ = Task.Run(async () => { try { await watcher.ScanExistingFilesAsync(project); } catch (Exception ex) { logger.LogError(ex, "Fehler beim initialen Scan für Projekt {Id}", project.Id); } }); await hubContext.Clients.All.SendAsync(HubMethodNames.ProjectConfigChanged, CancellationToken.None); return Results.Ok(project); }); group.MapDelete("/{id:guid}", async (Guid id, IDbContextFactory dbFactory, WatcherService watcher, IHubContext hubContext) => { await using var db = await dbFactory.CreateDbContextAsync(); var project = await db.Projects.FindAsync(id); if (project is null) return Results.NotFound(); await watcher.StopWatchingAsync(id); db.Projects.Remove(project); await db.SaveChangesAsync(); await hubContext.Clients.All.SendAsync(HubMethodNames.ProjectConfigChanged, CancellationToken.None); return Results.NoContent(); }); group.MapGet("/scan", async (string engineeringPath, string simulationPath, string fileExtensions) => { if (!Directory.Exists(engineeringPath)) return Results.BadRequest($"Engineering-Pfad existiert nicht: {engineeringPath}"); if (!Directory.Exists(simulationPath)) return Results.BadRequest($"Simulations-Pfad existiert nicht: {simulationPath}"); var extensions = string.IsNullOrWhiteSpace(fileExtensions) || fileExtensions == "*" ? new HashSet() : fileExtensions.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .ToHashSet(StringComparer.OrdinalIgnoreCase); var scanAll = extensions.Count == 0; var engFiles = scanAll ? Directory.EnumerateFiles(engineeringPath, "*", SearchOption.AllDirectories).ToList() : Directory.EnumerateFiles(engineeringPath, "*", SearchOption.AllDirectories) .Where(f => extensions.Contains(Path.GetExtension(f))) .ToList(); var results = new List(); foreach (var engFile in engFiles) { var relativePath = Path.GetRelativePath(engineeringPath, engFile); var simFile = Path.Combine(simulationPath, relativePath); var engInfo = new FileInfo(engFile); var engHash = await FileHasher.ComputeAsync(engFile); var existsInSim = File.Exists(simFile); var needsSync = !existsInSim; if (existsInSim) { var simHash = await FileHasher.ComputeAsync(simFile); needsSync = engHash != simHash; } if (needsSync) { results.Add(new ScanResultEntry( relativePath, existsInSim ? "Modified" : "Created", engInfo.Length, engInfo.LastWriteTimeUtc )); } } return Results.Ok(results); }); return app; } }