diff options
Diffstat (limited to 'lib/Travelynx/Controller/Api.pm')
-rwxr-xr-x | lib/Travelynx/Controller/Api.pm | 409 |
1 files changed, 265 insertions, 144 deletions
diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index 4546292..687243d 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -1,11 +1,17 @@ package Travelynx::Controller::Api; + +# 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); } @@ -18,13 +24,28 @@ sub sanitize { if ( $type eq '' ) { return '' . $value; } - return 0 + $value; + if ( $value =~ m{ ^ [0-9.e]+ $ }x ) { + return 0 + $value; + } + 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 { @@ -60,8 +81,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', @@ -70,7 +94,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( @@ -97,18 +121,6 @@ sub travel_v1 { return; } - if ( $self->app->mode ne 'development' ) { - $self->render( - json => { - success => \0, - deprecated => \0, - error => -'This feature is incomplete and only available in development mode', - }, - ); - return; - } - my $api_token = $payload->{token} // ''; if ( $api_token !~ qr{ ^ (?<id> \d+ ) - (?<token> .* ) $ }x ) { @@ -135,7 +147,7 @@ sub travel_v1 { 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 => { @@ -155,7 +167,7 @@ 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 ) }, ); return; @@ -165,12 +177,14 @@ sub travel_v1 { my $from_station = sanitize( q{}, $payload->{fromStation} ); my $to_station = sanitize( q{}, $payload->{toStation} ); my $train_id; + my $hafas = exists $payload->{train}{journeyID} ? 1 : 0; 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} ) ) ) { @@ -179,77 +193,139 @@ 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 ) }, ); return; } - if ( exists $payload->{train}{id} ) { - $train_id = sanitize( 0, $payload->{train}{id} ); + if ( not $hafas and not $self->stations->search($from_station) ) { + $self->render( + json => { + success => \0, + deprecated => \0, + error => 'Unknown fromStation', + status => $self->get_user_status_json_v1( uid => $uid ) + }, + ); + return; + } + + if ( $to_station + and not $hafas + and not $self->stations->search($to_station) ) + { + $self->render( + json => { + success => \0, + deprecated => \0, + error => 'Unknown toStation', + status => $self->get_user_status_json_v1( uid => $uid ) + }, + ); + return; + } + + 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->get_departures( $from_station, 140, 40, 0 ); - if ( $status->{errstr} ) { + + $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 ); + } + ); + } + + $self->render_later; + + $train_p->then( + sub { + my ($train_id) = @_; + return $self->checkin_p( + station => $from_station, + train_id => $train_id, + uid => $uid + ); + } + )->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( $from_station, $train_id, $uid ); - if ( $payload->{comment} and not $error ) { - $self->update_in_transit_comment( - sanitize( q{}, $payload->{comment} ), $uid ); - } - if ( $to_station and not $error ) { - ( $train, $error ) = $self->checkout( $to_station, 0, $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} ); @@ -260,38 +336,56 @@ 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; } if ( $payload->{comment} ) { - $self->update_in_transit_comment( - sanitize( q{}, $payload->{comment} ), $uid ); - } - - my ( $train, $error ) - = $self->checkout( $to_station, $payload->{force} ? 1 : 0, $uid ); - if ($error) { - $self->render( - json => { - success => \0, - deprecated => \0, - error => 'Checkout error: ' . $error, - status => $self->get_user_status_json_v1($uid) - } + $self->in_transit->update_user_data( + uid => $uid, + user_data => { comment => sanitize( q{}, $payload->{comment} ) } ); } - else { - $self->render( - json => { - success => \1, - deprecated => \0, - 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); } - ); - } + $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 ); @@ -301,7 +395,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 ) } ); } @@ -310,7 +404,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 ) } ); } @@ -325,19 +419,9 @@ sub import_v1 { if ( not $payload or ref($payload) ne 'HASH' ) { $self->render( json => { - success => \0, - error => 'Malformed JSON', - }, - ); - return; - } - - if ( $self->app->mode ne 'development' ) { - $self->render( - json => { - success => \0, - error => -'This feature is incomplete and only available in development mode', + success => \0, + deprecated => \0, + error => 'Malformed JSON', }, ); return; @@ -348,8 +432,9 @@ sub import_v1 { if ( $api_token !~ qr{ ^ (?<id> \d+ ) - (?<token> .* ) $ }x ) { $self->render( json => { - success => \0, - error => 'Malformed token', + success => \0, + deprecated => \0, + error => 'Malformed token', }, ); return; @@ -360,19 +445,21 @@ sub import_v1 { if ( $uid > 2147483647 ) { $self->render( json => { - success => \0, - error => 'Malformed token', + success => \0, + deprecated => \0, + error => 'Malformed token', }, ); return; } - my $token = $self->get_api_token($uid); - if ( $api_token ne $token->{'import'} ) { + my $token = $self->users->get_api_token( uid => $uid ); + if ( not $token->{'import'} or $api_token ne $token->{'import'} ) { $self->render( json => { - success => \0, - error => 'Invalid token', + success => \0, + deprecated => \0, + error => 'Invalid token', }, ); return; @@ -383,8 +470,9 @@ sub import_v1 { { $self->render( json => { - success => \0, - error => 'missing fromStation or toStation', + success => \0, + deprecated => \0, + error => 'missing fromStation or toStation', }, ); return; @@ -409,13 +497,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( @@ -434,9 +522,12 @@ sub import_v1 { lax => $payload->{lax} ? 1 : 0, ); - if ( $payload->{route} and ref( $payload->{route} ) eq 'ARRAY' ) { + if ( $payload->{intermediateStops} + and ref( $payload->{intermediateStops} ) eq 'ARRAY' ) + { $opt{route} - = [ map { sanitize( q{}, $_ ) } @{ $payload->{route} } ]; + = [ map { sanitize( q{}, $_ ) } + @{ $payload->{intermediateStops} } ]; } for my $key (qw(sched_departure rt_departure sched_arrival rt_arrival)) @@ -451,8 +542,9 @@ sub import_v1 { my ($first_line) = split( qr{\n}, $@ ); $self->render( json => { - success => \0, - error => $first_line + success => \0, + deprecated => \0, + error => $first_line } ); return; @@ -462,44 +554,52 @@ sub import_v1 { my $tx = $db->begin; $opt{db} = $db; - my ( $journey_id, $error ) = $self->add_journey(%opt); + my ( $journey_id, $error ) = $self->journeys->add(%opt); my $journey; if ( not $error ) { - $journey = $self->get_journey( + $journey = $self->journeys->get_single( uid => $uid, db => $db, journey_id => $journey_id, verbose => 1 ); $error - = $self->journey_sanity_check( $journey, $payload->{lax} ? 1 : 0 ); + = $self->journeys->sanity_check( $journey, $payload->{lax} ? 1 : 0 ); } if ($error) { $self->render( json => { - success => \0, - error => $error + success => \0, + deprecated => \0, + error => $error } ); } elsif ( $payload->{dryRun} ) { $self->render( json => { - success => \1, - id => $journey_id, - result => $journey + success => \1, + deprecated => \0, + id => $journey_id, + result => $journey } ); } else { + $self->journey_stats_cache->invalidate( + ts => $opt{rt_departure}, + db => $db, + uid => $uid + ); $tx->commit; $self->render( json => { - success => \1, - id => $journey_id, - result => $journey + success => \1, + deprecated => \0, + id => $journey_id, + result => $journey } ); } @@ -508,11 +608,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'); @@ -545,4 +649,21 @@ sub set_token { $self->redirect_to('account'); } +sub autocomplete { + my ($self) = @_; + + $self->res->headers->cache_control('max-age=86400, immutable'); + + 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 ); + $output .= "\n});});\n"; + + $self->render( + format => 'js', + data => $output + ); +} + 1; |