Add travel (checkin/checkout/undo) API
This commit is contained in:
parent
5fe4174feb
commit
46fc64de48
4 changed files with 246 additions and 29 deletions
|
@ -11,7 +11,7 @@ use DateTime::Format::Strptime;
|
||||||
use Encode qw(decode encode);
|
use Encode qw(decode encode);
|
||||||
use Geo::Distance;
|
use Geo::Distance;
|
||||||
use JSON;
|
use JSON;
|
||||||
use List::Util qw(first);
|
use List::Util;
|
||||||
use List::MoreUtils qw(after_incl before_incl);
|
use List::MoreUtils qw(after_incl before_incl);
|
||||||
use Travel::Status::DE::DBWagenreihung;
|
use Travel::Status::DE::DBWagenreihung;
|
||||||
use Travel::Status::DE::IRIS;
|
use Travel::Status::DE::IRIS;
|
||||||
|
@ -158,14 +158,14 @@ sub startup {
|
||||||
return {
|
return {
|
||||||
status => 1,
|
status => 1,
|
||||||
history => 2,
|
history => 2,
|
||||||
action => 3,
|
travel => 3,
|
||||||
import => 4,
|
import => 4,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$self->attr(
|
$self->attr(
|
||||||
token_types => sub {
|
token_types => sub {
|
||||||
return [qw(status history action import)];
|
return [qw(status history travel import)];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -425,21 +425,23 @@ sub startup {
|
||||||
|
|
||||||
$self->helper(
|
$self->helper(
|
||||||
'checkin' => sub {
|
'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 );
|
my $status = $self->get_departures( $station, 140, 40, 0 );
|
||||||
if ( $status->{errstr} ) {
|
if ( $status->{errstr} ) {
|
||||||
return ( undef, $status->{errstr} );
|
return ( undef, $status->{errstr} );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
my ($train)
|
my ($train) = List::Util::first { $_->train_id eq $train_id }
|
||||||
= first { $_->train_id eq $train_id } @{ $status->{results} };
|
@{ $status->{results} };
|
||||||
if ( not defined $train ) {
|
if ( not defined $train ) {
|
||||||
return ( undef, "Train ${train_id} not found" );
|
return ( undef, "Train ${train_id} not found" );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
my $user = $self->get_user_status;
|
my $user = $self->get_user_status($uid);
|
||||||
if ( $user->{checked_in} or $user->{cancelled} ) {
|
if ( $user->{checked_in} or $user->{cancelled} ) {
|
||||||
|
|
||||||
if ( $user->{train_id} eq $train_id
|
if ( $user->{train_id} eq $train_id
|
||||||
|
@ -450,7 +452,7 @@ sub startup {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Otherwise, someone forgot to check out first
|
# Otherwise, someone forgot to check out first
|
||||||
$self->checkout( $station, 1 );
|
$self->checkout( $station, 1, $uid );
|
||||||
}
|
}
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
|
@ -458,7 +460,7 @@ sub startup {
|
||||||
$self->pg->db->insert(
|
$self->pg->db->insert(
|
||||||
'in_transit',
|
'in_transit',
|
||||||
{
|
{
|
||||||
user_id => $self->current_user->{id},
|
user_id => $uid,
|
||||||
cancelled => $train->departure_is_cancelled
|
cancelled => $train->departure_is_cancelled
|
||||||
? 1
|
? 1
|
||||||
: 0,
|
: 0,
|
||||||
|
@ -488,14 +490,12 @@ sub startup {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
if ($@) {
|
if ($@) {
|
||||||
my $uid = $self->current_user->{id};
|
|
||||||
$self->app->log->error(
|
$self->app->log->error(
|
||||||
"Checkin($uid): INSERT failed: $@");
|
"Checkin($uid): INSERT failed: $@");
|
||||||
return ( undef, 'INSERT failed: ' . $@ );
|
return ( undef, 'INSERT failed: ' . $@ );
|
||||||
}
|
}
|
||||||
$self->add_route_timestamps( $self->current_user->{id},
|
$self->add_route_timestamps( $uid, $train, 1 );
|
||||||
$train, 1 );
|
$self->run_hook( $uid, 'checkin' );
|
||||||
$self->run_hook( $self->current_user->{id}, 'checkin' );
|
|
||||||
return ( $train, undef );
|
return ( $train, undef );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,8 +504,8 @@ sub startup {
|
||||||
|
|
||||||
$self->helper(
|
$self->helper(
|
||||||
'undo' => sub {
|
'undo' => sub {
|
||||||
my ( $self, $journey_id ) = @_;
|
my ( $self, $journey_id, $uid ) = @_;
|
||||||
my $uid = $self->current_user->{id};
|
$uid //= $self->current_user->{id};
|
||||||
|
|
||||||
if ( $journey_id eq 'in_transit' ) {
|
if ( $journey_id eq 'in_transit' ) {
|
||||||
eval {
|
eval {
|
||||||
|
@ -627,8 +627,8 @@ sub startup {
|
||||||
my $journey
|
my $journey
|
||||||
= $db->select( 'in_transit', '*', { user_id => $uid } )
|
= $db->select( 'in_transit', '*', { user_id => $uid } )
|
||||||
->expand->hash;
|
->expand->hash;
|
||||||
my ($train)
|
my ($train) = List::Util::first { $_->train_id eq $train_id }
|
||||||
= first { $_->train_id eq $train_id } @{ $status->{results} };
|
@{ $status->{results} };
|
||||||
|
|
||||||
# When a checkout is triggered by a checkin, there is an edge case
|
# When a checkout is triggered by a checkin, there is an edge case
|
||||||
# with related stations.
|
# with related stations.
|
||||||
|
@ -641,8 +641,8 @@ sub startup {
|
||||||
# well.
|
# well.
|
||||||
if ( not $train ) {
|
if ( not $train ) {
|
||||||
$status = $self->get_departures( $station, 120, 180, 1 );
|
$status = $self->get_departures( $station, 120, 180, 1 );
|
||||||
($train)
|
($train) = List::Util::first { $_->train_id eq $train_id }
|
||||||
= first { $_->train_id eq $train_id } @{ $status->{results} };
|
@{ $status->{results} };
|
||||||
}
|
}
|
||||||
|
|
||||||
# Store the intended checkout station regardless of this operation's
|
# 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
|
# Arrival time via IRIS is unknown, so the train probably has not
|
||||||
# arrived yet. Fall back to HAFAS.
|
# arrived yet. Fall back to HAFAS.
|
||||||
if ( my $station_data
|
if (
|
||||||
= first { $_->[0] eq $station } @{ $journey->{route} } )
|
my $station_data
|
||||||
|
= List::Util::first { $_->[0] eq $station }
|
||||||
|
@{ $journey->{route} }
|
||||||
|
)
|
||||||
{
|
{
|
||||||
$station_data = $station_data->[1];
|
$station_data = $station_data->[1];
|
||||||
if ( $station_data->{sched_arr} ) {
|
if ( $station_data->{sched_arr} ) {
|
||||||
|
@ -784,7 +787,7 @@ sub startup {
|
||||||
return ( 0, undef );
|
return ( 0, undef );
|
||||||
}
|
}
|
||||||
$self->run_hook( $uid, 'update' );
|
$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 );
|
return ( 1, undef );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -3234,6 +3237,7 @@ sub startup {
|
||||||
$r->get('/ajax/status/:name')->to('traveling#public_status_card');
|
$r->get('/ajax/status/:name')->to('traveling#public_status_card');
|
||||||
$r->get('/ajax/status/:name/:ts')->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/import')->to('api#import_v1');
|
||||||
|
$r->post('/api/v1/travel')->to('api#travel_v1');
|
||||||
$r->post('/action')->to('traveling#log_action');
|
$r->post('/action')->to('traveling#log_action');
|
||||||
$r->post('/geolocation')->to('traveling#geolocation');
|
$r->post('/geolocation')->to('traveling#geolocation');
|
||||||
$r->post('/list_departures')->to('traveling#redirect_to_station');
|
$r->post('/list_departures')->to('traveling#redirect_to_station');
|
||||||
|
|
|
@ -2,6 +2,7 @@ package Travelynx::Controller::Api;
|
||||||
use Mojo::Base 'Mojolicious::Controller';
|
use Mojo::Base 'Mojolicious::Controller';
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use List::Util;
|
||||||
use Travel::Status::DE::IRIS::Stations;
|
use Travel::Status::DE::IRIS::Stations;
|
||||||
use UUID::Tiny qw(:std);
|
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 {
|
sub import_v1 {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
|
|
|
@ -183,7 +183,7 @@
|
||||||
<td>
|
<td>
|
||||||
%= form_for 'set_token' => begin
|
%= form_for 'set_token' => begin
|
||||||
%= csrf_field
|
%= csrf_field
|
||||||
%= hidden_field 'token' => 'action'
|
%= hidden_field 'token' => 'travel'
|
||||||
<button class="btn waves-effect waves-light" type="submit" name="action" value="generate">
|
<button class="btn waves-effect waves-light" type="submit" name="action" value="generate">
|
||||||
Generieren
|
Generieren
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -64,17 +64,69 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
|
||||||
<h3>History</h3>
|
<h2>Travel</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>-->
|
</div>
|
||||||
|
|
||||||
<h3>Import</h3>
|
<h2>Import</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<p>
|
<p>
|
||||||
|
@ -86,7 +138,7 @@
|
||||||
<p>Payload (alle nicht als optional markierten Felder sind Pflicht):</p>
|
<p>Payload (alle nicht als optional markierten Felder sind Pflicht):</p>
|
||||||
<p style="font-family: Monospace;">
|
<p style="font-family: Monospace;">
|
||||||
{<br/>
|
{<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/>
|
"dryRun" : True/False, (optional: wenn True, wird die Eingabe validiert, aber keine Zugfahrt angelegt)<br/>
|
||||||
"cancelled" : True/False, (Zugausfall?)<br/>
|
"cancelled" : True/False, (Zugausfall?)<br/>
|
||||||
"train" : {<br/>
|
"train" : {<br/>
|
||||||
|
|
Loading…
Reference in a new issue