summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2019-12-14 20:46:02 +0100
committerDaniel Friesel <derf@finalrewind.org>2019-12-14 20:46:02 +0100
commit5fe4174febbf833e0fdbfd5f1500883720c420df (patch)
tree1fdd5cb327f639f5a0548f9aecdb016998a22b13
parent7e9a2ebfef85a0ac45179e4bb120f69a7bdc2614 (diff)
Add API entry point for journey import
-rwxr-xr-xlib/Travelynx.pm8
-rwxr-xr-xlib/Travelynx/Controller/Api.pm167
-rw-r--r--templates/account.html.ep27
-rw-r--r--templates/api_documentation.html.ep60
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>
--->