summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2019-12-14 21:53:23 +0100
committerDaniel Friesel <derf@finalrewind.org>2019-12-14 21:53:23 +0100
commit46fc64de4831e90971c4a3db91d6fcfa3628a2d0 (patch)
treeba6a21e9695cac5fa28709abd42c0339a0df828c
parent5fe4174febbf833e0fdbfd5f1500883720c420df (diff)
Add travel (checkin/checkout/undo) API
-rwxr-xr-xlib/Travelynx.pm48
-rwxr-xr-xlib/Travelynx/Controller/Api.pm161
-rw-r--r--templates/account.html.ep2
-rw-r--r--templates/api_documentation.html.ep64
4 files changed, 246 insertions, 29 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index 3abe949..def2d3d 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -11,7 +11,7 @@ use DateTime::Format::Strptime;
use Encode qw(decode encode);
use Geo::Distance;
use JSON;
-use List::Util qw(first);
+use List::Util;
use List::MoreUtils qw(after_incl before_incl);
use Travel::Status::DE::DBWagenreihung;
use Travel::Status::DE::IRIS;
@@ -158,14 +158,14 @@ sub startup {
return {
status => 1,
history => 2,
- action => 3,
+ travel => 3,
import => 4,
};
}
);
$self->attr(
token_types => sub {
- return [qw(status history action import)];
+ return [qw(status history travel import)];
}
);
@@ -425,21 +425,23 @@ sub startup {
$self->helper(
'checkin' => sub {
- my ( $self, $station, $train_id ) = @_;
+ my ( $self, $station, $train_id, $uid ) = @_;
+
+ $uid //= $self->current_user->{id};
my $status = $self->get_departures( $station, 140, 40, 0 );
if ( $status->{errstr} ) {
return ( undef, $status->{errstr} );
}
else {
- my ($train)
- = first { $_->train_id eq $train_id } @{ $status->{results} };
+ my ($train) = List::Util::first { $_->train_id eq $train_id }
+ @{ $status->{results} };
if ( not defined $train ) {
return ( undef, "Train ${train_id} not found" );
}
else {
- my $user = $self->get_user_status;
+ my $user = $self->get_user_status($uid);
if ( $user->{checked_in} or $user->{cancelled} ) {
if ( $user->{train_id} eq $train_id
@@ -450,7 +452,7 @@ sub startup {
}
# Otherwise, someone forgot to check out first
- $self->checkout( $station, 1 );
+ $self->checkout( $station, 1, $uid );
}
eval {
@@ -458,7 +460,7 @@ sub startup {
$self->pg->db->insert(
'in_transit',
{
- user_id => $self->current_user->{id},
+ user_id => $uid,
cancelled => $train->departure_is_cancelled
? 1
: 0,
@@ -488,14 +490,12 @@ sub startup {
);
};
if ($@) {
- my $uid = $self->current_user->{id};
$self->app->log->error(
"Checkin($uid): INSERT failed: $@");
return ( undef, 'INSERT failed: ' . $@ );
}
- $self->add_route_timestamps( $self->current_user->{id},
- $train, 1 );
- $self->run_hook( $self->current_user->{id}, 'checkin' );
+ $self->add_route_timestamps( $uid, $train, 1 );
+ $self->run_hook( $uid, 'checkin' );
return ( $train, undef );
}
}
@@ -504,8 +504,8 @@ sub startup {
$self->helper(
'undo' => sub {
- my ( $self, $journey_id ) = @_;
- my $uid = $self->current_user->{id};
+ my ( $self, $journey_id, $uid ) = @_;
+ $uid //= $self->current_user->{id};
if ( $journey_id eq 'in_transit' ) {
eval {
@@ -627,8 +627,8 @@ sub startup {
my $journey
= $db->select( 'in_transit', '*', { user_id => $uid } )
->expand->hash;
- my ($train)
- = first { $_->train_id eq $train_id } @{ $status->{results} };
+ my ($train) = List::Util::first { $_->train_id eq $train_id }
+ @{ $status->{results} };
# When a checkout is triggered by a checkin, there is an edge case
# with related stations.
@@ -641,8 +641,8 @@ sub startup {
# well.
if ( not $train ) {
$status = $self->get_departures( $station, 120, 180, 1 );
- ($train)
- = first { $_->train_id eq $train_id } @{ $status->{results} };
+ ($train) = List::Util::first { $_->train_id eq $train_id }
+ @{ $status->{results} };
}
# Store the intended checkout station regardless of this operation's
@@ -681,8 +681,11 @@ sub startup {
# Arrival time via IRIS is unknown, so the train probably has not
# arrived yet. Fall back to HAFAS.
- if ( my $station_data
- = first { $_->[0] eq $station } @{ $journey->{route} } )
+ if (
+ my $station_data
+ = List::Util::first { $_->[0] eq $station }
+ @{ $journey->{route} }
+ )
{
$station_data = $station_data->[1];
if ( $station_data->{sched_arr} ) {
@@ -784,7 +787,7 @@ sub startup {
return ( 0, undef );
}
$self->run_hook( $uid, 'update' );
- $self->add_route_timestamps( $self->current_user->{id}, $train, 0 );
+ $self->add_route_timestamps( $uid, $train, 0 );
return ( 1, undef );
}
);
@@ -3234,6 +3237,7 @@ sub startup {
$r->get('/ajax/status/:name')->to('traveling#public_status_card');
$r->get('/ajax/status/:name/:ts')->to('traveling#public_status_card');
$r->post('/api/v1/import')->to('api#import_v1');
+ $r->post('/api/v1/travel')->to('api#travel_v1');
$r->post('/action')->to('traveling#log_action');
$r->post('/geolocation')->to('traveling#geolocation');
$r->post('/list_departures')->to('traveling#redirect_to_station');
diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm
index 84e1507..f420a9e 100755
--- a/lib/Travelynx/Controller/Api.pm
+++ b/lib/Travelynx/Controller/Api.pm
@@ -2,6 +2,7 @@ package Travelynx::Controller::Api;
use Mojo::Base 'Mojolicious::Controller';
use DateTime;
+use List::Util;
use Travel::Status::DE::IRIS::Stations;
use UUID::Tiny qw(:std);
@@ -165,6 +166,166 @@ sub get_v1 {
}
}
+sub travel_v1 {
+ my ($self) = @_;
+
+ my $payload = $self->req->json;
+ my $api_token = $payload->{token} // '';
+
+ if ( $api_token !~ qr{ ^ (?<id> \d+ ) - (?<token> .* ) $ }x ) {
+ $self->render(
+ json => {
+ success => \0,
+ error => 'Malformed JSON or malformed token',
+ },
+ );
+ return;
+ }
+ my $uid = $+{id};
+ $api_token = $+{token};
+
+ if ( $uid > 2147483647 ) {
+ $self->render(
+ json => {
+ success => \0,
+ error => 'Malformed token',
+ },
+ );
+ return;
+ }
+
+ my $token = $self->get_api_token($uid);
+ if ( $api_token ne $token->{'travel'} ) {
+ $self->render(
+ json => {
+ success => \0,
+ error => 'Invalid token',
+ },
+ );
+ return;
+ }
+
+ if ( not exists $payload->{action}
+ or $payload->{action} !~ m{^(checkin|checkout|undo)$} )
+ {
+ $self->render(
+ json => {
+ success => \0,
+ error => 'Missing or invalid action',
+ },
+ );
+ return;
+ }
+
+ if ( $payload->{action} eq 'checkin' ) {
+ my $from_station = sanitize( q{}, $payload->{fromStation} );
+ my $to_station = sanitize( q{}, $payload->{toStation} );
+ my $train_id;
+
+ if ( exists $payload->{train}{id} ) {
+ $train_id = 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} ) {
+ $self->render(
+ json => {
+ success => \0,
+ error => 'Fehler am Abfahrtsbahnhof: '
+ . $status->{errstr},
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ return;
+ }
+ my ($train) = List::Util::first {
+ $_->type eq $train_type and $_->train_no eq $train_no
+ }
+ @{ $status->{results} };
+ if ( not defined $train ) {
+ $self->render(
+ json => {
+ success => \0,
+ error => 'Fehler am Abfahrtsbahnhof: '
+ . $status->{errstr},
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ return;
+ }
+ $train_id = $train->train_id;
+ }
+
+ my ( $train, $error )
+ = $self->checkin( $from_station, $train_id, $uid );
+ if ( $to_station and not $error ) {
+ ( $train, $error ) = $self->checkout( $to_station, 0, $uid );
+ }
+ if ($error) {
+ $self->render(
+ json => {
+ success => \0,
+ error => $error,
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ }
+ else {
+ $self->render(
+ json => {
+ success => \1,
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ }
+ }
+ elsif ( $payload->{action} eq 'checkout' ) {
+ my $to_station = sanitize( q{}, $payload->{toStation} );
+
+ my ( $train, $error )
+ = $self->checkout( $to_station, $payload->{force} ? 1 : 0, $uid );
+ if ($error) {
+ $self->render(
+ json => {
+ success => \0,
+ error => $error,
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ }
+ else {
+ $self->render(
+ json => {
+ success => \1,
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ }
+ }
+ elsif ( $payload->{action} eq 'undo' ) {
+ my $error = $self->undo( 'in_transit', $uid );
+ if ($error) {
+ $self->render(
+ json => {
+ success => \0,
+ error => $error,
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ }
+ else {
+ $self->render(
+ json => {
+ success => \1,
+ status => $self->get_user_status_json_v1($uid)
+ }
+ );
+ }
+ }
+}
+
sub import_v1 {
my ($self) = @_;
diff --git a/templates/account.html.ep b/templates/account.html.ep
index a9f1bb0..be61518 100644
--- a/templates/account.html.ep
+++ b/templates/account.html.ep
@@ -183,7 +183,7 @@
<td>
%= form_for 'set_token' => begin
%= csrf_field
- %= hidden_field 'token' => 'action'
+ %= hidden_field 'token' => 'travel'
<button class="btn waves-effect waves-light" type="submit" name="action" value="generate">
Generieren
</button>
diff --git a/templates/api_documentation.html.ep b/templates/api_documentation.html.ep
index de0231d..b831e33 100644
--- a/templates/api_documentation.html.ep
+++ b/templates/api_documentation.html.ep
@@ -64,17 +64,69 @@
</p>
</div>
</div>
-<!--
-<h3>History</h3>
+
+<h2>Travel</h2>
<div class="row">
<div class="col s12">
<p>
- Coming soon.
+ Checkin per API. Sobald eine Zielstation bekannt ist, erfolgt der
+ Checkout wie beim Webinterface automatisch zehn Minuten nach Ankunft.
+ </p>
+ <p style="font-family: Monospace;">
+ curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>"}' <%= $api_root %>/travel
+ </p>
+ <p>Payload zum Einchecken, optional mit Zielwahl:</p>
+ <p style="font-family: Monospace;">
+ {<br/>
+ "token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
+ "action" : "checkin",<br/>
+ "train" : {<br/>
+ "type" : "ICE",<br/>
+ "no" : "1234",<br/>
+ }<br/>
+ "fromStation" : "Essen Hbf", (DS100 oder EVA-Nummer sind ebenfalls möglich)<br/>
+ "toStation" : "Berlin Hbf" (optional, DS100 oder EVA-Nummer sind ebenfalls möglich)<br/>
+ }
+ </p>
+ <p>Payload zur Wahl eines neuen Ziels, wenn bereits eingecheckt:</p>
+ <p style="font-family: Monospace;">
+ {<br/>
+ "token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
+ "action" : "checkout",<br/>
+ "force" : True/False, (wenn True: Checkout jetzt durchführen und auftretende Fehler ignorieren. Kann zu Logeinträgen ohne Ankunftsdaten führen.)<br/>
+ "toStation" : "Berlin Hbf" (DS100 oder EVA-Nummer sind ebenfalls möglich)<br/>
+ }
+ </p>
+ <p>Payload zum Rückgängigmachen eines Checkins (nur während der Fahrt möglich):</p>
+ <p style="font-family: Monospace;">
+ {<br/>
+ "token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
+ "action" : "undo"<br/>
+ }
+ </p>
+ <p>
+ Antwort bei Erfolg:
+ </p>
+ <p style="font-family: Monospace;">
+ {<br/>
+ "success" : True,<br/>
+ "status" : { aktueller Nutzerstatus gemäß Status-API }<br/>
+ }
+ </p>
+ <p>
+ Antwort bei Fehler:
+ </p>
+ <p style="font-family: Monospace;">
+ {<br/>
+ "success" : False,<br/>
+ "error" : "Begründung",<br/>
+ "status" : { aktueller Nutzerstatus gemäß Status-API }<br/>
+ }
</p>
</div>
-</div>-->
+</div>
-<h3>Import</h3>
+<h2>Import</h2>
<div class="row">
<div class="col s12">
<p>
@@ -86,7 +138,7 @@
<p>Payload (alle nicht als optional markierten Felder sind Pflicht):</p>
<p style="font-family: Monospace;">
{<br/>
- "token" : "<%= $token->{import} // 'TOKEN' %>",<br/>
+ "token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
"dryRun" : True/False, (optional: wenn True, wird die Eingabe validiert, aber keine Zugfahrt angelegt)<br/>
"cancelled" : True/False, (Zugausfall?)<br/>
"train" : {<br/>