aboutsummaryrefslogtreecommitdiff
path: root/src/Enmarcha.Backend/Controllers/TransitController.cs
blob: 62b37252afd2cdd45dfe29b8bca7fa929587b0df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
using Enmarcha.Sources.OpenTripPlannerGql;
using Enmarcha.Sources.OpenTripPlannerGql.Queries;
using Enmarcha.Backend.Configuration;
using Enmarcha.Backend.Helpers;
using Enmarcha.Backend.Services;
using Enmarcha.Backend.Types.Transit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

namespace Enmarcha.Backend.Controllers;

[ApiController]
[Route("api/transit")]
public class TransitController : ControllerBase
{
    private readonly ILogger<TransitController> _logger;
    private readonly OtpService _otpService;
    private readonly AppConfiguration _config;
    private readonly HttpClient _httpClient;
    private readonly IMemoryCache _cache;

    public TransitController(
        ILogger<TransitController> logger,
        OtpService otpService,
        IOptions<AppConfiguration> config,
        HttpClient httpClient,
        IMemoryCache cache
    )
    {
        _logger = logger;
        _otpService = otpService;
        _config = config.Value;
        _httpClient = httpClient;
        _cache = cache;
    }

    [HttpGet("routes")]
    public async Task<ActionResult<List<RouteDto>>> GetRoutes([FromQuery] string[] feeds)
    {
        if (feeds.Length == 0)
        {
            feeds = ["tussa", "vitrasa", "tranvias", "feve"];
        }

        var serviceDate = DateTime.Now.ToString("yyyy-MM-dd");
        var cacheKey = $"routes_{string.Join("_", feeds)}_{serviceDate}";
        if (_cache.TryGetValue(cacheKey, out List<RouteDto>? cachedRoutes))
        {
            return Ok(cachedRoutes);
        }

        try
        {
            var query = RoutesListContent.Query(new RoutesListContent.Args(feeds, serviceDate));
            var response = await SendOtpQueryAsync<RoutesListResponse>(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<string?>.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<ActionResult<RouteDetailsDto>> 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<RouteDetailsResponse>(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<GraphClientResponse<T>?> SendOtpQueryAsync<T>(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<GraphClientResponse<T>>();
    }
}