From 12ecc97b07093f3cac6567c70ff75d57b429c674 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Tue, 21 Oct 2025 17:38:01 +0200 Subject: Implement new Santiago region (WIP) --- .../GetStopEstimates.cs | 127 --------------------- .../SantiagoController.cs | 81 +++++++++++++ src/Costasdev.Busurbano.Backend/VigoController.cs | 127 +++++++++++++++++++++ 3 files changed, 208 insertions(+), 127 deletions(-) delete mode 100644 src/Costasdev.Busurbano.Backend/GetStopEstimates.cs create mode 100644 src/Costasdev.Busurbano.Backend/SantiagoController.cs create mode 100644 src/Costasdev.Busurbano.Backend/VigoController.cs (limited to 'src/Costasdev.Busurbano.Backend') diff --git a/src/Costasdev.Busurbano.Backend/GetStopEstimates.cs b/src/Costasdev.Busurbano.Backend/GetStopEstimates.cs deleted file mode 100644 index 8fcdd8e..0000000 --- a/src/Costasdev.Busurbano.Backend/GetStopEstimates.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Memory; -using Costasdev.VigoTransitApi; -using System.Text.Json; - -namespace Costasdev.Busurbano.Backend; - -[ApiController] -[Route("api")] -public class ApiController : ControllerBase -{ - private readonly VigoTransitApiClient _api; - private readonly IMemoryCache _cache; - private readonly HttpClient _httpClient; - - public ApiController(HttpClient http, IMemoryCache cache) - { - _api = new VigoTransitApiClient(http); - _cache = cache; - _httpClient = http; - } - - [HttpGet("GetStopEstimates")] - public async Task Run() - { - var argumentAvailable = Request.Query.TryGetValue("id", out var requestedStopIdString); - if (!argumentAvailable) - { - return BadRequest("Please provide a stop id as a query parameter with the name 'id'."); - } - - var argumentNumber = int.TryParse(requestedStopIdString, out var requestedStopId); - if (!argumentNumber) - { - return BadRequest("The provided stop id is not a valid number."); - } - - try - { - var estimates = await _api.GetStopEstimates(requestedStopId); - return new OkObjectResult(estimates); - } - catch (InvalidOperationException) - { - return new BadRequestObjectResult("Stop not found"); - } - } - - [HttpGet("GetStopTimetable")] - public async Task GetStopTimetable() - { - // Get date parameter (default to today if not provided) - var dateString = Request.Query.TryGetValue("date", out var requestedDate) - ? requestedDate.ToString() - : DateTime.Today.ToString("yyyy-MM-dd"); - - // Validate date format - if (!DateTime.TryParseExact(dateString, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out var parsedDate)) - { - return BadRequest("Invalid date format. Please use yyyy-MM-dd format."); - } - - // Get stopId parameter - if (!Request.Query.TryGetValue("stopId", out var requestedStopIdString)) - { - return BadRequest("Please provide a stop id as a query parameter with the name 'stopId'."); - } - - if (!int.TryParse(requestedStopIdString, out var requestedStopId)) - { - return BadRequest("The provided stop id is not a valid number."); - } - - // Create cache key - var cacheKey = $"timetable_{dateString}_{requestedStopId}"; - - // Try to get from cache first - if (_cache.TryGetValue(cacheKey, out var cachedData)) - { - return new OkObjectResult(cachedData); - } - - try - { - // Fetch data from external API - var url = $"https://costas.dev/static-storage/vitrasa_svc/stops/{dateString}/{requestedStopId}.json"; - var response = await _httpClient.GetAsync(url); - - if (!response.IsSuccessStatusCode) - { - if (response.StatusCode == System.Net.HttpStatusCode.NotFound) - { - return NotFound($"Timetable data not found for stop {requestedStopId} on {dateString}"); - } - return StatusCode((int)response.StatusCode, "Error fetching timetable data"); - } - - var jsonContent = await response.Content.ReadAsStringAsync(); - var timetableData = JsonSerializer.Deserialize(jsonContent); - - // Cache the data for 12 hours - var cacheOptions = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12), - SlidingExpiration = TimeSpan.FromHours(6), // Refresh cache if accessed within 6 hours of expiry - Priority = CacheItemPriority.Normal - }; - - _cache.Set(cacheKey, timetableData, cacheOptions); - - return new OkObjectResult(timetableData); - } - catch (HttpRequestException ex) - { - return StatusCode(500, $"Error fetching timetable data: {ex.Message}"); - } - catch (JsonException ex) - { - return StatusCode(500, $"Error parsing timetable data: {ex.Message}"); - } - catch (Exception ex) - { - return StatusCode(500, $"Unexpected error: {ex.Message}"); - } - } -} - diff --git a/src/Costasdev.Busurbano.Backend/SantiagoController.cs b/src/Costasdev.Busurbano.Backend/SantiagoController.cs new file mode 100644 index 0000000..51a76c6 --- /dev/null +++ b/src/Costasdev.Busurbano.Backend/SantiagoController.cs @@ -0,0 +1,81 @@ +using System.Text.Json; +using Costasdev.VigoTransitApi.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; + +namespace Costasdev.Busurbano.Backend; + +[ApiController] +[Route("api/santiago")] +public class SantiagoController : ControllerBase +{ + private readonly IMemoryCache _cache; + private readonly HttpClient _httpClient; + + public SantiagoController(HttpClient http, IMemoryCache cache) + { + _cache = cache; + _httpClient = http; + } + + [HttpGet("GetStopEstimates")] + public async Task Run() + { + var argumentAvailable = Request.Query.TryGetValue("id", out var requestedStopIdString); + if (!argumentAvailable) + { + return BadRequest("Please provide a stop id as a query parameter with the name 'id'."); + } + + var argumentNumber = int.TryParse(requestedStopIdString, out var requestedStopId); + if (!argumentNumber) + { + return BadRequest("The provided stop id is not a valid number."); + } + + try + { + var obj = await _httpClient.GetFromJsonAsync( + $"https://app.tussa.org/tussa/api/paradas/{requestedStopId}"); + + if (obj is null) + { + return BadRequest("No response returned from the API, or whatever"); + } + + var root = obj.RootElement; + + List estimates = root + .GetProperty("lineas") + .EnumerateArray() + .Select(el => new StopEstimate( + el.GetProperty("sinoptico").GetString() ?? string.Empty, + el.GetProperty("nombre").GetString() ?? string.Empty, + el.GetProperty("minutosProximoPaso").GetInt32(), + 0 + )).ToList(); + + return new OkObjectResult(new StopEstimateResponse + { + Stop = new StopEstimateResponse.StopInfo + { + Name = root.GetProperty("nombre").GetString() ?? string.Empty, + Id = root.GetProperty("id").GetInt32(), + Latitude = root.GetProperty("coordenadas").GetProperty("latitud").GetDecimal(), + Longitude = root.GetProperty("coordenadas").GetProperty("longitud").GetDecimal() + }, + Estimates = estimates + }); + } + catch (InvalidOperationException) + { + return new BadRequestObjectResult("Stop not found"); + } + } + + [HttpGet("GetStopTimetable")] + public async Task GetStopTimetable() + { + throw new NotImplementedException(); + } +} diff --git a/src/Costasdev.Busurbano.Backend/VigoController.cs b/src/Costasdev.Busurbano.Backend/VigoController.cs new file mode 100644 index 0000000..41a8765 --- /dev/null +++ b/src/Costasdev.Busurbano.Backend/VigoController.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; +using Costasdev.VigoTransitApi; +using System.Text.Json; + +namespace Costasdev.Busurbano.Backend; + +[ApiController] +[Route("api/vigo")] +public class VigoController : ControllerBase +{ + private readonly VigoTransitApiClient _api; + private readonly IMemoryCache _cache; + private readonly HttpClient _httpClient; + + public VigoController(HttpClient http, IMemoryCache cache) + { + _api = new VigoTransitApiClient(http); + _cache = cache; + _httpClient = http; + } + + [HttpGet("GetStopEstimates")] + public async Task Run() + { + var argumentAvailable = Request.Query.TryGetValue("id", out var requestedStopIdString); + if (!argumentAvailable) + { + return BadRequest("Please provide a stop id as a query parameter with the name 'id'."); + } + + var argumentNumber = int.TryParse(requestedStopIdString, out var requestedStopId); + if (!argumentNumber) + { + return BadRequest("The provided stop id is not a valid number."); + } + + try + { + var estimates = await _api.GetStopEstimates(requestedStopId); + return new OkObjectResult(estimates); + } + catch (InvalidOperationException) + { + return new BadRequestObjectResult("Stop not found"); + } + } + + [HttpGet("GetStopTimetable")] + public async Task GetStopTimetable() + { + // Get date parameter (default to today if not provided) + var dateString = Request.Query.TryGetValue("date", out var requestedDate) + ? requestedDate.ToString() + : DateTime.Today.ToString("yyyy-MM-dd"); + + // Validate date format + if (!DateTime.TryParseExact(dateString, "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out var parsedDate)) + { + return BadRequest("Invalid date format. Please use yyyy-MM-dd format."); + } + + // Get stopId parameter + if (!Request.Query.TryGetValue("stopId", out var requestedStopIdString)) + { + return BadRequest("Please provide a stop id as a query parameter with the name 'stopId'."); + } + + if (!int.TryParse(requestedStopIdString, out var requestedStopId)) + { + return BadRequest("The provided stop id is not a valid number."); + } + + // Create cache key + var cacheKey = $"timetable_{dateString}_{requestedStopId}"; + + // Try to get from cache first + if (_cache.TryGetValue(cacheKey, out var cachedData)) + { + return new OkObjectResult(cachedData); + } + + try + { + // Fetch data from external API + var url = $"https://costas.dev/static-storage/vitrasa_svc/stops/{dateString}/{requestedStopId}.json"; + var response = await _httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return NotFound($"Timetable data not found for stop {requestedStopId} on {dateString}"); + } + return StatusCode((int)response.StatusCode, "Error fetching timetable data"); + } + + var jsonContent = await response.Content.ReadAsStringAsync(); + var timetableData = JsonSerializer.Deserialize(jsonContent); + + // Cache the data for 12 hours + var cacheOptions = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12), + SlidingExpiration = TimeSpan.FromHours(6), // Refresh cache if accessed within 6 hours of expiry + Priority = CacheItemPriority.Normal + }; + + _cache.Set(cacheKey, timetableData, cacheOptions); + + return new OkObjectResult(timetableData); + } + catch (HttpRequestException ex) + { + return StatusCode(500, $"Error fetching timetable data: {ex.Message}"); + } + catch (JsonException ex) + { + return StatusCode(500, $"Error parsing timetable data: {ex.Message}"); + } + catch (Exception ex) + { + return StatusCode(500, $"Unexpected error: {ex.Message}"); + } + } +} + -- cgit v1.3