From 48ec0aae80a200d7eb50639ff4c4ca8ae564f29b Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Sun, 28 Dec 2025 22:24:26 +0100 Subject: Implement displaying routes with dynamic data from OTP --- .../Controllers/TransitController.cs | 127 +++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/Costasdev.Busurbano.Backend/Controllers/TransitController.cs (limited to 'src/Costasdev.Busurbano.Backend/Controllers/TransitController.cs') diff --git a/src/Costasdev.Busurbano.Backend/Controllers/TransitController.cs b/src/Costasdev.Busurbano.Backend/Controllers/TransitController.cs new file mode 100644 index 0000000..b519ea7 --- /dev/null +++ b/src/Costasdev.Busurbano.Backend/Controllers/TransitController.cs @@ -0,0 +1,127 @@ +using Costasdev.Busurbano.Backend.Configuration; +using Costasdev.Busurbano.Backend.Helpers; +using Costasdev.Busurbano.Backend.Services; +using Costasdev.Busurbano.Backend.Types.Transit; +using Costasdev.Busurbano.Sources.OpenTripPlannerGql; +using Costasdev.Busurbano.Sources.OpenTripPlannerGql.Queries; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace Costasdev.Busurbano.Backend.Controllers; + +[ApiController] +[Route("api/transit")] +public class TransitController : ControllerBase +{ + private readonly ILogger _logger; + private readonly OtpService _otpService; + private readonly AppConfiguration _config; + private readonly HttpClient _httpClient; + private readonly IMemoryCache _cache; + + public TransitController( + ILogger logger, + OtpService otpService, + IOptions config, + HttpClient httpClient, + IMemoryCache cache + ) + { + _logger = logger; + _otpService = otpService; + _config = config.Value; + _httpClient = httpClient; + _cache = cache; + } + + [HttpGet("routes")] + public async Task>> GetRoutes([FromQuery] string[] feeds) + { + if (feeds.Length == 0) + { + feeds = ["santiago", "vitrasa", "coruna", "feve"]; + } + + var serviceDate = DateTime.Now.ToString("yyyy-MM-dd"); + var cacheKey = $"routes_{string.Join("_", feeds)}_{serviceDate}"; + if (_cache.TryGetValue(cacheKey, out List? cachedRoutes)) + { + return Ok(cachedRoutes); + } + + try + { + var query = RoutesListContent.Query(new RoutesListContent.Args(feeds, serviceDate)); + var response = await SendOtpQueryAsync(query); + + if (response?.Data == null) + { + return StatusCode(500, "Failed to fetch routes from OTP."); + } + + var routes = response.Data.Routes + .Select(_otpService.MapRoute) + .Where(r => r.TripCount > 0) + .OrderBy(r => r.ShortName, Comparer.Create(SortingHelper.SortRouteShortNames)) + .ToList(); + + _cache.Set(cacheKey, routes, TimeSpan.FromHours(1)); + + return Ok(routes); + } + catch (Exception e) + { + _logger.LogError(e, "Error fetching routes"); + return StatusCode(500, "An error occurred while fetching routes."); + } + } + + [HttpGet("routes/{id}")] + public async Task> GetRouteDetails(string id) + { + var serviceDate = DateTime.Now.ToString("yyyy-MM-dd"); + var cacheKey = $"route_details_{id}_{serviceDate}"; + + if (_cache.TryGetValue(cacheKey, out RouteDetailsDto? cachedDetails)) + { + return Ok(cachedDetails); + } + + try + { + var query = RouteDetailsContent.Query(new RouteDetailsContent.Args(id, serviceDate)); + var response = await SendOtpQueryAsync(query); + + if (response?.Data?.Route == null) + { + return NotFound(); + } + + var details = _otpService.MapRouteDetails(response.Data.Route); + _cache.Set(cacheKey, details, TimeSpan.FromHours(1)); + + return Ok(details); + } + catch (Exception e) + { + _logger.LogError(e, "Error fetching route details for {Id}", id); + return StatusCode(500, "An error occurred while fetching route details."); + } + } + + private async Task?> SendOtpQueryAsync(string query) where T : AbstractGraphResponse + { + var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1"); + request.Content = JsonContent.Create(new GraphClientRequest { Query = query }); + + var response = await _httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + _logger.LogError("OTP query failed with status {StatusCode}", response.StatusCode); + return null; + } + + return await response.Content.ReadFromJsonAsync>(); + } +} -- cgit v1.3