| | | 1 | | using Microsoft.AspNetCore.Authorization; |
| | | 2 | | using Microsoft.AspNetCore.Mvc; |
| | | 3 | | using Services.ApiServices.InterpartsEngine; |
| | | 4 | | |
| | | 5 | | namespace OzonOrdersWeb.Areas.Studio2.Controllers; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Проценка через движок Interparts: бренды по артикулу, предложения и перепроверка предложения. |
| | | 9 | | /// Поиск может идти до ~2 минут, а nginx рвёт запрос через 60 с, поэтому запрос ждёт не дольше |
| | | 10 | | /// <see cref="InterpartsEngineConfig.WebWaitSeconds"/> и отвечает 202 «ещё идёт»: поиск продолжается |
| | | 11 | | /// в фоне (single-flight сервиса), повтор того же запроса подхватывает его или берёт готовый ответ из кэша. |
| | | 12 | | /// </summary> |
| | | 13 | | [Area("Studio2")] |
| | | 14 | | [Authorize(Roles = "User,Admin")] |
| | | 15 | | [Route("Studio2/InterpartsEngine")] |
| | | 16 | | public sealed class InterpartsEngineController : Controller |
| | | 17 | | { |
| | | 18 | | private readonly IInterpartsEngineService _engine; |
| | | 19 | | private readonly InterpartsEngineConfig _config; |
| | | 20 | | |
| | 0 | 21 | | public InterpartsEngineController(IInterpartsEngineService engine, InterpartsEngineConfig config) |
| | | 22 | | { |
| | 0 | 23 | | _engine = engine; |
| | 0 | 24 | | _config = config; |
| | 0 | 25 | | } |
| | | 26 | | |
| | | 27 | | [HttpGet("Brands")] |
| | | 28 | | public Task<IActionResult> Brands(string article, CancellationToken cancellationToken) => |
| | 0 | 29 | | RunAsync(ct => _engine.GetBrandsAsync(article, ct), cancellationToken); |
| | | 30 | | |
| | | 31 | | [HttpGet("Offers")] |
| | | 32 | | public Task<IActionResult> Offers(string article, string brand, CancellationToken cancellationToken) => |
| | 0 | 33 | | RunAsync(ct => _engine.SearchOffersAsync(article, brand, ct), cancellationToken); |
| | | 34 | | |
| | | 35 | | [HttpGet("Offer/{offerId}")] |
| | | 36 | | public Task<IActionResult> Offer(string offerId, CancellationToken cancellationToken) => |
| | 0 | 37 | | RunAsync(ct => _engine.RecheckOfferAsync(offerId, ct), cancellationToken); |
| | | 38 | | |
| | | 39 | | private async Task<IActionResult> RunAsync<T>(Func<CancellationToken, Task<InterpartsEngineResult<T>>> call, |
| | | 40 | | CancellationToken requestAborted) |
| | | 41 | | where T : class |
| | | 42 | | { |
| | 0 | 43 | | using var wait = CancellationTokenSource.CreateLinkedTokenSource(requestAborted); |
| | 0 | 44 | | wait.CancelAfter(TimeSpan.FromSeconds(Math.Max(5, _config.WebWaitSeconds))); |
| | | 45 | | try |
| | | 46 | | { |
| | 0 | 47 | | return ToResponse(await call(wait.Token)); |
| | | 48 | | } |
| | 0 | 49 | | catch (OperationCanceledException) when (!requestAborted.IsCancellationRequested) |
| | | 50 | | { |
| | 0 | 51 | | return StatusCode(StatusCodes.Status202Accepted, new { pending = true }); |
| | | 52 | | } |
| | 0 | 53 | | } |
| | | 54 | | |
| | | 55 | | private IActionResult ToResponse<T>(InterpartsEngineResult<T> result) where T : class |
| | | 56 | | { |
| | 0 | 57 | | if (result.IsOk) return Json(result.Value); |
| | | 58 | | |
| | 0 | 59 | | var status = result.Status switch |
| | 0 | 60 | | { |
| | 0 | 61 | | InterpartsEngineStatus.NotFound => StatusCodes.Status404NotFound, |
| | 0 | 62 | | InterpartsEngineStatus.Expired => StatusCodes.Status410Gone, |
| | 0 | 63 | | _ when result.ErrorCode == "BAD_REQUEST" => StatusCodes.Status400BadRequest, |
| | 0 | 64 | | _ when result.ErrorCode == "TIMEOUT" => StatusCodes.Status504GatewayTimeout, |
| | 0 | 65 | | _ when result.Retry => StatusCodes.Status503ServiceUnavailable, |
| | 0 | 66 | | _ => StatusCodes.Status502BadGateway |
| | 0 | 67 | | }; |
| | 0 | 68 | | return StatusCode(status, new { code = result.ErrorCode, message = result.Message, retry = result.Retry }); |
| | | 69 | | } |
| | | 70 | | } |