aboutsummaryrefslogtreecommitdiff
path: root/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-12-29 00:41:52 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2025-12-29 00:41:52 +0100
commita304c24b32c0327436bbd8c2853e60668e161b42 (patch)
tree08f65c05daca134cf4d2e4f779bd15d98fd66370 /src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
parent120a3c6bddd0fb8d9fa05df4763596956554c025 (diff)
Rename a lot of stuff, add Santiago real time
Diffstat (limited to 'src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs')
-rw-r--r--src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs249
1 files changed, 0 insertions, 249 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
deleted file mode 100644
index a0e8505..0000000
--- a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
+++ /dev/null
@@ -1,249 +0,0 @@
-using System.Net;
-using Costasdev.Busurbano.Backend.Configuration;
-using Costasdev.Busurbano.Backend.Helpers;
-using Costasdev.Busurbano.Backend.Services;
-using Costasdev.Busurbano.Backend.Types;
-using Costasdev.Busurbano.Backend.Types.Arrivals;
-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/stops")]
-public partial class ArrivalsController : ControllerBase
-{
- private readonly ILogger<ArrivalsController> _logger;
- private readonly IMemoryCache _cache;
- private readonly HttpClient _httpClient;
- private readonly ArrivalsPipeline _pipeline;
- private readonly FeedService _feedService;
- private readonly AppConfiguration _config;
-
- public ArrivalsController(
- ILogger<ArrivalsController> logger,
- IMemoryCache cache,
- HttpClient httpClient,
- ArrivalsPipeline pipeline,
- FeedService feedService,
- IOptions<AppConfiguration> configOptions
- )
- {
- _logger = logger;
- _cache = cache;
- _httpClient = httpClient;
- _pipeline = pipeline;
- _feedService = feedService;
- _config = configOptions.Value;
- }
-
- [HttpGet("arrivals")]
- public async Task<IActionResult> GetArrivals(
- [FromQuery] string id,
- [FromQuery] bool reduced
- )
- {
- var tz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid");
- var nowLocal = TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz);
- var todayLocal = nowLocal.Date;
-
- var requestContent = ArrivalsAtStopContent.Query(
- new ArrivalsAtStopContent.Args(id, reduced)
- );
-
- var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1");
- request.Content = JsonContent.Create(new GraphClientRequest
- {
- Query = requestContent
- });
-
- var response = await _httpClient.SendAsync(request);
- var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<ArrivalsAtStopResponse>>();
-
- if (responseBody is not { IsSuccess: true } || responseBody.Data?.Stop == null)
- {
- LogErrorFetchingStopData(response.StatusCode, await response.Content.ReadAsStringAsync());
- return StatusCode(500, "Error fetching stop data");
- }
-
- var stop = responseBody.Data.Stop;
- _logger.LogInformation("Fetched {Count} arrivals for stop {StopName} ({StopId})", stop.Arrivals.Count, stop.Name, id);
-
- List<Arrival> arrivals = [];
- foreach (var item in stop.Arrivals)
- {
- // Discard trip without pickup at stop
- if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None))
- {
- continue;
- }
-
- // Discard on last stop
- if (item.Trip.ArrivalStoptime.Stop.GtfsId == id)
- {
- continue;
- }
-
- if (item.Trip.Geometry?.Points != null)
- {
- _logger.LogDebug("Trip {TripId} has geometry", item.Trip.GtfsId);
- }
-
- // Calculate departure time using the service day in the feed's timezone (Europe/Madrid)
- // This ensures we treat ScheduledDepartureSeconds as relative to the local midnight of the service day
- var serviceDayLocal = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeSeconds(item.ServiceDay), tz);
- var departureTime = serviceDayLocal.Date.AddSeconds(item.ScheduledDepartureSeconds);
-
- var minutesToArrive = (int)(departureTime - nowLocal).TotalMinutes;
- //var isRunning = departureTime < nowLocal;
-
- Arrival arrival = new()
- {
- TripId = item.Trip.GtfsId,
- Route = new RouteInfo
- {
- GtfsId = item.Trip.Route.GtfsId,
- ShortName = item.Trip.RouteShortName,
- Colour = item.Trip.Route.Color ?? "FFFFFF",
- TextColour = item.Trip.Route.TextColor ?? "000000"
- },
- Headsign = new HeadsignInfo
- {
- Destination = item.Headsign
- },
- Estimate = new ArrivalDetails
- {
- Minutes = minutesToArrive,
- Precision = departureTime < nowLocal.AddMinutes(-1) ? ArrivalPrecision.Past : ArrivalPrecision.Scheduled
- },
- RawOtpTrip = item
- };
-
- arrivals.Add(arrival);
- }
-
- await _pipeline.ExecuteAsync(new ArrivalsContext
- {
- StopId = id,
- StopCode = stop.Code,
- IsReduced = reduced,
- Arrivals = arrivals,
- NowLocal = nowLocal,
- StopLocation = new Position { Latitude = stop.Lat, Longitude = stop.Lon }
- });
-
- var feedId = id.Split(':')[0];
-
- // Time after an arrival's time to still include it in the response. This is useful without real-time data, for delayed buses.
- var timeThreshold = GetThresholdForFeed(id);
-
- var (fallbackColor, fallbackTextColor) = _feedService.GetFallbackColourForFeed(feedId);
-
- return Ok(new StopArrivalsResponse
- {
- StopCode = _feedService.NormalizeStopCode(feedId, stop.Code),
- StopName = _feedService.NormalizeStopName(feedId, stop.Name),
- StopLocation = new Position
- {
- Latitude = stop.Lat,
- Longitude = stop.Lon
- },
- Routes = [.. stop.Routes
- .OrderBy(
- r => r.ShortName,
- Comparer<string?>.Create(SortingHelper.SortRouteShortNames)
- )
- .Select(r => new RouteInfo
- {
- GtfsId = r.GtfsId,
- ShortName = _feedService.NormalizeRouteShortName(feedId, r.ShortName ?? ""),
- Colour = r.Color ?? fallbackColor,
- TextColour = r.TextColor is null or "000000" ?
- ContrastHelper.GetBestTextColour(r.Color ?? fallbackColor) :
- r.TextColor
- })],
- Arrivals = [.. arrivals.Where(a => a.Estimate.Minutes >= timeThreshold)]
- });
-
- }
-
- private static int GetThresholdForFeed(string id)
- {
- string feedId = id.Split(':', 2)[0];
-
- if (feedId is "vitrasa" or "coruna")
- {
- return 0;
- }
-
- return -30;
- }
-
- [HttpGet]
- public async Task<IActionResult> GetStops([FromQuery] string ids)
- {
- if (string.IsNullOrWhiteSpace(ids))
- {
- return BadRequest("Ids parameter is required");
- }
-
- var stopIds = ids.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
- var requestContent = StopsInfoContent.Query(new StopsInfoContent.Args(stopIds));
-
- var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1");
- request.Content = JsonContent.Create(new GraphClientRequest
- {
- Query = requestContent
- });
-
- var response = await _httpClient.SendAsync(request);
- var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<StopsInfoResponse>>();
-
- if (responseBody is not { IsSuccess: true } || responseBody.Data?.Stops == null)
- {
- return StatusCode(500, "Error fetching stops data");
- }
-
- var result = responseBody.Data.Stops.ToDictionary(
- s => s.GtfsId,
- s =>
- {
- var feedId = s.GtfsId.Split(':', 2)[0];
- var (fallbackColor, _) = _feedService.GetFallbackColourForFeed(feedId);
-
- return new
- {
- id = s.GtfsId,
- code = _feedService.NormalizeStopCode(feedId, s.Code ?? ""),
- name = s.Name,
- routes = s.Routes
- .OrderBy(r => r.ShortName, Comparer<string?>.Create(SortingHelper.SortRouteShortNames))
- .Select(r => new
- {
- shortName = _feedService.NormalizeRouteShortName(feedId, r.ShortName ?? ""),
- colour = r.Color ?? fallbackColor,
- textColour = r.TextColor is null or "000000" ?
- ContrastHelper.GetBestTextColour(r.Color ?? fallbackColor) :
- r.TextColor
- })
- .ToList()
- };
- }
- );
-
- return Ok(result);
- }
-
- [HttpGet("search")]
- public IActionResult SearchStops([FromQuery] string q)
- {
- // Placeholder for future implementation with Postgres and fuzzy searching
- return Ok(new List<object>());
- }
-
- [LoggerMessage(LogLevel.Error, "Error fetching stop data, received {statusCode} {responseBody}")]
- partial void LogErrorFetchingStopData(HttpStatusCode statusCode, string responseBody);
-}