diff options
author | Daniel Friesel <derf@finalrewind.org> | 2019-12-14 20:46:02 +0100 |
---|---|---|
committer | Daniel Friesel <derf@finalrewind.org> | 2019-12-14 20:46:02 +0100 |
commit | 5fe4174febbf833e0fdbfd5f1500883720c420df (patch) | |
tree | 1fdd5cb327f639f5a0548f9aecdb016998a22b13 | |
parent | 7e9a2ebfef85a0ac45179e4bb120f69a7bdc2614 (diff) |
Add API entry point for journey import
-rwxr-xr-x | lib/Travelynx.pm | 8 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Api.pm | 167 | ||||
-rw-r--r-- | templates/account.html.ep | 27 | ||||
-rw-r--r-- | templates/api_documentation.html.ep | 60 |
4 files changed, 249 insertions, 13 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index cc0f81d..3abe949 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -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)]; } ); @@ -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} ); @@ -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 ($@) { @@ -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'); diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index a442784..84e1507 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -1,6 +1,7 @@ package Travelynx::Controller::Api; use Mojo::Base 'Mojolicious::Controller'; +use DateTime; use Travel::Status::DE::IRIS::Stations; use UUID::Tiny qw(:std); @@ -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) = @_; @@ -71,9 +83,9 @@ sub get_v0 { or $status->{cancelled} ) ? \1 : \0, station => { - ds100 => $status->{arr_ds100} // $status->{dep_ds100}, - name => $status->{arr_name} // $status->{dep_name}, - uic => $station_eva, + ds100 => $status->{arr_ds100} // $status->{dep_ds100}, + name => $status->{arr_name} // $status->{dep_name}, + uic => $station_eva, longitude => $station_lon, latitude => $station_lat, }, @@ -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') ) { diff --git a/templates/account.html.ep b/templates/account.html.ep index b3ec52a..a9f1bb0 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -169,7 +169,7 @@ </button> %= end </td> - </tr> + </tr>--> <tr> <th scope="row">Travel</th> <td> @@ -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> diff --git a/templates/api_documentation.html.ep b/templates/api_documentation.html.ep index dd59be6..de0231d 100644 --- a/templates/api_documentation.html.ep +++ b/templates/api_documentation.html.ep @@ -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/> @@ -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> ---> |