using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using RobotNet.MapManager.Data; using RobotNet.MapManager.Hubs; using RobotNet.MapManager.Services; using RobotNet.MapShares.Dtos; using RobotNet.MapShares.Enums; using RobotNet.Shares; namespace RobotNet.MapManager.Controllers; [Route("api/[controller]")] [ApiController] [Authorize] public class MapsManagerController(MapEditorDbContext MapDb, MapEditorStorageRepository StorageRepo, IHubContext MapHub, LoggerController Logger) : ControllerBase { [HttpGet] public async Task> GetMapInfos([FromQuery(Name = "txtSearch")] string? txtSearch) { try { if (string.IsNullOrWhiteSpace(txtSearch)) { return await (from map in MapDb.Maps select new MapInfoDto() { Id = map.Id, VersionId = map.VersionId, Name = map.Name, Description = map.Description, Active = map.Active, OriginX = map.OriginX, OriginY = map.OriginY, Width = Math.Round(map.ImageWidth * map.Resolution, 2), Height = Math.Round(map.ImageHeight * map.Resolution, 2), Resolution = map.Resolution, VDA5050 = map.VDA5050, }).ToListAsync(); } else { return await (from map in MapDb.Maps where !string.IsNullOrEmpty(map.Name) && map.Name.Contains(txtSearch) select new MapInfoDto() { Id = map.Id, VersionId = map.VersionId, Name = map.Name, Description = map.Description, Active = map.Active, OriginX = map.OriginX, OriginY = map.OriginY, Width = Math.Round(map.ImageWidth * map.Resolution, 2), Height = Math.Round(map.ImageHeight * map.Resolution, 2), Resolution = map.Resolution, VDA5050 = map.VDA5050, }).ToListAsync(); } } catch (Exception ex) { Logger.Warning($"GetMapInfos: Hệ thống có lỗi xảy ra - {ex.Message}"); return []; } } [HttpGet] [Route("{id}")] public async Task> GetMapInfoId(Guid id) { var map = await MapDb.Maps.FirstOrDefaultAsync(m => m.Id == id); if (map == null) return new MessageResult(false, $"Không tìm thấy map {id}"); return new(true) { Data = new MapInfoDto() { Id = map.Id, Name = map.Name, Active = map.Active, OriginX = map.OriginX, OriginY = map.OriginY, Width = Math.Round(map.ImageWidth * map.Resolution, 2), Height = Math.Round(map.ImageHeight * map.Resolution, 2), Resolution = map.Resolution, }, }; } [HttpGet] [Route("info")] public async Task> GetMapInfoName([FromQuery(Name = "name")] string name) { var map = await MapDb.Maps.FirstOrDefaultAsync(m => m.Name == name); if (map == null) return new MessageResult(false, $"Không tìm thấy map {name}"); return new(true) { Data = new MapInfoDto() { Id = map.Id, Name = map.Name, Active = map.Active, OriginX = map.OriginX, OriginY = map.OriginY, Width = Math.Round(map.ImageWidth * map.Resolution, 2), Height = Math.Round(map.ImageHeight * map.Resolution, 2), Resolution = map.Resolution, }, }; } [HttpPost] [Route("")] public async Task> CreateMap([FromForm] MapCreateModel model, [FromForm(Name = "Image")] IFormFile imageUpload) { try { if (imageUpload is null) return new(false, "Dữ liệu không hợp lệ"); if (imageUpload.ContentType != "image/png") return new(false, "Ảnh map chỉ hỗ trợ định dạng image/png"); if (await MapDb.Maps.AnyAsync(map => map.Name == model.Name)) return new(false, "Tên của map đã tồn tại. Hãy đặt tên khác!"); var image = SixLabors.ImageSharp.Image.Load(imageUpload.OpenReadStream()); var entityMap = await MapDb.Maps.AddAsync(new Map() { Name = model.Name, OriginX = model.OriginX, OriginY = model.OriginY, Resolution = model.Resolution, ImageHeight = (ushort)image.Height, ImageWidth = (ushort)image.Width, Active = false, NodeCount = 0, NodeNameAutoGenerate = true, NodeNameTemplateDefault = "Node", NodeAllowedDeviationXyDefault = 0.1, NodeAllowedDeviationThetaDefault = 0, EdgeMinLengthDefault = 1, EdgeMaxHeightDefault = 1, EdgeMinHeightDefault = 0.1, EdgeStraightMaxSpeedDefault = 1, EdgeCurveMaxSpeedDefault = 0.3, EdgeMaxRotationSpeedDefault = 0.5, EdgeAllowedDeviationXyDefault = 0.1, EdgeAllowedDeviationThetaDefault = 0, EdgeRotationAllowedDefault = true, EdgeDirectionAllowedDefault = DirectionAllowed.Both, ZoneMinSquareDefault = 0.25, }); await MapDb.SaveChangesAsync(); var (isSuccess, message) = await StorageRepo.UploadAsync("MapImages", $"{entityMap.Entity.Id}", imageUpload.OpenReadStream(), imageUpload.Length, imageUpload.ContentType, CancellationToken.None); if (!isSuccess) { MapDb.Maps.Remove(entityMap.Entity); await MapDb.SaveChangesAsync(); return new(false, message); } await MapDb.SaveChangesAsync(); Logger.Info($"User {HttpContext.User.Identity?.Name} đã tạo bản đồ mới với tên: {model.Name}"); return new(true) { Data = new MapInfoDto() { Id = entityMap.Entity.Id, Name = entityMap.Entity.Name, Active = entityMap.Entity.Active, OriginX = entityMap.Entity.OriginX, OriginY = entityMap.Entity.OriginY, Width = entityMap.Entity.ImageWidth * entityMap.Entity.Resolution, Height = entityMap.Entity.ImageHeight * entityMap.Entity.Resolution, Resolution = entityMap.Entity.Resolution, }, }; } catch (Exception ex) { Logger.Warning($"CreateMap: Hệ thống có lỗi xảy ra - {ex.Message}"); return new(false, $"CreateMap: Hệ thống có lỗi xảy ra - {ex.Message}"); } } [HttpPut] [Route("{id}")] public async Task> UpdateMap(Guid id, [FromBody] MapUpdateModel model) { try { if (id != model.Id) return new(false, "Dữ liệu gửi không chính xác"); var map = await MapDb.Maps.FindAsync(id); if (map == null) return new(false, $"Không tồn tại map id = {id}"); if (map.Name != model.Name && await MapDb.Maps.AnyAsync(m => m.Name == model.Name)) { return new(false, "Tên của map đã tồn tại, Hãy đặt tên khác!"); } if (model.Resolution <= 0) { return new(false, "Độ phân giải của bản đồ phải lớn hơn 0"); } map.Name = model.Name; bool originChange = map.OriginX != model.OriginX || map.OriginY != model.OriginY; if (originChange) { map.OriginX = model.OriginX; map.OriginY = model.OriginY; } if (map.Resolution != model.Resolution) { var scale = model.Resolution / map.Resolution; map.Resolution = model.Resolution; if (originChange) { map.OriginX *= scale; map.OriginY *= scale; } var nodes = await MapDb.Nodes.Where(n => n.MapId == map.Id).ToListAsync(); foreach (var node in nodes) { node.X *= scale; node.Y *= scale; } var edges = await MapDb.Edges.Where(e => e.MapId == map.Id).ToListAsync(); foreach (var edge in edges) { edge.ControlPoint1X *= scale; edge.ControlPoint1Y *= scale; edge.ControlPoint2X *= scale; edge.ControlPoint2Y *= scale; } } await MapDb.SaveChangesAsync(); Logger.Info($"User {HttpContext.User.Identity?.Name} đã cập nhật thông tin bản đồ : {model.Id} - {map.Name}"); return new(true) { Data = new() { Id = id, Name = map.Name, OriginX = map.OriginX, OriginY = map.OriginY, Resolution = model.Resolution, Width = Math.Round(map.ImageWidth * map.Resolution, 2), Height = Math.Round(map.ImageHeight * map.Resolution, 2), }, }; } catch (Exception ex) { Logger.Warning($"UpdateMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}"); return new(false, $"UpdateMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}"); } } [HttpDelete] [Route("{id}")] public async Task DeleteMap(Guid id) { try { var map = await MapDb.Maps.FindAsync(id); if (map == null) return new(false, $"Không tồn tại map id = {id}"); MapDb.Elements.RemoveRange(MapDb.Elements.Where(e => e.MapId == map.Id)); MapDb.ElementModels.RemoveRange(MapDb.ElementModels.Where(em => em.MapId == map.Id)); MapDb.Edges.RemoveRange(MapDb.Edges.Where(edge => edge.MapId == map.Id)); MapDb.Nodes.RemoveRange(MapDb.Nodes.Where(node => node.MapId == map.Id)); MapDb.Zones.RemoveRange(MapDb.Zones.Where(zone => zone.MapId == map.Id)); MapDb.Actions.RemoveRange(MapDb.Actions.Where(action => action.MapId == map.Id)); MapDb.Maps.Remove(map); await MapDb.SaveChangesAsync(); await StorageRepo.DeleteAsync("MapImages", $"{id}", CancellationToken.None); Logger.Info($"User {HttpContext.User.Identity?.Name} đã xóa bản đồ {map.Name}"); return new(true); } catch (Exception ex) { Logger.Warning($"DeleteMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}"); return new(false, $"DeleteMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}"); } } [HttpPut] [Route("active")] public async Task ActiveToggle([FromBody] MapActiveModel model) { var map = await MapDb.Maps.FindAsync(model.Id); if (map == null) return new(false, $"Không tồn tại map id = {model.Id}"); map.Active = model.Active; await MapDb.SaveChangesAsync(); await MapHub.Clients.All.SendAsync("MapUpdated", model.Id); Logger.Info($"User {HttpContext.User.Identity?.Name} đã thay đổi trạng thái active bản đồ {map.Name}: {model.Active}"); return new(true); } [HttpPut] [Route("image/{id}")] public async Task UpdateImage(Guid id, [FromForm(Name = "image")] IFormFile image) { try { var map = await MapDb.Maps.FindAsync(id); if (map == null) return new(false, $"Không tồn tại map id = {id}"); var imageStream = image.OpenReadStream(); var imageUpdate = SixLabors.ImageSharp.Image.Load(imageStream); map.ImageWidth = (ushort)imageUpdate.Width; map.ImageHeight = (ushort)imageUpdate.Height; await MapDb.SaveChangesAsync(); var (isSuccess, message) = await StorageRepo.UploadAsync("MapImages", $"{id}", image.OpenReadStream(), image.Length, image.ContentType, CancellationToken.None); Logger.Info($"User {HttpContext.User.Identity?.Name} đã thay đổi ảnh của bản đồ {map.Name}"); return new(true); } catch (Exception ex) { Logger.Warning($"UpdateImage {id}: Hệ thống có lỗi xảy ra - {ex.Message}"); return new(false, $"UpdateImage {id}: Hệ thống có lỗi xảy ra - {ex.Message}"); } } [HttpPost] [Route("import")] public async Task> ImportNewMap([FromBody] MapExportDto model) { if (model == null || model.Data == null || model.Data.ImageData == null || model.Data.ImageData.Length == 0) { return new(false, "Dữ liệu đầu vào không hợp lệ"); } using var transaction = await MapDb.Database.BeginTransactionAsync(); try { if (await MapDb.Maps.AnyAsync(map => map.Name == model.Name)) return new(false, "Tên của map đã tồn tại, Hãy đặt tên khác!"); using var stream = new MemoryStream(model.Data.ImageData); var imageFile = new FormFile(stream, 0, model.Data.ImageData.Length, "", $"{model.Name}.png") { Headers = new HeaderDictionary(), ContentType = "image/png", }; var image = SixLabors.ImageSharp.Image.Load(imageFile.OpenReadStream()); var entityMap = await MapDb.Maps.AddAsync(new Map() { Name = model.Name, Description = model.Description, OriginX = model.Info.OriginX, OriginY = model.Info.OriginY, Resolution = model.Info.Resolution, ImageHeight = (ushort)image.Height, ImageWidth = (ushort)image.Width, Active = false, ViewX = model.Info.ViewX, ViewY = model.Info.ViewY, ViewWidth = model.Info.ViewWidth, ViewHeight = model.Info.ViewHeight, VDA5050 = model.Info.VDA5050, NodeCount = model.Data.NodeCount, NodeNameAutoGenerate = model.Setting.NodeNameAutoGenerate, NodeNameTemplateDefault = model.Setting.NodeNameTemplate, NodeAllowedDeviationXyDefault = model.Setting.NodeAllowedDeviationXy, NodeAllowedDeviationThetaDefault = model.Setting.NodeAllowedDeviationTheta, EdgeMinLengthDefault = model.Setting.EdgeMinLength, EdgeMaxHeightDefault = model.Setting.EdgeMaxHeight, EdgeMinHeightDefault = model.Setting.EdgeMinHeight, EdgeStraightMaxSpeedDefault = model.Setting.EdgeStraightMaxSpeed, EdgeCurveMaxSpeedDefault = model.Setting.EdgeCurveMaxSpeed, EdgeMaxRotationSpeedDefault = model.Setting.EdgeMaxRotationSpeed, EdgeAllowedDeviationXyDefault = model.Setting.EdgeAllowedDeviationXy, EdgeAllowedDeviationThetaDefault = model.Setting.EdgeAllowedDeviationTheta, EdgeRotationAllowedDefault = model.Setting.EdgeRotationAllowed, EdgeDirectionAllowedDefault = model.Setting.EdgeDirectionAllowed, ZoneMinSquareDefault = model.Setting.ZoneMinSquare, }); var (isSuccess, message) = await StorageRepo.UploadAsync("MapImages", $"{entityMap.Entity.Id}", imageFile.OpenReadStream(), imageFile.Length, imageFile.ContentType, CancellationToken.None); if (!isSuccess) { await transaction.RollbackAsync(); return new(false, message); } Dictionary actionSwap = []; foreach (var action in model.Data.Actions) { var actionDb = await MapDb.Actions.AddAsync(new Data.Action() { MapId = entityMap.Entity.Id, Name = action.Name, Content = action.Content, }); actionSwap.Add(action.Id, actionDb.Entity.Id); } Dictionary nodeSwap = []; foreach (var node in model.Data.Nodes) { var actions = System.Text.Json.JsonSerializer.Deserialize(node.Actions ?? ""); List newActions = []; if (actions is not null && actions.Length > 0) { foreach (var actionId in actions) { if (actionSwap.TryGetValue(actionId, out Guid newActionId) && newActionId != Guid.Empty) newActions.Add(newActionId); } } var nodeDb = await MapDb.Nodes.AddAsync(new Node() { Name = node.Name, MapId = entityMap.Entity.Id, X = node.X, Y = node.Y, Theta = node.Theta, AllowedDeviationXy = node.AllowedDeviationXy, AllowedDeviationTheta = node.AllowedDeviationTheta, Actions = System.Text.Json.JsonSerializer.Serialize(newActions), }); nodeSwap.Add(node.Id, nodeDb.Entity.Id); } var Edges = model.Data.Edges.Select(e => new Edge() { MapId = entityMap.Entity.Id, StartNodeId = nodeSwap[e.StartNodeId], EndNodeId = nodeSwap[e.EndNodeId], ControlPoint1X = e.ControlPoint1X, ControlPoint1Y = e.ControlPoint1Y, ControlPoint2X = e.ControlPoint2X, ControlPoint2Y = e.ControlPoint2Y, TrajectoryDegree = e.TrajectoryDegree, MaxHeight = e.MaxHeight, MinHeight = e.MinHeight, MaxSpeed = e.MaxSpeed, MaxRotationSpeed = e.MaxRotationSpeed, AllowedDeviationXy = e.AllowedDeviationXy, AllowedDeviationTheta = e.AllowedDeviationTheta, DirectionAllowed = e.DirectionAllowed, RotationAllowed = e.RotationAllowed, Actions = e.Actions, }).ToList(); var Zones = model.Data.Zones.Select(z => new Zone() { MapId = entityMap.Entity.Id, Type = z.Type, X1 = z.X1, X2 = z.X2, Y1 = z.Y1, Y2 = z.Y2, X3 = z.X3, X4 = z.X4, Y3 = z.Y3, Y4 = z.Y4, }).ToList(); Dictionary elementModelSwap = []; foreach (var elementModel in model.Data.ElementModels) { var elementModelDb = await MapDb.ElementModels.AddAsync(new ElementModel() { MapId = entityMap.Entity.Id, Name = elementModel.Name, Height = elementModel.Height, Width = elementModel.Width, Image1Height = elementModel.Image1Height, Image1Width = elementModel.Image1Width, Image2Height = elementModel.Image2Height, Image2Width = elementModel.Image2Width, Content = elementModel.Content, }); elementModelSwap.Add(elementModel.Id, elementModelDb.Entity.Id); using var openStream = new MemoryStream(elementModel.ImageOpenData); var imageOpenFile = new FormFile(openStream, 0, elementModel.ImageOpenData.Length, "", $"{elementModel.Name}O.png") { Headers = new HeaderDictionary(), ContentType = "image/png", }; await StorageRepo.UploadAsync("ElementOpenModels", $"{elementModelDb.Entity.Id}", imageOpenFile.OpenReadStream(), imageOpenFile.Length, imageOpenFile.ContentType, CancellationToken.None); using var closeStream = new MemoryStream(elementModel.ImageCloseData); var imageCloseFile = new FormFile(closeStream, 0, elementModel.ImageCloseData.Length, "", $"{elementModel.Name}C.png") { Headers = new HeaderDictionary(), ContentType = "image/png", }; await StorageRepo.UploadAsync("ElementCloseModels", $"{elementModelDb.Entity.Id}", imageCloseFile.OpenReadStream(), imageCloseFile.Length, imageCloseFile.ContentType, CancellationToken.None); } var Elements = model.Data.Elements.Select(e => new Data.Element() { MapId = entityMap.Entity.Id, Name = e.Name, IsOpen = e.IsOpen, ModelId = elementModelSwap[e.ModelId], Content = e.Content, NodeId = nodeSwap[e.NodeId], OffsetX = e.OffsetX, OffsetY = e.OffsetY, }).ToList(); if (Edges.Count > 0) await MapDb.Edges.AddRangeAsync(Edges); if (Zones.Count > 0) await MapDb.Zones.AddRangeAsync(Zones); if (Elements.Count > 0) await MapDb.Elements.AddRangeAsync(Elements); await MapDb.SaveChangesAsync(); await transaction.CommitAsync(); return new(true) { Data = new MapInfoDto() { Id = entityMap.Entity.Id, Name = entityMap.Entity.Name, Active = entityMap.Entity.Active, OriginX = entityMap.Entity.OriginX, OriginY = entityMap.Entity.OriginY, Width = entityMap.Entity.ImageWidth * entityMap.Entity.Resolution, Height = entityMap.Entity.ImageHeight * entityMap.Entity.Resolution, Resolution = entityMap.Entity.Resolution, } }; } catch (IOException ex) { await transaction.RollbackAsync(); Logger.Warning($"ImportNewMap: Lỗi khi xử lý hình ảnh: {ex.Message}"); return new(false, $"ImportNewMap: Lỗi khi xử lý hình ảnh: {ex.Message}"); } catch (DbUpdateException ex) { await transaction.RollbackAsync(); Logger.Warning($"ImportNewMap: Lỗi khi lưu vào database: {ex.Message}"); return new(false, $"ImportNewMap: Lỗi khi lưu vào database: {ex.Message}"); } catch (Exception ex) { await transaction.RollbackAsync(); Logger.Warning($"ImportNewMap: Hệ thống có lỗi xảy ra - {ex.Message}"); return new(false, $"ImportNewMap: Hệ thống có lỗi xảy ra - {ex.Message}"); } } }