Loading lib/Travelynx.pm +5 −3 Original line number Diff line number Diff line Loading @@ -159,12 +159,13 @@ sub startup { status => 1, history => 2, action => 3, import => 4, }; } ); $self->attr( token_types => sub { return [qw(status history action)]; return [qw(status history action import)]; } ); Loading Loading @@ -330,7 +331,7 @@ sub startup { my ( $self, %opt ) = @_; my $db = $opt{db}; my $uid = $self->current_user->{id}; my $uid = $opt{uid} // $self->current_user->{id}; my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $dep_station = get_station( $opt{dep_station} ); my $arr_station = get_station( $opt{arr_station} ); Loading Loading @@ -410,7 +411,7 @@ sub startup { $journey_id = $db->insert( 'journeys', $entry, { returning => 'id' } ) ->hash->{id}; $self->invalidate_stats_cache( $opt{rt_departure}, $db ); $self->invalidate_stats_cache( $opt{rt_departure}, $db, $uid ); }; if ($@) { Loading Loading @@ -3232,6 +3233,7 @@ sub startup { $r->get('/status/:name/:ts')->to('traveling#user_status'); $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('/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 +164 −3 Original line number Diff line number Diff line package Travelynx::Controller::Api; use Mojo::Base 'Mojolicious::Controller'; use DateTime; use Travel::Status::DE::IRIS::Stations; use UUID::Tiny qw(:std); Loading @@ -8,6 +9,17 @@ sub make_token { return create_uuid_as_string(UUID_V4); } sub sanitize { my ( $type, $value ) = @_; if ( not defined $value ) { return undef; } if ( $type eq '' ) { return '' . $value; } return 0 + $value; } sub documentation { my ($self) = @_; Loading Loading @@ -153,6 +165,155 @@ sub get_v1 { } } sub import_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->{'import'} ) { $self->render( json => { success => \0, error => 'Invalid token', }, ); return; } if ( not exists $payload->{fromStation} or not exists $payload->{toStation} ) { $self->render( json => { success => \0, error => 'missing fromStation or toStation', }, ); return; } my %opt; eval { %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} ), sched_departure => sanitize( 0, $payload->{fromStation}{scheduledTime} ), rt_departure => sanitize( 0, $payload->{fromStation}{realTime} // $payload->{fromStation}{scheduledTime} ), sched_arrival => sanitize( 0, $payload->{toStation}{scheduledTime} ), rt_arrival => sanitize( 0, $payload->{toStation}{realTime} // $payload->{toStation}{scheduledTime} ), comment => sanitize( q{}, $payload->{comment} ), ); if ( $payload->{route} and ref( $payload->{route} ) eq 'ARRAY' ) { $opt{route} = [ map { sanitize( q{}, $_ ) } @{ $payload->{route} } ]; } for my $key (qw(sched_departure rt_departure sched_arrival rt_arrival)) { $opt{$key} = DateTime->from_epoch( time_zone => 'Europe/Berlin', epoch => $opt{$key} ); } }; if ($@) { my ($first_line) = split( qr{\n}, $@ ); $self->render( json => { success => \0, error => $first_line } ); return; } my $db = $self->pg->db; my $tx = $db->begin; $opt{db} = $db; my ( $journey_id, $error ) = $self->add_journey(%opt); my $journey; if ( not $error ) { $journey = $self->get_journey( uid => $uid, db => $db, journey_id => $journey_id, verbose => 1 ); $error = $self->journey_sanity_check($journey); } if ($error) { $self->render( json => { success => \0, error => $error } ); } elsif ( $payload->{dryRun} ) { $self->render( json => { success => \1, id => $journey_id, result => $journey } ); } else { $tx->commit; $self->render( json => { success => \1, id => $journey_id, result => $journey } ); } } sub set_token { my ($self) = @_; if ( $self->validation->csrf_protect->has_error('csrf_token') ) { Loading templates/account.html.ep +25 −2 Original line number Diff line number Diff line Loading @@ -169,7 +169,7 @@ </button> %= end </td> </tr> </tr>--> <tr> <th scope="row">Travel</th> <td> Loading @@ -192,7 +192,30 @@ </button> %= end </td> </tr> --> </tr> <tr> <th scope="row">Import</th> <td> % if ($token->{import}) { %= $acc->{id} . '-' . $token->{import} % } % else { — % } </td> <td> %= form_for 'set_token' => begin %= csrf_field %= hidden_field 'token' => 'import' <button class="btn waves-effect waves-light" type="submit" name="action" value="generate"> Generieren </button> <button class="btn waves-effect waves-light red" type="submit" name="action" value="delete"> Löschen </button> %= end </td> </tr> </table> </div> </div> Loading templates/api_documentation.html.ep +55 −5 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ "scheduledTime": 1556083680,<br/> "realTime": 1556083680,<br/> },<br/> "fromStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)<br/> "toStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)<br/> "name" : "Essen Stadtwald",<br/> "ds100" : "EESA",<br/> "uic" : 8001896,<br/> Loading Loading @@ -72,14 +72,64 @@ Coming soon. </p> </div> </div> </div>--> <h3>Travel</h3> <h3>Import</h3> <div class="row"> <div class="col s12"> <p> Ein- und Auschecken per API. Coming soon. Manueller Import vergangener Zugfahrten (eine Fahrt pro API-Aufruf). </p> <p style="font-family: Monospace;"> curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{status} // 'TOKEN' %>"}' <%= $api_root %>/import </p> <p>Payload (alle nicht als optional markierten Felder sind Pflicht):</p> <p style="font-family: Monospace;"> {<br/> "token" : "<%= $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/> "type" : "S", (Zugtyp, z.B. ICE, RE, S)<br/> "line" : "6", (Linie als String, bei Zügen ohne Linie wie IC/ICE u.ä. null)<br/> "no" : "30634", (Zugnummer als String)<br/> },<br/> "fromStation" : { (Start / Checkin)<br/> "name" : "Essen Hbf", (Name oder DS100)<br/> "scheduledTime": 1556083680, (UNIX-Timestamp)<br/> "realTime": 1556083680, (UNIX-Timestamp, optional, default == scheduledTime)<br/> },<br/> "toStation" : { (Ziel / Checkout)<br/> "name" : "Essen Stadtwald", (Name oder DS100)<br/> "scheduledTime": 1556083980, (UNIX-Timestamp)<br/> "realTime": 1556083980, (UNIX-Timestamp, optional, default == scheduledTime)<br/> },<br/> "route" : [ (optionale Liste mit Unterwegshalten als Name oder DS100, darf keine Stationen vor Checkin oder nach Checkout beinhalten)<br/> "Essen Hbf",<br/> "Essen Süd",<br/> "Essen Stadtwald"<br/> ],<br/> "comment" : "Beliebiger Text" (optionaler Freitext-Kommentar)<br/> } </p> <p> Antwort bei Erfolg (der Inhalt von "result" ist von dryRun unabhängig): </p> <p style="font-family: Monospace;"> {<br/> "success" : True,<br/> "id" : 1234, (ID der eingetragenen Zugfahrt)<br/> "result" : { ... } (Eingetragene Daten, Inhalt ist variabel)<br/> } </p> <p> Antwort bei Fehler: </p> <p style="font-family: Monospace;"> {<br/> "success" : False,<br/> "error" : "Begründung"<br/> } </p> </div> </div> --> Loading
lib/Travelynx.pm +5 −3 Original line number Diff line number Diff line Loading @@ -159,12 +159,13 @@ sub startup { status => 1, history => 2, action => 3, import => 4, }; } ); $self->attr( token_types => sub { return [qw(status history action)]; return [qw(status history action import)]; } ); Loading Loading @@ -330,7 +331,7 @@ sub startup { my ( $self, %opt ) = @_; my $db = $opt{db}; my $uid = $self->current_user->{id}; my $uid = $opt{uid} // $self->current_user->{id}; my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $dep_station = get_station( $opt{dep_station} ); my $arr_station = get_station( $opt{arr_station} ); Loading Loading @@ -410,7 +411,7 @@ sub startup { $journey_id = $db->insert( 'journeys', $entry, { returning => 'id' } ) ->hash->{id}; $self->invalidate_stats_cache( $opt{rt_departure}, $db ); $self->invalidate_stats_cache( $opt{rt_departure}, $db, $uid ); }; if ($@) { Loading Loading @@ -3232,6 +3233,7 @@ sub startup { $r->get('/status/:name/:ts')->to('traveling#user_status'); $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('/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 +164 −3 Original line number Diff line number Diff line package Travelynx::Controller::Api; use Mojo::Base 'Mojolicious::Controller'; use DateTime; use Travel::Status::DE::IRIS::Stations; use UUID::Tiny qw(:std); Loading @@ -8,6 +9,17 @@ sub make_token { return create_uuid_as_string(UUID_V4); } sub sanitize { my ( $type, $value ) = @_; if ( not defined $value ) { return undef; } if ( $type eq '' ) { return '' . $value; } return 0 + $value; } sub documentation { my ($self) = @_; Loading Loading @@ -153,6 +165,155 @@ sub get_v1 { } } sub import_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->{'import'} ) { $self->render( json => { success => \0, error => 'Invalid token', }, ); return; } if ( not exists $payload->{fromStation} or not exists $payload->{toStation} ) { $self->render( json => { success => \0, error => 'missing fromStation or toStation', }, ); return; } my %opt; eval { %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} ), sched_departure => sanitize( 0, $payload->{fromStation}{scheduledTime} ), rt_departure => sanitize( 0, $payload->{fromStation}{realTime} // $payload->{fromStation}{scheduledTime} ), sched_arrival => sanitize( 0, $payload->{toStation}{scheduledTime} ), rt_arrival => sanitize( 0, $payload->{toStation}{realTime} // $payload->{toStation}{scheduledTime} ), comment => sanitize( q{}, $payload->{comment} ), ); if ( $payload->{route} and ref( $payload->{route} ) eq 'ARRAY' ) { $opt{route} = [ map { sanitize( q{}, $_ ) } @{ $payload->{route} } ]; } for my $key (qw(sched_departure rt_departure sched_arrival rt_arrival)) { $opt{$key} = DateTime->from_epoch( time_zone => 'Europe/Berlin', epoch => $opt{$key} ); } }; if ($@) { my ($first_line) = split( qr{\n}, $@ ); $self->render( json => { success => \0, error => $first_line } ); return; } my $db = $self->pg->db; my $tx = $db->begin; $opt{db} = $db; my ( $journey_id, $error ) = $self->add_journey(%opt); my $journey; if ( not $error ) { $journey = $self->get_journey( uid => $uid, db => $db, journey_id => $journey_id, verbose => 1 ); $error = $self->journey_sanity_check($journey); } if ($error) { $self->render( json => { success => \0, error => $error } ); } elsif ( $payload->{dryRun} ) { $self->render( json => { success => \1, id => $journey_id, result => $journey } ); } else { $tx->commit; $self->render( json => { success => \1, id => $journey_id, result => $journey } ); } } sub set_token { my ($self) = @_; if ( $self->validation->csrf_protect->has_error('csrf_token') ) { Loading
templates/account.html.ep +25 −2 Original line number Diff line number Diff line Loading @@ -169,7 +169,7 @@ </button> %= end </td> </tr> </tr>--> <tr> <th scope="row">Travel</th> <td> Loading @@ -192,7 +192,30 @@ </button> %= end </td> </tr> --> </tr> <tr> <th scope="row">Import</th> <td> % if ($token->{import}) { %= $acc->{id} . '-' . $token->{import} % } % else { — % } </td> <td> %= form_for 'set_token' => begin %= csrf_field %= hidden_field 'token' => 'import' <button class="btn waves-effect waves-light" type="submit" name="action" value="generate"> Generieren </button> <button class="btn waves-effect waves-light red" type="submit" name="action" value="delete"> Löschen </button> %= end </td> </tr> </table> </div> </div> Loading
templates/api_documentation.html.ep +55 −5 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ "scheduledTime": 1556083680,<br/> "realTime": 1556083680,<br/> },<br/> "fromStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)<br/> "toStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)<br/> "name" : "Essen Stadtwald",<br/> "ds100" : "EESA",<br/> "uic" : 8001896,<br/> Loading Loading @@ -72,14 +72,64 @@ Coming soon. </p> </div> </div> </div>--> <h3>Travel</h3> <h3>Import</h3> <div class="row"> <div class="col s12"> <p> Ein- und Auschecken per API. Coming soon. Manueller Import vergangener Zugfahrten (eine Fahrt pro API-Aufruf). </p> <p style="font-family: Monospace;"> curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{status} // 'TOKEN' %>"}' <%= $api_root %>/import </p> <p>Payload (alle nicht als optional markierten Felder sind Pflicht):</p> <p style="font-family: Monospace;"> {<br/> "token" : "<%= $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/> "type" : "S", (Zugtyp, z.B. ICE, RE, S)<br/> "line" : "6", (Linie als String, bei Zügen ohne Linie wie IC/ICE u.ä. null)<br/> "no" : "30634", (Zugnummer als String)<br/> },<br/> "fromStation" : { (Start / Checkin)<br/> "name" : "Essen Hbf", (Name oder DS100)<br/> "scheduledTime": 1556083680, (UNIX-Timestamp)<br/> "realTime": 1556083680, (UNIX-Timestamp, optional, default == scheduledTime)<br/> },<br/> "toStation" : { (Ziel / Checkout)<br/> "name" : "Essen Stadtwald", (Name oder DS100)<br/> "scheduledTime": 1556083980, (UNIX-Timestamp)<br/> "realTime": 1556083980, (UNIX-Timestamp, optional, default == scheduledTime)<br/> },<br/> "route" : [ (optionale Liste mit Unterwegshalten als Name oder DS100, darf keine Stationen vor Checkin oder nach Checkout beinhalten)<br/> "Essen Hbf",<br/> "Essen Süd",<br/> "Essen Stadtwald"<br/> ],<br/> "comment" : "Beliebiger Text" (optionaler Freitext-Kommentar)<br/> } </p> <p> Antwort bei Erfolg (der Inhalt von "result" ist von dryRun unabhängig): </p> <p style="font-family: Monospace;"> {<br/> "success" : True,<br/> "id" : 1234, (ID der eingetragenen Zugfahrt)<br/> "result" : { ... } (Eingetragene Daten, Inhalt ist variabel)<br/> } </p> <p> Antwort bei Fehler: </p> <p style="font-family: Monospace;"> {<br/> "success" : False,<br/> "error" : "Begründung"<br/> } </p> </div> </div> -->