Loading lib/Travelynx.pm +26 −22 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)]; } ); Loading Loading @@ -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 Loading @@ -450,7 +452,7 @@ sub startup { } # Otherwise, someone forgot to check out first $self->checkout( $station, 1 ); $self->checkout( $station, 1, $uid ); } eval { Loading @@ -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, Loading Loading @@ -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 ); } } Loading @@ -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 { Loading Loading @@ -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. Loading @@ -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 Loading Loading @@ -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} ) { Loading Loading @@ -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 ); } ); Loading Loading @@ -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'); Loading lib/Travelynx/Controller/Api.pm +161 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) = @_; Loading templates/account.html.ep +1 −1 Original line number Diff line number Diff line Loading @@ -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> Loading templates/api_documentation.html.ep +58 −6 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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/> Loading Loading
lib/Travelynx.pm +26 −22 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)]; } ); Loading Loading @@ -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 Loading @@ -450,7 +452,7 @@ sub startup { } # Otherwise, someone forgot to check out first $self->checkout( $station, 1 ); $self->checkout( $station, 1, $uid ); } eval { Loading @@ -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, Loading Loading @@ -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 ); } } Loading @@ -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 { Loading Loading @@ -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. Loading @@ -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 Loading Loading @@ -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} ) { Loading Loading @@ -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 ); } ); Loading Loading @@ -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'); Loading
lib/Travelynx/Controller/Api.pm +161 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) = @_; Loading
templates/account.html.ep +1 −1 Original line number Diff line number Diff line Loading @@ -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> Loading
templates/api_documentation.html.ep +58 −6 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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/> Loading