174 lines
7.5 KiB
C#
174 lines
7.5 KiB
C#
|
|
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<AppDbContext> 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<AppDbContext> 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<AppDbContext> dbFactory,
|
||
|
|
WatcherService watcher, IHubContext<NotificationHub> 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<AppDbContext> dbFactory, WatcherService watcher, IHubContext<NotificationHub> 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<AppDbContext> dbFactory,
|
||
|
|
WatcherService watcher, IHubContext<NotificationHub> 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<string>()
|
||
|
|
: 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<ScanResultEntry>();
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|