diff options
-rwxr-xr-x | lib/Travelynx.pm | 48 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Api.pm | 161 | ||||
-rw-r--r-- | templates/account.html.ep | 2 | ||||
-rw-r--r-- | templates/api_documentation.html.ep | 64 |
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/> |