11import { Injectable , InternalServerErrorException } from '@nestjs/common' ;
22import { ConfigService } from '@nestjs/config' ;
3- import axios , { AxiosError , AxiosResponse } from 'axios' ;
3+ import axios , { AxiosResponse } from 'axios' ;
44import { decode } from 'html-entities' ;
55
6- import { DepartureQueryDto } from '../types/client.types' ;
7- import { ApiResponse , Departure , FutarAPI } from '../types/departure.types' ;
6+ import { DepartureQueryDto , MapQueryDto , RouteQueryDto } from '../types/client.types' ;
7+ import {
8+ ApiResponse as DepartureApiResponse ,
9+ Departure ,
10+ FutarAPI as DeparturesFutarAPI ,
11+ } from '../types/departure.types' ;
12+ import {
13+ ApiResponse as MapApiResponse ,
14+ DeparturesData ,
15+ FutarAPI as MapFutarAPI ,
16+ RoutesData ,
17+ VehiclesData ,
18+ } from '../types/map.types' ;
819import { ProxyQueryDto } from '../types/proxy.types' ;
920import { ConfigKeys } from '../utils/configuration' ;
1021
1122@Injectable ( )
1223export class ClientService {
13- private readonly apiUrl : URL ;
24+ private readonly departuresApiUrl : URL ;
25+ private readonly vehiclesApiUrl : URL ;
26+ private readonly routesApiUrl : URL ;
1427
1528 constructor ( private configService : ConfigService ) {
16- this . apiUrl = new URL ( 'https://go.bkk.hu/api/query/v1/ws/otp/api/where/arrivals-and-departures-for-location.json' ) ;
17- this . apiUrl . searchParams . append ( 'minutesBefore' , '0' ) ;
18- this . apiUrl . searchParams . append ( 'limit' , '30' ) ;
19- this . apiUrl . searchParams . append ( 'groupLimit' , '1' ) ;
20- this . apiUrl . searchParams . append ( 'onlyDepartures' , 'true' ) ;
21- this . apiUrl . searchParams . append ( 'key' , configService . get < string > ( ConfigKeys . FUTAR_API_KEY ) ) ;
29+ const apiKey = configService . get < string > ( ConfigKeys . FUTAR_API_KEY ) ;
30+
31+ this . departuresApiUrl = new URL (
32+ 'https://go.bkk.hu/api/query/v1/ws/otp/api/where/arrivals-and-departures-for-location.json'
33+ ) ;
34+ this . departuresApiUrl . searchParams . append ( 'minutesBefore' , '0' ) ;
35+ this . departuresApiUrl . searchParams . append ( 'limit' , '30' ) ;
36+ this . departuresApiUrl . searchParams . append ( 'groupLimit' , '1' ) ;
37+ this . departuresApiUrl . searchParams . append ( 'onlyDepartures' , 'true' ) ;
38+ this . departuresApiUrl . searchParams . append ( 'key' , apiKey ) ;
39+
40+ this . vehiclesApiUrl = new URL ( 'https://go.bkk.hu/api/query/v1/ws/otp/api/where/vehicles-for-location.json' ) ;
41+ this . vehiclesApiUrl . searchParams . append ( 'includeReferences' , 'false' ) ;
42+ this . vehiclesApiUrl . searchParams . append ( 'key' , apiKey ) ;
43+
44+ this . routesApiUrl = new URL ( 'https://go.bkk.hu/api/query/v1/ws/otp/api/where/multi-route-details.json' ) ;
45+ this . routesApiUrl . searchParams . append ( 'includeReferences' , 'false' ) ;
46+ this . routesApiUrl . searchParams . append ( 'key' , apiKey ) ;
2247 }
2348
24- getUrl ( { lat, lon, radius } : DepartureQueryDto ) {
25- const url = new URL ( this . apiUrl ) ;
49+ getDeparturesUrl ( { lat, lon, radius } : DepartureQueryDto ) : URL {
50+ const url = new URL ( this . departuresApiUrl . toString ( ) ) ;
2651 url . searchParams . append ( 'radius' , radius . toString ( ) ) ;
2752 url . searchParams . append ( 'lon' , lon ) ;
2853 url . searchParams . append ( 'lat' , lat ) ;
@@ -31,50 +56,126 @@ export class ClientService {
3156 return url ;
3257 }
3358
34- async getDepartures ( query : DepartureQueryDto ) {
35- const url = this . getUrl ( query ) . toString ( ) ;
36- let response : AxiosResponse < FutarAPI > ;
59+ getVehiclesUrl ( { lat, lon, radius } : MapQueryDto ) : URL {
60+ const url = new URL ( this . vehiclesApiUrl . toString ( ) ) ;
61+ url . searchParams . append ( 'radius' , radius . toString ( ) ) ;
62+ url . searchParams . append ( 'lon' , lon ) ;
63+ url . searchParams . append ( 'lat' , lat ) ;
64+ return url ;
65+ }
66+
67+ getRoutesUrl ( { routeId } : RouteQueryDto ) : URL {
68+ const url = new URL ( this . routesApiUrl . toString ( ) ) ;
69+ routeId . forEach ( ( id ) => url . searchParams . append ( 'routeId' , id ) ) ;
70+ return url ;
71+ }
72+
73+ async getDepartures ( query : DepartureQueryDto ) : Promise < DepartureApiResponse > {
74+ const url = this . getDeparturesUrl ( query ) . toString ( ) ;
75+ let response : AxiosResponse < DeparturesFutarAPI > ;
76+
3777 try {
38- response = await axios . get < FutarAPI > ( url ) ;
39- } catch ( e ) {
40- throw new AxiosError ( ) ;
78+ response = await axios . get < DeparturesFutarAPI > ( url ) ;
79+ } catch ( error ) {
80+ throw new InternalServerErrorException ( 'Failed to fetch departures.' ) ;
4181 }
4282
43- const apiResponse : ApiResponse = { departures : [ ] } ;
83+ const { list, references } = response . data . data ;
84+ const departures : Departure [ ] = [ ] ;
4485
4586 try {
46- const { list, references } = response . data . data ;
47-
4887 list ?. forEach ( ( { stopTimes, headsign, routeId } ) => {
4988 stopTimes ?. forEach ( ( { departureTime, predictedDepartureTime, alertIds, stopId } ) => {
89+ const route = references . routes [ routeId ] ;
5090 const departure : Departure = {
51- type : references . routes [ routeId ] . type ,
52- style : references . routes [ routeId ] . style ,
53- headsign : headsign ,
91+ type : route . type ,
92+ style : route . style ,
93+ headsign,
5494 scheduled : departureTime ,
55- predicted : predictedDepartureTime || departureTime ,
56- alert : alertIds ?. map ( ( id ) =>
57- decode (
58- references . alerts [ id ] . description . translations . hu . replace ( / ( \n ) + / g, ' ' ) . replace ( / < \/ ? [ ^ > ] + ( > | $ ) / g, '' )
59- )
60- ) ,
61- isDelayed : ( predictedDepartureTime || departureTime ) - departureTime > 180 ,
95+ predicted : predictedDepartureTime ?? departureTime ,
96+ alert : alertIds ?. map ( ( id ) => {
97+ const rawDescription = references . alerts [ id ] . description . translations . hu ;
98+ return decode ( rawDescription . replace ( / ( \n ) + / g, ' ' ) . replace ( / < \/ ? [ ^ > ] + ( > | $ ) / g, '' ) ) ;
99+ } ) ,
100+ isDelayed : ( predictedDepartureTime ?? departureTime ) - departureTime > 180 ,
62101 departureText : '' ,
63- stopId : stopId ,
102+ stopId,
64103 } ;
65- const minutes = Math . floor ( ( departure . predicted * 1000 - Date . now ( ) ) / 60000 ) ;
66104
105+ const minutes = Math . floor ( ( departure . predicted * 1000 - Date . now ( ) ) / 60000 ) ;
67106 departure . departureText = minutes < 1 ? 'azonnal indul' : `${ minutes } perc` ;
68- apiResponse . departures . push ( departure ) ;
107+ departures . push ( departure ) ;
108+ } ) ;
109+ } ) ;
110+ } catch ( error ) {
111+ throw new InternalServerErrorException ( 'Error processing departures data.' ) ;
112+ }
113+
114+ return { departures } ;
115+ }
116+
117+ async getMap ( query : MapQueryDto ) : Promise < MapApiResponse > {
118+ // Adjust departures API to include data from 30 minutes before
119+ const departuresUrl = this . getDeparturesUrl ( query ) ;
120+ departuresUrl . searchParams . set ( 'minutesBefore' , '30' ) ;
121+ const vehiclesUrl = this . getVehiclesUrl ( query ) . toString ( ) ;
122+
123+ let departuresResponse : AxiosResponse < MapFutarAPI < DeparturesData > > ;
124+ let vehiclesResponse : AxiosResponse < MapFutarAPI < VehiclesData > > ;
125+ try {
126+ [ departuresResponse , vehiclesResponse ] = await Promise . all ( [
127+ axios . get < MapFutarAPI < DeparturesData > > ( departuresUrl . toString ( ) ) ,
128+ axios . get < MapFutarAPI < VehiclesData > > ( vehiclesUrl ) ,
129+ ] ) ;
130+ } catch ( error ) {
131+ throw new InternalServerErrorException ( 'Failed to fetch map data.' ) ;
132+ }
133+
134+ const mapApiResponse : MapApiResponse = { routes : [ ] , stops : [ ] , vehicles : [ ] } ;
135+
136+ try {
137+ const departuresData = departuresResponse . data . data ;
138+ const vehiclesData = vehiclesResponse . data . data ;
139+ const { list : departuresList , references : departuresReferences } = departuresData ;
140+ const { list : vehiclesList } = vehiclesData ;
141+
142+ const routeIds = new Set < string > ( ) ;
143+ departuresList ?. forEach ( ( { stopTimes, routeId } ) => {
144+ routeIds . add ( routeId ) ;
145+ stopTimes ?. forEach ( ( { stopId } ) => {
146+ const stop = departuresReferences . stops [ stopId ] ;
147+ if ( stop ) {
148+ mapApiResponse . stops . push ( stop ) ;
149+ }
69150 } ) ;
70151 } ) ;
71- } catch ( e ) {
72- throw new InternalServerErrorException ( ) ;
152+
153+ mapApiResponse . vehicles = vehiclesList || [ ] ;
154+
155+ const routesUrl = this . getRoutesUrl ( { routeId : Array . from ( routeIds ) } ) . toString ( ) ;
156+ let routesResponse : AxiosResponse < MapFutarAPI < RoutesData > > ;
157+ try {
158+ routesResponse = await axios . get < MapFutarAPI < RoutesData > > ( routesUrl ) ;
159+ const { list : routesList } = routesResponse . data . data ;
160+ if ( routesList ) {
161+ mapApiResponse . routes = routesList ;
162+ }
163+ } catch ( error ) {
164+ throw new InternalServerErrorException ( 'Failed to fetch route details.' ) ;
165+ }
166+ } catch ( error ) {
167+ throw new InternalServerErrorException ( 'Error processing map data.' ) ;
73168 }
74- return apiResponse ;
169+
170+ return mapApiResponse ;
75171 }
76172
77- async getResource ( proxyQueryDto : ProxyQueryDto ) {
78- return axios . get ( proxyQueryDto . url ) . then ( ( response ) => response . data ) ;
173+ async getResource ( proxyQueryDto : ProxyQueryDto ) : Promise < any > {
174+ try {
175+ const response = await axios . get ( proxyQueryDto . url ) ;
176+ return response . data ;
177+ } catch ( error ) {
178+ throw new InternalServerErrorException ( 'Failed to fetch resource.' ) ;
179+ }
79180 }
80181}
0 commit comments