diff options
Diffstat (limited to 'lib/Travelynx/Controller/Api.pm')
-rwxr-xr-x | lib/Travelynx/Controller/Api.pm | 370 |
1 files changed, 234 insertions, 136 deletions
diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index 974b9ca..572d3fa 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -1,15 +1,17 @@ package Travelynx::Controller::Api; -# Copyright (C) 2020 Daniel Friesel +# Copyright (C) 2020-2023 Birte Kristina Friesel # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; use DateTime; use List::Util; -use Travel::Status::DE::IRIS::Stations; +use Mojo::JSON qw(encode_json); use UUID::Tiny qw(:std); +# Internal Helpers + sub make_token { return create_uuid_as_string(UUID_V4); } @@ -19,6 +21,9 @@ sub sanitize { if ( not defined $value ) { return undef; } + if ( not defined $type ) { + return $value ? ( '' . $value ) : undef; + } if ( $type eq '' ) { return '' . $value; } @@ -28,15 +33,29 @@ sub sanitize { return 0; } +# Contollers + sub documentation { my ($self) = @_; - $self->render('api_documentation'); + if ( $self->is_user_authenticated ) { + my $uid = $self->current_user->{id}; + $self->render( + 'api_documentation', + uid => $uid, + api_token => $self->users->get_api_token( uid => $uid ), + ); + } + else { + $self->render('api_documentation'); + } } sub get_v1 { my ($self) = @_; + $self->res->headers->access_control_allow_origin(q{*}); + my $api_action = $self->stash('user_action'); my $api_token = $self->stash('token'); if ( $api_action !~ qr{ ^ (?: status | history | action ) $ }x ) { @@ -67,8 +86,11 @@ sub get_v1 { return; } - my $token = $self->get_api_token($uid); - if ( $api_token ne $token->{$api_action} ) { + my $token = $self->users->get_api_token( uid => $uid ); + if ( not $api_token + or not $token->{$api_action} + or $api_token ne $token->{$api_action} ) + { $self->render( json => { error => 'Invalid token', @@ -77,7 +99,7 @@ sub get_v1 { return; } if ( $api_action eq 'status' ) { - $self->render( json => $self->get_user_status_json_v1($uid) ); + $self->render( json => $self->get_user_status_json_v1( uid => $uid ) ); } else { $self->render( @@ -100,6 +122,7 @@ sub travel_v1 { deprecated => \0, error => 'Malformed JSON', }, + status => 400, ); return; } @@ -113,6 +136,7 @@ sub travel_v1 { deprecated => \0, error => 'Malformed token', }, + status => 400, ); return; } @@ -126,11 +150,12 @@ sub travel_v1 { deprecated => \0, error => 'Malformed token', }, + status => 400, ); return; } - my $token = $self->get_api_token($uid); + my $token = $self->users->get_api_token( uid => $uid ); if ( not $token->{'travel'} or $api_token ne $token->{'travel'} ) { $self->render( json => { @@ -138,6 +163,7 @@ sub travel_v1 { deprecated => \0, error => 'Invalid token', }, + status => 400, ); return; } @@ -150,8 +176,9 @@ sub travel_v1 { success => \0, deprecated => \0, error => 'Missing or invalid action', - status => $self->get_user_status_json_v1($uid) + status => $self->get_user_status_json_v1( uid => $uid ) }, + status => 400, ); return; } @@ -160,12 +187,20 @@ sub travel_v1 { my $from_station = sanitize( q{}, $payload->{fromStation} ); my $to_station = sanitize( q{}, $payload->{toStation} ); my $train_id; + my $dbris = sanitize( undef, $payload->{dbris} ); + my $hafas = sanitize( undef, $payload->{hafas} ); + my $motis = sanitize( undef, $payload->{motis} ); + + if ( not $hafas and exists $payload->{train}{journeyID} ) { + $dbris //= 'bahn.de'; + } if ( not( $from_station - and ( ( $payload->{train}{type} and $payload->{train}{no} ) - or $payload->{train}{id} ) + and ( ( $payload->{train}{type} and $payload->{train}{no} ) + or $payload->{train}{id} + or $payload->{train}{journeyID} ) ) ) { @@ -174,131 +209,149 @@ sub travel_v1 { success => \0, deprecated => \0, error => 'Missing fromStation or train data', - status => $self->get_user_status_json_v1($uid) + status => $self->get_user_status_json_v1( uid => $uid ) }, + status => 400, ); return; } - if ( - @{ - [ - Travel::Status::DE::IRIS::Stations::get_station( - $from_station) - ] - } != 1 - ) + if ( not $hafas + and not $dbris + and not $self->stations->search( $from_station, backend_id => 1 ) ) { $self->render( json => { success => \0, deprecated => \0, - error => 'fromStation is ambiguous', - status => $self->get_user_status_json_v1($uid) + error => 'Unknown fromStation', + status => $self->get_user_status_json_v1( uid => $uid ) }, + status => 400, ); return; } - if ( - $to_station - and @{ - [ - Travel::Status::DE::IRIS::Stations::get_station( - $to_station) - ] - } != 1 - ) + if ( $to_station + and not $hafas + and not $dbris + and not $self->stations->search( $to_station, backend_id => 1 ) ) { $self->render( json => { success => \0, deprecated => \0, - error => 'toStation is ambiguous', - status => $self->get_user_status_json_v1($uid) + error => 'Unknown toStation', + status => $self->get_user_status_json_v1( uid => $uid ) }, + status => 400, ); return; } - if ( exists $payload->{train}{id} ) { - $train_id = sanitize( 0, $payload->{train}{id} ); + my $train_p; + + if ( exists $payload->{train}{journeyID} ) { + $train_p = Mojo::Promise->resolve( + sanitize( q{}, $payload->{train}{journeyID} ) ); + } + elsif ( exists $payload->{train}{id} ) { + $train_p + = Mojo::Promise->resolve( sanitize( 0, $payload->{train}{id} ) ); } else { my $train_type = sanitize( q{}, $payload->{train}{type} ); my $train_no = sanitize( q{}, $payload->{train}{no} ); - my $status = $self->iris->get_departures( + + $train_p = $self->iris->get_departures_p( station => $from_station, lookbehind => 140, lookahead => 40 + )->then( + sub { + my ($status) = @_; + if ( $status->{errstr} ) { + return Mojo::Promise->reject( + 'Error requesting departures from fromStation: ' + . $status->{errstr} ); + } + my ($train) = List::Util::first { + $_->type eq $train_type and $_->train_no eq $train_no + } + @{ $status->{results} }; + if ( not defined $train ) { + return Mojo::Promise->reject( + 'Train not found at fromStation'); + } + return Mojo::Promise->resolve( $train->train_id ); + } ); - if ( $status->{errstr} ) { + } + + $self->render_later; + + $train_p->then( + sub { + my ($train_id) = @_; + return $self->checkin_p( + station => $from_station, + train_id => $train_id, + uid => $uid, + hafas => $hafas, + dbris => $dbris, + motis => $motis, + ); + } + )->then( + sub { + my ($train) = @_; + if ( $payload->{comment} ) { + $self->in_transit->update_user_data( + uid => $uid, + user_data => + { comment => sanitize( q{}, $payload->{comment} ) } + ); + } + if ($to_station) { + + # the user may not have provided the correct to_station, so + # request related stations for checkout. + return $self->checkout_p( + station => $to_station, + force => 0, + uid => $uid, + with_related => 1, + ); + } + return Mojo::Promise->resolve; + } + )->then( + sub { + my ( undef, $error ) = @_; + if ($error) { + return Mojo::Promise->reject($error); + } $self->render( json => { - success => \0, - error => - 'Error requesting departures from fromStation: ' - . $status->{errstr}, - status => $self->get_user_status_json_v1($uid) + success => \1, + deprecated => \0, + status => $self->get_user_status_json_v1( uid => $uid ) } ); - return; } - my ($train) = List::Util::first { - $_->type eq $train_type and $_->train_no eq $train_no - } - @{ $status->{results} }; - if ( not defined $train ) { + )->catch( + sub { + my ($error) = @_; $self->render( json => { success => \0, deprecated => \0, - error => 'Train not found at fromStation', - status => $self->get_user_status_json_v1($uid) + error => 'Checkin/Checkout error: ' . $error, + status => $self->get_user_status_json_v1( uid => $uid ) } ); - return; } - $train_id = $train->train_id; - } - - my ( $train, $error ) = $self->checkin( - station => $from_station, - train_id => $train_id, - uid => $uid - ); - if ( $payload->{comment} and not $error ) { - $self->in_transit->update_user_data( - uid => $uid, - user_data => { comment => sanitize( q{}, $payload->{comment} ) } - ); - } - if ( $to_station and not $error ) { - ( $train, $error ) = $self->checkout( - station => $to_station, - force => 0, - uid => $uid - ); - } - if ($error) { - $self->render( - json => { - success => \0, - deprecated => \0, - error => 'Checkin/Checkout error: ' . $error, - status => $self->get_user_status_json_v1($uid) - } - ); - } - else { - $self->render( - json => { - success => \1, - deprecated => \0, - status => $self->get_user_status_json_v1($uid) - } - ); - } + )->wait; } elsif ( $payload->{action} eq 'checkout' ) { my $to_station = sanitize( q{}, $payload->{toStation} ); @@ -309,7 +362,7 @@ sub travel_v1 { success => \0, deprecated => \0, error => 'Missing toStation', - status => $self->get_user_status_json_v1($uid) + status => $self->get_user_status_json_v1( uid => $uid ) }, ); return; @@ -322,30 +375,43 @@ sub travel_v1 { ); } - my ( $train, $error ) = $self->checkout( - station => $to_station, - force => $payload->{force} ? 1 : 0, - uid => $uid - ); - if ($error) { - $self->render( - json => { - success => \0, - deprecated => \0, - error => 'Checkout error: ' . $error, - status => $self->get_user_status_json_v1($uid) + $self->render_later; + + # the user may not have provided the correct to_station, so + # request related stations for checkout. + $self->checkout_p( + station => $to_station, + force => $payload->{force} ? 1 : 0, + uid => $uid, + with_related => 1, + )->then( + sub { + my ( $train, $error ) = @_; + if ($error) { + return Mojo::Promise->reject($error); } - ); - } - else { - $self->render( - json => { - success => \1, - deprecated => \0, - status => $self->get_user_status_json_v1($uid) - } - ); - } + $self->render( + json => { + success => \1, + deprecated => \0, + status => $self->get_user_status_json_v1( uid => $uid ) + } + ); + return; + } + )->catch( + sub { + my ($err) = @_; + $self->render( + json => { + success => \0, + deprecated => \0, + error => 'Checkout error: ' . $err, + status => $self->get_user_status_json_v1( uid => $uid ) + } + ); + } + )->wait; } elsif ( $payload->{action} eq 'undo' ) { my $error = $self->undo( 'in_transit', $uid ); @@ -355,7 +421,7 @@ sub travel_v1 { success => \0, deprecated => \0, error => $error, - status => $self->get_user_status_json_v1($uid) + status => $self->get_user_status_json_v1( uid => $uid ) } ); } @@ -364,7 +430,7 @@ sub travel_v1 { json => { success => \1, deprecated => \0, - status => $self->get_user_status_json_v1($uid) + status => $self->get_user_status_json_v1( uid => $uid ) } ); } @@ -413,7 +479,7 @@ sub import_v1 { return; } - my $token = $self->get_api_token($uid); + my $token = $self->users->get_api_token( uid => $uid ); if ( not $token->{'import'} or $api_token ne $token->{'import'} ) { $self->render( json => { @@ -457,13 +523,13 @@ sub import_v1 { } %opt = ( - uid => $uid, - train_type => sanitize( q{}, $payload->{train}{type} ), - train_no => sanitize( q{}, $payload->{train}{no} ), - train_line => sanitize( q{}, $payload->{train}{line} ), - cancelled => $payload->{cancelled} ? 1 : 0, - dep_station => sanitize( q{}, $payload->{fromStation}{name} ), - arr_station => sanitize( q{}, $payload->{toStation}{name} ), + uid => $uid, + train_type => sanitize( q{}, $payload->{train}{type} ), + train_no => sanitize( q{}, $payload->{train}{no} ), + train_line => sanitize( q{}, $payload->{train}{line} ), + cancelled => $payload->{cancelled} ? 1 : 0, + dep_station => sanitize( q{}, $payload->{fromStation}{name} ), + arr_station => sanitize( q{}, $payload->{toStation}{name} ), sched_departure => sanitize( 0, $payload->{fromStation}{scheduledTime} ), rt_departure => sanitize( @@ -478,8 +544,9 @@ sub import_v1 { $payload->{toStation}{realTime} // $payload->{toStation}{scheduledTime} ), - comment => sanitize( q{}, $payload->{comment} ), - lax => $payload->{lax} ? 1 : 0, + comment => sanitize( q{}, $payload->{comment} ), + lax => $payload->{lax} ? 1 : 0, + backend_id => 1, ); if ( $payload->{intermediateStops} @@ -518,14 +585,20 @@ sub import_v1 { my $journey; if ( not $error ) { - $journey = $self->journeys->get_single( - uid => $uid, - db => $db, - journey_id => $journey_id, - verbose => 1 - ); - $error - = $self->journeys->sanity_check( $journey, $payload->{lax} ? 1 : 0 ); + eval { + $journey = $self->journeys->get_single( + uid => $uid, + db => $db, + journey_id => $journey_id, + verbose => 1 + ); + $error + = $self->journeys->sanity_check( $journey, + $payload->{lax} ? 1 : 0 ); + }; + if ($@) { + $error = $@; + } } if ($error) { @@ -568,11 +641,15 @@ sub import_v1 { sub set_token { my ($self) = @_; if ( $self->validation->csrf_protect->has_error('csrf_token') ) { - $self->render( 'account', invalid => 'csrf' ); + $self->render( + 'bad_request', + csrf => 1, + status => 400 + ); return; } my $token = make_token(); - my $token_id = $self->app->token_type->{ $self->param('token') }; + my $token_id = $self->users->get_token_id( $self->param('token') ); if ( not $token_id ) { $self->redirect_to('account'); @@ -605,4 +682,25 @@ sub set_token { $self->redirect_to('account'); } +sub autocomplete { + my ($self) = @_; + + $self->res->headers->cache_control('max-age=86400, immutable'); + + my $backend_id = $self->param('backend_id') // 1; + + my $output + = "document.addEventListener('DOMContentLoaded',function(){M.Autocomplete.init(document.querySelectorAll('.autocomplete'),{\n"; + $output .= 'minLength:3,limit:50,data:'; + $output + .= encode_json( + $self->stations->get_for_autocomplete( backend_id => $backend_id ) ); + $output .= "\n});});\n"; + + $self->render( + format => 'js', + data => $output + ); +} + 1; |