|  |  | 1 |  | using System.Text.Json; | 
|  |  | 2 |  | using LGDXRobotCloud.API.Authorisation; | 
|  |  | 3 |  | using LGDXRobotCloud.API.Services.Navigation; | 
|  |  | 4 |  | using LGDXRobotCloud.Data.Models.DTOs.V1.Commands; | 
|  |  | 5 |  | using LGDXRobotCloud.Data.Models.DTOs.V1.Responses; | 
|  |  | 6 |  | using LGDXRobotCloud.Data.Models.Business.Navigation; | 
|  |  | 7 |  | using Microsoft.AspNetCore.Authentication.JwtBearer; | 
|  |  | 8 |  | using Microsoft.AspNetCore.Authorization; | 
|  |  | 9 |  | using Microsoft.AspNetCore.Mvc; | 
|  |  | 10 |  | using LGDXRobotCloud.Utilities.Constants; | 
|  |  | 11 |  | using LGDXRobotCloud.Data.Models.DTOs.V1.Requests; | 
|  |  | 12 |  | using LGDXRobotCloud.Protos; | 
|  |  | 13 |  | using LGDXRobotCloud.API.Exceptions; | 
|  |  | 14 |  |  | 
|  |  | 15 |  | namespace LGDXRobotCloud.API.Areas.Navigation.Controllers; | 
|  |  | 16 |  |  | 
|  |  | 17 |  | [ApiController] | 
|  |  | 18 |  | [Area("Navigation")] | 
|  |  | 19 |  | [Route("[area]/[controller]")] | 
|  |  | 20 |  | [Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.ApiKeyOrCertificateScheme)] | 
|  |  | 21 |  | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] | 
|  |  | 22 |  | [ValidateLgdxUserAccess] | 
|  | 0 | 23 |  | public class RealmsController( | 
|  | 0 | 24 |  |   IRealmService realmService, | 
|  | 0 | 25 |  |   ISlamService slamService | 
|  | 0 | 26 |  | ) : ControllerBase | 
|  |  | 27 |  | { | 
|  | 0 | 28 |  |   private readonly IRealmService _realmService = realmService ?? throw new ArgumentNullException(nameof(realmService)); | 
|  | 0 | 29 |  |   private readonly ISlamService _slamService = slamService ?? throw new ArgumentNullException(nameof(slamService)); | 
|  |  | 30 |  |  | 
|  |  | 31 |  |   [HttpGet("")] | 
|  |  | 32 |  |   [ProducesResponseType(typeof(IEnumerable<RealmListDto>), StatusCodes.Status200OK)] | 
|  |  | 33 |  |   public async Task<ActionResult<IEnumerable<RealmListDto>>> GetRealms(string? name, int pageNumber = 1, int pageSize =  | 
|  | 0 | 34 |  |   { | 
|  | 0 | 35 |  |     pageSize = (pageSize > 100) ? 100 : pageSize; | 
|  | 0 | 36 |  |     var (realms, PaginationHelper) = await _realmService.GetRealmsAsync(name, pageNumber, pageSize); | 
|  | 0 | 37 |  |     Response.Headers.Append("X-Pagination", JsonSerializer.Serialize(PaginationHelper)); | 
|  | 0 | 38 |  |     return Ok(realms.ToDto()); | 
|  | 0 | 39 |  |   } | 
|  |  | 40 |  |  | 
|  |  | 41 |  |   [HttpGet("Search")] | 
|  |  | 42 |  |   [ProducesResponseType(typeof(IEnumerable<RealmSearchDto>), StatusCodes.Status200OK)] | 
|  |  | 43 |  |   public async Task<ActionResult<IEnumerable<RealmSearchDto>>> SearchRealms(string? name) | 
|  | 0 | 44 |  |   { | 
|  | 0 | 45 |  |     var realms = await _realmService.SearchRealmsAsync(name); | 
|  | 0 | 46 |  |     return Ok(realms.ToDto()); | 
|  | 0 | 47 |  |   } | 
|  |  | 48 |  |  | 
|  |  | 49 |  |   [HttpGet("{id}", Name = "GetRealm")] | 
|  |  | 50 |  |   [ProducesResponseType(typeof(RealmDto), StatusCodes.Status200OK)] | 
|  |  | 51 |  |   [ProducesResponseType(StatusCodes.Status404NotFound)] | 
|  |  | 52 |  |   public async Task<ActionResult<RealmDto>> GetRealm(int id) | 
|  | 0 | 53 |  |   { | 
|  | 0 | 54 |  |     var realm = await _realmService.GetRealmAsync(id); | 
|  | 0 | 55 |  |     return Ok(realm.ToDto()); | 
|  | 0 | 56 |  |   } | 
|  |  | 57 |  |  | 
|  |  | 58 |  |   [HttpGet("Default")] | 
|  |  | 59 |  |   [ProducesResponseType(typeof(RealmDto), StatusCodes.Status200OK)] | 
|  |  | 60 |  |   [ProducesResponseType(StatusCodes.Status404NotFound)] | 
|  |  | 61 |  |   public async Task<ActionResult<RealmDto>> GetDefaultRealm() | 
|  | 0 | 62 |  |   { | 
|  | 0 | 63 |  |     var realm = await _realmService.GetDefaultRealmAsync(); | 
|  | 0 | 64 |  |     return Ok(realm.ToDto()); | 
|  | 0 | 65 |  |   } | 
|  |  | 66 |  |  | 
|  |  | 67 |  |   [HttpPost("")] | 
|  |  | 68 |  |   [ProducesResponseType(typeof(RealmDto), StatusCodes.Status201Created)] | 
|  |  | 69 |  |   public async Task<ActionResult> CreateRealm(RealmCreateDto realmCreateDto) | 
|  | 0 | 70 |  |   { | 
|  | 0 | 71 |  |     var realm = await _realmService.CreateRealmAsync(realmCreateDto.ToBusinessModel()); | 
|  | 0 | 72 |  |     return CreatedAtAction(nameof(GetRealm), new { id = realm.Id }, realm.ToDto()); | 
|  | 0 | 73 |  |   } | 
|  |  | 74 |  |  | 
|  |  | 75 |  |   [HttpPut("{id}")] | 
|  |  | 76 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 77 |  |   [ProducesResponseType(StatusCodes.Status404NotFound)] | 
|  |  | 78 |  |   public async Task<ActionResult> UpdateRealm(int id, RealmUpdateDto realmUpdateDto) | 
|  | 0 | 79 |  |   { | 
|  | 0 | 80 |  |     if (!await _realmService.UpdateRealmAsync(id, realmUpdateDto.ToBusinessModel())) | 
|  | 0 | 81 |  |     { | 
|  | 0 | 82 |  |       return NotFound(); | 
|  |  | 83 |  |     } | 
|  | 0 | 84 |  |     return NoContent(); | 
|  | 0 | 85 |  |   } | 
|  |  | 86 |  |  | 
|  |  | 87 |  |   [HttpPost("{id}/TestDelete")] | 
|  |  | 88 |  |   [ProducesResponseType(StatusCodes.Status200OK)] | 
|  |  | 89 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 90 |  |   public async Task<ActionResult> TestDeleteRealm(int id) | 
|  | 0 | 91 |  |   { | 
|  | 0 | 92 |  |     await _realmService.TestDeleteRealmAsync(id); | 
|  | 0 | 93 |  |     return Ok(); | 
|  | 0 | 94 |  |   } | 
|  |  | 95 |  |  | 
|  |  | 96 |  |   [HttpDelete("{id}")] | 
|  |  | 97 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 98 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 99 |  |   [ProducesResponseType(StatusCodes.Status404NotFound)] | 
|  |  | 100 |  |   public async Task<ActionResult> DeleteRealm(int id) | 
|  | 0 | 101 |  |   { | 
|  | 0 | 102 |  |     await _realmService.TestDeleteRealmAsync(id); | 
|  | 0 | 103 |  |     if (!await _realmService.DeleteRealmAsync(id)) | 
|  | 0 | 104 |  |     { | 
|  | 0 | 105 |  |       return NotFound(); | 
|  |  | 106 |  |     } | 
|  | 0 | 107 |  |     return NoContent(); | 
|  | 0 | 108 |  |   } | 
|  |  | 109 |  |  | 
|  |  | 110 |  |   /* | 
|  |  | 111 |  |    * Slam | 
|  |  | 112 |  |    */ | 
|  |  | 113 |  |  | 
|  |  | 114 |  |   [HttpPost("{id}/Slam/SetGoal")] | 
|  |  | 115 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 116 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 117 |  |   public async Task<ActionResult> SetGoalAsync(int id, RobotDofDto goal) | 
|  | 0 | 118 |  |   { | 
|  | 0 | 119 |  |     if (await _slamService.AddSlamCommandAsync(id, new RobotClientsSlamCommands | 
|  | 0 | 120 |  |     { | 
|  | 0 | 121 |  |       SetGoal = new RobotClientsDof | 
|  | 0 | 122 |  |       { | 
|  | 0 | 123 |  |         X = goal.X, | 
|  | 0 | 124 |  |         Y = goal.Y, | 
|  | 0 | 125 |  |         Rotation = goal.Rotation | 
|  | 0 | 126 |  |       } | 
|  | 0 | 127 |  |     })) | 
|  | 0 | 128 |  |     { | 
|  | 0 | 129 |  |       return NoContent(); | 
|  |  | 130 |  |     } | 
|  | 0 | 131 |  |     throw new LgdxValidation400Expection(nameof(id), $"The realm has no robot running SLAM or the realm does not exist." | 
|  | 0 | 132 |  |   } | 
|  |  | 133 |  |  | 
|  |  | 134 |  |   [HttpPost("{id}/Slam/AbortGoal")] | 
|  |  | 135 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 136 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 137 |  |   public async Task<ActionResult> AbortGoalAsync(int id) | 
|  | 0 | 138 |  |   { | 
|  | 0 | 139 |  |     if (await _slamService.AddSlamCommandAsync(id, new RobotClientsSlamCommands | 
|  | 0 | 140 |  |     { | 
|  | 0 | 141 |  |       AbortGoal = true | 
|  | 0 | 142 |  |     })) | 
|  | 0 | 143 |  |     { | 
|  | 0 | 144 |  |       return NoContent(); | 
|  |  | 145 |  |     } | 
|  | 0 | 146 |  |     throw new LgdxValidation400Expection(nameof(id), $"The realm has no robot running SLAM or the realm does not exist." | 
|  | 0 | 147 |  |   } | 
|  |  | 148 |  |  | 
|  |  | 149 |  |   [HttpPost("{id}/Slam/RefreshMap")] | 
|  |  | 150 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 151 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 152 |  |   public async Task<ActionResult> RefreshMapAsync(int id) | 
|  | 0 | 153 |  |   { | 
|  | 0 | 154 |  |     if (await _slamService.AddSlamCommandAsync(id, new RobotClientsSlamCommands | 
|  | 0 | 155 |  |     { | 
|  | 0 | 156 |  |       RefreshMap = true | 
|  | 0 | 157 |  |     })) | 
|  | 0 | 158 |  |     { | 
|  | 0 | 159 |  |       return NoContent(); | 
|  |  | 160 |  |     } | 
|  | 0 | 161 |  |     throw new LgdxValidation400Expection(nameof(id), $"The realm has no robot running SLAM or the realm does not exist." | 
|  | 0 | 162 |  |   } | 
|  |  | 163 |  |  | 
|  |  | 164 |  |   [HttpPost("{id}/Slam/SaveMap")] | 
|  |  | 165 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 166 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 167 |  |   public async Task<ActionResult> SaveMapAsync(int id) | 
|  | 0 | 168 |  |   { | 
|  | 0 | 169 |  |     if (await _slamService.AddSlamCommandAsync(id, new RobotClientsSlamCommands | 
|  | 0 | 170 |  |     { | 
|  | 0 | 171 |  |       SaveMap = true | 
|  | 0 | 172 |  |     })) | 
|  | 0 | 173 |  |     { | 
|  | 0 | 174 |  |       return NoContent(); | 
|  |  | 175 |  |     } | 
|  | 0 | 176 |  |     throw new LgdxValidation400Expection(nameof(id), $"The realm has no robot running SLAM or the realm does not exist." | 
|  | 0 | 177 |  |   } | 
|  |  | 178 |  |  | 
|  |  | 179 |  |   [HttpPost("{id}/Slam/EmergencyStop")] | 
|  |  | 180 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 181 |  |   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] | 
|  |  | 182 |  |   public async Task<ActionResult> EmergencyStopAsync(int id, EnableDto enableDto) | 
|  | 0 | 183 |  |   { | 
|  | 0 | 184 |  |     var command = new RobotClientsSlamCommands(); | 
|  | 0 | 185 |  |     if (enableDto.Enable) | 
|  | 0 | 186 |  |     { | 
|  | 0 | 187 |  |       command.SoftwareEmergencyStopEnable = true; | 
|  | 0 | 188 |  |     } | 
|  |  | 189 |  |     else | 
|  | 0 | 190 |  |     { | 
|  | 0 | 191 |  |       command.SoftwareEmergencyStopDisable = true; | 
|  | 0 | 192 |  |     } | 
|  |  | 193 |  |  | 
|  | 0 | 194 |  |     if (await _slamService.AddSlamCommandAsync(id, command)) | 
|  | 0 | 195 |  |     { | 
|  | 0 | 196 |  |       return NoContent(); | 
|  |  | 197 |  |     } | 
|  | 0 | 198 |  |     throw new LgdxValidation400Expection(nameof(id), $"The realm has no robot running SLAM or the realm does not exist." | 
|  | 0 | 199 |  |   } | 
|  |  | 200 |  |  | 
|  |  | 201 |  |   [HttpPost("{id}/Slam/Abort")] | 
|  |  | 202 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 203 |  |   public async Task<ActionResult> AbortSlamAsync(int id) | 
|  | 0 | 204 |  |   { | 
|  | 0 | 205 |  |     await _slamService.AddSlamCommandAsync(id, new RobotClientsSlamCommands | 
|  | 0 | 206 |  |     { | 
|  | 0 | 207 |  |       AbortSlam = true | 
|  | 0 | 208 |  |     }); | 
|  | 0 | 209 |  |     return NoContent(); | 
|  |  | 210 |  |     // Don't throw exception to allow the UI continue | 
|  | 0 | 211 |  |   } | 
|  |  | 212 |  |  | 
|  |  | 213 |  |   [HttpPost("{id}/Slam/Complete")] | 
|  |  | 214 |  |   [ProducesResponseType(StatusCodes.Status204NoContent)] | 
|  |  | 215 |  |   public async Task<ActionResult> CompleteSlamAsync(int id, RealmMapUpdateDto realmMapUpdateDto) | 
|  | 0 | 216 |  |   { | 
|  | 0 | 217 |  |     await _realmService.UpdateRealmMapAsync(id, realmMapUpdateDto.ToBusinessModel()); | 
|  | 0 | 218 |  |     await _slamService.AddSlamCommandAsync(id, new RobotClientsSlamCommands | 
|  | 0 | 219 |  |     { | 
|  | 0 | 220 |  |       CompleteSlam = true | 
|  | 0 | 221 |  |     }); | 
|  | 0 | 222 |  |     return NoContent(); | 
|  |  | 223 |  |     // Don't throw exception to allow the UI continue | 
|  | 0 | 224 |  |   } | 
|  |  | 225 |  | } |