| | 1 | | using Microsoft.AspNetCore.Authorization; |
| | 2 | | using Microsoft.AspNetCore.Mvc; |
| | 3 | | using MRA.DTO.Exceptions; |
| | 4 | | using MRA.DTO.Models; |
| | 5 | | using MRA.DTO.ViewModels.Art; |
| | 6 | | using MRA.DTO.ViewModels.Art.Select; |
| | 7 | | using MRA.Services; |
| | 8 | | using MRA.Services.Models.Drawings; |
| | 9 | | using MRA.Services.Storage; |
| | 10 | | using MRA.WebApi.Models.Responses; |
| | 11 | | using MRA.WebApi.Models.Responses.Errors; |
| | 12 | | using MRA.WebApi.Models.Responses.Errors.Drawings; |
| | 13 | | using System.Globalization; |
| | 14 | |
|
| | 15 | | namespace MRA.WebApi.Controllers.Art; |
| | 16 | |
|
| | 17 | | [ApiController] |
| | 18 | | [Route("api/art/drawing")] |
| | 19 | | public class DrawingController : Controller |
| | 20 | | { |
| | 21 | | private readonly IAppService _appService; |
| | 22 | | private readonly IDrawingService _drawingService; |
| | 23 | | private readonly IStorageService _storageService; |
| | 24 | | private readonly ILogger<DrawingController> _logger; |
| | 25 | |
|
| 13 | 26 | | public DrawingController( |
| 13 | 27 | | ILogger<DrawingController> logger, |
| 13 | 28 | | IAppService appService, |
| 13 | 29 | | IStorageService storageService, |
| 13 | 30 | | IDrawingService drawingService) |
| | 31 | | { |
| 13 | 32 | | _logger = logger; |
| 13 | 33 | | _appService = appService; |
| 13 | 34 | | _drawingService = drawingService; |
| 13 | 35 | | _storageService = storageService; |
| 13 | 36 | | } |
| | 37 | |
|
| | 38 | | [HttpGet("products")] |
| | 39 | | public async Task<ActionResult<IEnumerable<ProductListItem>>> Products() |
| | 40 | | { |
| | 41 | | try |
| | 42 | | { |
| 2 | 43 | | _logger.LogInformation("Requesting products"); |
| 2 | 44 | | var products = await _drawingService.GetProductsAsync(); |
| 1 | 45 | | _logger.LogInformation("Products found: {Count}", products.Count()); |
| 1 | 46 | | return Ok(products); |
| | 47 | | } |
| 1 | 48 | | catch (Exception ex) |
| | 49 | | { |
| 1 | 50 | | _logger.LogError(ex, DrawingProductErrorMessages.InternalServer); |
| 1 | 51 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 1 | 52 | | new ErrorResponse(DrawingProductErrorMessages.InternalServer)); |
| | 53 | | } |
| 2 | 54 | | } |
| | 55 | |
|
| | 56 | | [HttpGet("characters")] |
| | 57 | | public async Task<ActionResult<List<CharacterListItem>>> Characters() |
| | 58 | | { |
| | 59 | | try |
| | 60 | | { |
| 2 | 61 | | _logger.LogInformation("Requesting characters"); |
| 2 | 62 | | var characters = await _drawingService.GetCharactersAsync(); |
| 1 | 63 | | _logger.LogInformation("Characters found: {Count}", characters.Count()); |
| 1 | 64 | | return Ok(characters); |
| | 65 | | } |
| 1 | 66 | | catch (Exception ex) |
| | 67 | | { |
| 1 | 68 | | _logger.LogError(ex, DrawingCharactersErrorMessages.InternalServer); |
| 1 | 69 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 1 | 70 | | new ErrorResponse(DrawingCharactersErrorMessages.InternalServer)); |
| | 71 | | } |
| 2 | 72 | | } |
| | 73 | |
|
| | 74 | | [HttpGet("models")] |
| | 75 | | public async Task<ActionResult<List<string>>> Models() |
| | 76 | | { |
| | 77 | | try |
| | 78 | | { |
| 2 | 79 | | _logger.LogInformation("Requesting models"); |
| 2 | 80 | | var models = await _drawingService.GetModelsAsync(); |
| 1 | 81 | | _logger.LogInformation("Models found: '{Count}'", models.Count()); |
| 3 | 82 | | var modelNames = models.Select(x => x.ModelName).ToList(); |
| 1 | 83 | | return Ok(modelNames); |
| | 84 | | } |
| 1 | 85 | | catch (Exception ex) |
| | 86 | | { |
| 1 | 87 | | _logger.LogError(ex, DrawingModelsErrorMessages.InternalServer); |
| 1 | 88 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 1 | 89 | | new ErrorResponse(DrawingModelsErrorMessages.InternalServer)); |
| | 90 | | } |
| 2 | 91 | | } |
| | 92 | |
|
| | 93 | |
|
| | 94 | | [HttpGet("details/{id}")] |
| | 95 | | public async Task<ActionResult<DrawingModel>> Details(string id) |
| | 96 | | { |
| 3 | 97 | | return await FetchDrawingDetails(id, true); |
| 3 | 98 | | } |
| | 99 | |
|
| | 100 | | [Authorize] |
| | 101 | | [HttpGet("full-details/{id}")] |
| | 102 | | public async Task<ActionResult<DrawingModel>> FullDetails(string id) |
| | 103 | | { |
| 3 | 104 | | return await FetchDrawingDetails(id, false); |
| 3 | 105 | | } |
| | 106 | |
|
| | 107 | | private async Task<ActionResult<DrawingModel>> FetchDrawingDetails(string id, bool onlyIfVisible) |
| | 108 | | { |
| | 109 | | try |
| | 110 | | { |
| 6 | 111 | | _logger.LogInformation("Requesting drawing details '{Id}'", id); |
| 6 | 112 | | var drawing = await _appService.FindDrawingByIdAsync(id, onlyIfVisible: onlyIfVisible, updateViews: true, ca |
| 2 | 113 | | return Ok(drawing); |
| | 114 | | } |
| 2 | 115 | | catch (DrawingNotFoundException dnf) |
| | 116 | | { |
| 2 | 117 | | _logger.LogWarning(dnf, dnf.Message); |
| 2 | 118 | | return NotFound(new ErrorResponse(dnf.Message)); |
| | 119 | | } |
| 2 | 120 | | catch (Exception ex) |
| | 121 | | { |
| 2 | 122 | | _logger.LogError(ex, DrawingDetailsErrorMessages.InternalServer(id)); |
| 2 | 123 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 2 | 124 | | new ErrorResponse(DrawingDetailsErrorMessages.InternalServer(id))); |
| | 125 | | } |
| 6 | 126 | | } |
| | 127 | |
|
| | 128 | |
|
| | 129 | | [HttpPost("filter")] |
| | 130 | | public async Task<ActionResult<FilterResults>> Filter([FromBody] DrawingFilter filters) |
| | 131 | | { |
| 1 | 132 | | filters.OnlyVisible = true; |
| 1 | 133 | | return await FilteringDrawings(filters); |
| 1 | 134 | | } |
| | 135 | |
|
| | 136 | | [Authorize] |
| | 137 | | [HttpPost("full-filter")] |
| | 138 | | public async Task<ActionResult<FilterResults>> FullFilter([FromBody] DrawingFilter filters) |
| | 139 | | { |
| 0 | 140 | | filters.OnlyVisible = false; |
| 0 | 141 | | return await FilteringDrawings(filters); |
| 0 | 142 | | } |
| | 143 | |
|
| | 144 | | private async Task<ActionResult<FilterResults>> FilteringDrawings(DrawingFilter filters) |
| | 145 | | { |
| | 146 | | try |
| | 147 | | { |
| 1 | 148 | | _logger.LogInformation($"Filtering drawings"); |
| 1 | 149 | | var results = await _appService.FilterDrawingsAsync(filters); |
| 0 | 150 | | _logger.LogInformation("Total drawings found: {Count}", results.TotalDrawings.Count()); |
| 0 | 151 | | return Ok(results); |
| | 152 | | } |
| 1 | 153 | | catch (Exception ex) |
| | 154 | | { |
| 1 | 155 | | _logger.LogError(ex, DrawingFilterErrorMessages.InternalServer); |
| 1 | 156 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 1 | 157 | | new ErrorResponse(DrawingFilterErrorMessages.InternalServer)); |
| | 158 | | } |
| 1 | 159 | | } |
| | 160 | |
|
| | 161 | | [HttpPost("cheer")] |
| | 162 | | public async Task<ActionResult> Cheer([FromBody] string id) |
| | 163 | | { |
| | 164 | | try |
| | 165 | | { |
| 0 | 166 | | _logger.LogInformation($"Recibido like para dibujo \"{id}\""); |
| 0 | 167 | | return Ok(await _drawingService.UpdateLikesAsync(id)); |
| | 168 | | } |
| 0 | 169 | | catch (DrawingNotFoundException dnf) |
| | 170 | | { |
| 0 | 171 | | _logger.LogWarning($"No se encontró ningún dibujo \"{id}\""); |
| 0 | 172 | | return NotFound(new { message = $"No drawing found with ID \"{id}\"" }); |
| | 173 | | } |
| 0 | 174 | | catch (Exception ex) |
| | 175 | | { |
| 0 | 176 | | _logger.LogError($"Error al recibir like para dibujo \"{id}\": " + ex.Message); |
| 0 | 177 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 0 | 178 | | new { message = $"Error when liking drawing with ID \"{id}\"" }); |
| | 179 | | } |
| 0 | 180 | | } |
| | 181 | |
|
| | 182 | | [HttpPost("vote/{id}")] |
| | 183 | | public async Task<ActionResult<VoteSubmittedModel>> Vote(string id, [FromBody] int score) |
| | 184 | | { |
| | 185 | | try |
| | 186 | | { |
| 0 | 187 | | _logger.LogInformation($"Recibido voto para dibujo \"{id}\" ({score})"); |
| 0 | 188 | | var results = await _drawingService.VoteDrawingAsync(id, score); |
| 0 | 189 | | _logger.LogInformation($"Nuevos resultados para \"{id}\" ({results.NewScoreHuman}) [{results.NewVotes} votos |
| 0 | 190 | | return Ok(results); |
| | 191 | | } |
| 0 | 192 | | catch (DrawingNotFoundException dnf) |
| | 193 | | { |
| 0 | 194 | | _logger.LogWarning($"No se encontró ningún dibujo \"{id}\""); |
| 0 | 195 | | return NotFound(new { message = $"No drawing found with ID \"{id}\"" }); |
| | 196 | | } |
| 0 | 197 | | catch (Exception ex) |
| | 198 | | { |
| 0 | 199 | | _logger.LogError($"Error al recibir voto para dibujo \"{id}\": " + ex.Message); |
| 0 | 200 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 0 | 201 | | new { message = $"Error when receiving vote with ID \"{id}\"" }); |
| | 202 | | } |
| 0 | 203 | | } |
| | 204 | |
|
| | 205 | | [Authorize] |
| | 206 | | [HttpPost("save/{id}")] |
| | 207 | | public async Task<ActionResult<DrawingModel>> Save(string id, [FromBody] SaveDrawingRequest request) |
| | 208 | | { |
| | 209 | | try |
| | 210 | | { |
| 0 | 211 | | _logger.LogInformation("Guardando dibujo '{Id}'", id); |
| 0 | 212 | | var dateObject = DateTime.ParseExact(request.DateHyphen, "yyyy-MM-dd", CultureInfo.InvariantCulture); |
| 0 | 213 | | var drawing = new DrawingModel() |
| 0 | 214 | | { |
| 0 | 215 | | ListComments = request.ListComments ?? [], |
| 0 | 216 | | ListCommentsStyle = request.ListCommentsStyle ?? [], |
| 0 | 217 | | ListCommentsPros = request.ListCommentsPros ?? [], |
| 0 | 218 | | ListCommentsCons = request.ListCommentsCons ?? [], |
| 0 | 219 | | Date = request.DateHyphen.Replace("-", "/"), |
| 0 | 220 | | DateHyphen = request.DateHyphen, |
| 0 | 221 | | DateObject = dateObject, |
| 0 | 222 | | Favorite = request.Favorite, |
| 0 | 223 | | Id = request.Id, |
| 0 | 224 | | ModelName = request.ModelName, |
| 0 | 225 | | Name = request.Name, |
| 0 | 226 | | Paper = (int)request.Paper, |
| 0 | 227 | | Path = request.Path, |
| 0 | 228 | | PathThumbnail = request.PathThumbnail, |
| 0 | 229 | | ProductName = request.ProductName, |
| 0 | 230 | | ProductType = (int)request.ProductType, |
| 0 | 231 | | ReferenceUrl = request.ReferenceUrl, |
| 0 | 232 | | ScoreCritic = request.ScoreCritic, |
| 0 | 233 | | Software = (int)request.Software, |
| 0 | 234 | | Filter = (int)request.Filter, |
| 0 | 235 | | SpotifyUrl = request.SpotifyUrl, |
| 0 | 236 | | Tags = request.TagsText?.Split(DrawingTagManager.TAG_SEPARATOR) ?? [], |
| 0 | 237 | | Time = request.Time ?? 0, |
| 0 | 238 | | Title = request.Title ?? string.Empty, |
| 0 | 239 | | Type = (int)request.Type, |
| 0 | 240 | | InstagramUrl = request.InstagramUrl, |
| 0 | 241 | | TwitterUrl = request.BlueskyUrl, |
| 0 | 242 | | Visible = request.Visible |
| 0 | 243 | | }; |
| | 244 | |
|
| 0 | 245 | | var savedDrawing = await _drawingService.SaveDrawingAsync(drawing); |
| 0 | 246 | | _appService.Clear(); |
| 0 | 247 | | savedDrawing.TagsText = string.Join(DrawingTagManager.TAG_SEPARATOR, savedDrawing.Tags); |
| 0 | 248 | | return Ok(savedDrawing); |
| | 249 | | } |
| 0 | 250 | | catch (Exception ex) |
| | 251 | | { |
| 0 | 252 | | _logger.LogError($"Error al guardar dibujo \"{id}\": " + ex.Message); |
| 0 | 253 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 0 | 254 | | new { message = $"Error al guardar dibujo \"{id}\"" }); |
| | 255 | | } |
| 0 | 256 | | } |
| | 257 | |
|
| | 258 | | [HttpGet("exist/{id}")] |
| | 259 | | public async Task<ActionResult<bool>> Exist(string id) |
| | 260 | | { |
| | 261 | | try |
| | 262 | | { |
| 0 | 263 | | _logger.LogInformation($"Comprobando si existe dibujo \"{id}\""); |
| 0 | 264 | | var exists = await _drawingService.ExistsDrawing(id); |
| 0 | 265 | | _logger.LogInformation($"Existe dibujo \"{id}\": {(exists ? "Sí" : "No")}"); |
| 0 | 266 | | return Ok(exists); |
| | 267 | | } |
| 0 | 268 | | catch (Exception ex) |
| | 269 | | { |
| 0 | 270 | | _logger.LogError($"Error al comprobar dibujo \"{id}\": " + ex.Message); |
| 0 | 271 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 0 | 272 | | new { message = $"Error when checking drawing with ID \"{id}\"" }); |
| | 273 | | } |
| 0 | 274 | | } |
| | 275 | |
|
| | 276 | | [Authorize] |
| | 277 | | [HttpPost("check/blob")] |
| | 278 | | public async Task<ActionResult<CheckAzurePathResponse>> CheckBlob([FromBody] CheckAzurePathRequest request) |
| | 279 | | { |
| | 280 | | try |
| | 281 | | { |
| 0 | 282 | | _logger.LogInformation($"Comprobando si existe blob de Azure \"{request.Id}\""); |
| 0 | 283 | | var existe = await _storageService.ExistsBlob(request.Id); |
| 0 | 284 | | if (!existe) |
| | 285 | | { |
| 0 | 286 | | _logger.LogWarning($"No existe el blob de Azure \"{request.Id}\""); |
| | 287 | | } |
| | 288 | |
|
| 0 | 289 | | var blobLocationThumbnail = _storageService.CrearThumbnailName(request.Id); |
| | 290 | |
|
| 0 | 291 | | var urlBase = _storageService.GetBlobURL(); |
| 0 | 292 | | var url = urlBase + request.Id; |
| 0 | 293 | | var url_tn = urlBase + blobLocationThumbnail; |
| | 294 | |
|
| 0 | 295 | | return new CheckAzurePathResponse() |
| 0 | 296 | | { |
| 0 | 297 | | Existe = existe, |
| 0 | 298 | | Url = url, |
| 0 | 299 | | UrlThumbnail = url_tn, |
| 0 | 300 | | PathThumbnail = blobLocationThumbnail |
| 0 | 301 | | }; |
| | 302 | | } |
| 0 | 303 | | catch (Exception ex) |
| | 304 | | { |
| 0 | 305 | | _logger.LogError($"Error al comprobar si existe blob de Azure \"{request.Id}\": " + ex.Message); |
| 0 | 306 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 0 | 307 | | new { message = $"Error when checking blob \"{request.Id}\"" }); |
| | 308 | | } |
| 0 | 309 | | } |
| | 310 | |
|
| | 311 | | [Authorize] |
| | 312 | | [HttpPost("upload/blob")] |
| | 313 | | public async Task<ActionResult<UploadAzureImageResponse>> UploadBlob(IFormFile image, [FromForm] int size, [FromForm |
| | 314 | | { |
| | 315 | | try |
| | 316 | | { |
| 0 | 317 | | _logger.LogInformation($"Subiendo fichero a Azure \"{image.FileName}\""); |
| 0 | 318 | | if (image == null || image.Length == 0) |
| | 319 | | { |
| | 320 | | //return new UploadAzureImageResponse() |
| | 321 | | //{ |
| | 322 | | // Ok = false, |
| | 323 | | // Error = "No se ha proporcionado ninguna imagen", |
| | 324 | | //}; |
| 0 | 325 | | _logger.LogWarning("No se ha proporcionado ningún fichero"); |
| 0 | 326 | | return BadRequest(new { message = $"No se ha proporcinoado ningún fichero" }); |
| | 327 | | } |
| | 328 | |
|
| 0 | 329 | | var blobLocationThumbnail = _storageService.CrearThumbnailName(path); |
| 0 | 330 | | await UploadImage(image, blobLocationThumbnail, size); |
| 0 | 331 | | _logger.LogInformation($"Subida imagen de Thumbnail a \"{blobLocationThumbnail}\""); |
| 0 | 332 | | await UploadImage(image, path, 0); |
| 0 | 333 | | _logger.LogInformation($"Subida imagen a \"{path}\""); |
| | 334 | |
|
| 0 | 335 | | var urlBase = _storageService.GetBlobURL(); |
| 0 | 336 | | var url = urlBase + path; |
| 0 | 337 | | var url_tn = urlBase + blobLocationThumbnail; |
| | 338 | |
|
| 0 | 339 | | return Ok(new UploadAzureImageResponse() |
| 0 | 340 | | { |
| 0 | 341 | | Ok = true, |
| 0 | 342 | | Error = "", |
| 0 | 343 | | Url = url, |
| 0 | 344 | | UrlThumbnail = url_tn, |
| 0 | 345 | | PathThumbnail = blobLocationThumbnail |
| 0 | 346 | | }); |
| | 347 | | } |
| 0 | 348 | | catch (Exception ex) |
| | 349 | | { |
| 0 | 350 | | _logger.LogError($"Error al subir blob de Azure: " + ex.Message); |
| 0 | 351 | | return StatusCode(StatusCodes.Status500InternalServerError, |
| 0 | 352 | | new { message = $"Error when uploading blob" }); |
| | 353 | | } |
| 0 | 354 | | } |
| | 355 | |
|
| | 356 | | private async Task UploadImage(IFormFile azureImage, string path, int azureImageThumbnailSize) |
| | 357 | | { |
| 0 | 358 | | using (var imageStream = new MemoryStream()) |
| | 359 | | { |
| 0 | 360 | | await azureImage.CopyToAsync(imageStream); |
| 0 | 361 | | imageStream.Position = 0; |
| | 362 | |
|
| 0 | 363 | | await _storageService.ResizeAndSave(imageStream, path, azureImageThumbnailSize); |
| 0 | 364 | | } |
| 0 | 365 | | } |
| | 366 | | } |