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.pm409
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;