summaryrefslogtreecommitdiff
path: root/lib/Travelynx/Controller/Api.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Travelynx/Controller/Api.pm')
-rwxr-xr-xlib/Travelynx/Controller/Api.pm462
1 files changed, 310 insertions, 152 deletions
diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm
index 4546292..572d3fa 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);
}
@@ -15,21 +21,41 @@ sub sanitize {
if ( not defined $value ) {
return undef;
}
+ if ( not defined $type ) {
+ return $value ? ( '' . $value ) : undef;
+ }
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 {
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 ) {
@@ -60,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',
@@ -70,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(
@@ -93,18 +122,7 @@ sub travel_v1 {
deprecated => \0,
error => 'Malformed JSON',
},
- );
- 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',
- },
+ status => 400,
);
return;
}
@@ -118,6 +136,7 @@ sub travel_v1 {
deprecated => \0,
error => 'Malformed token',
},
+ status => 400,
);
return;
}
@@ -131,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 => {
@@ -143,6 +163,7 @@ sub travel_v1 {
deprecated => \0,
error => 'Invalid token',
},
+ status => 400,
);
return;
}
@@ -155,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;
}
@@ -165,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} )
)
)
{
@@ -179,77 +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 ( exists $payload->{train}{id} ) {
- $train_id = sanitize( 0, $payload->{train}{id} );
+ if ( not $hafas
+ and not $dbris
+ and not $self->stations->search( $from_station, backend_id => 1 ) )
+ {
+ $self->render(
+ json => {
+ success => \0,
+ deprecated => \0,
+ error => 'Unknown fromStation',
+ status => $self->get_user_status_json_v1( uid => $uid )
+ },
+ status => 400,
+ );
+ return;
+ }
+
+ 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 => 'Unknown toStation',
+ status => $self->get_user_status_json_v1( uid => $uid )
+ },
+ status => 400,
+ );
+ 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,
+ 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( $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 +362,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 +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 )
}
);
}
@@ -310,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 )
}
);
}
@@ -325,19 +445,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 +458,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 +471,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 +496,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 +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(
@@ -430,13 +544,17 @@ 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->{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 +569,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 +581,58 @@ 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(
- uid => $uid,
- db => $db,
- journey_id => $journey_id,
- verbose => 1
- );
- $error
- = $self->journey_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) {
$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 +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');
@@ -545,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;