From 5fe4174febbf833e0fdbfd5f1500883720c420df Mon Sep 17 00:00:00 2001
From: Daniel Friesel
Date: Sat, 14 Dec 2019 20:46:02 +0100
Subject: [PATCH] Add API entry point for journey import
---
lib/Travelynx.pm | 8 +-
lib/Travelynx/Controller/Api.pm | 167 +++++++++++++++++++++++++++-
templates/account.html.ep | 27 ++++-
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{ ^ (? \d+ ) - (? .* ) $ }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 @@
%= end
-
+ -->
Travel |
@@ -192,7 +192,30 @@
%= end
|
-
-->
+
+
+ Import |
+
+ % if ($token->{import}) {
+ %= $acc->{id} . '-' . $token->{import}
+ % }
+ % else {
+ —
+ % }
+ |
+
+ %= form_for 'set_token' => begin
+ %= csrf_field
+ %= hidden_field 'token' => 'import'
+
+
+ %= end
+ |
+
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,
"realTime": 1556083680,
},
- "fromStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)
+ "toStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)
"name" : "Essen Stadtwald",
"ds100" : "EESA",
"uic" : 8001896,
@@ -72,14 +72,64 @@
Coming soon.
-
+-->
-Travel
+Import
- Ein- und Auschecken per API. Coming soon.
+ Manueller Import vergangener Zugfahrten (eine Fahrt pro API-Aufruf).
+
+
+ curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{status} // 'TOKEN' %>"}' <%= $api_root %>/import
+
+
Payload (alle nicht als optional markierten Felder sind Pflicht):
+
+ {
+ "token" : "<%= $token->{import} // 'TOKEN' %>",
+ "dryRun" : True/False, (optional: wenn True, wird die Eingabe validiert, aber keine Zugfahrt angelegt)
+ "cancelled" : True/False, (Zugausfall?)
+ "train" : {
+ "type" : "S", (Zugtyp, z.B. ICE, RE, S)
+ "line" : "6", (Linie als String, bei Zügen ohne Linie wie IC/ICE u.ä. null)
+ "no" : "30634", (Zugnummer als String)
+ },
+ "fromStation" : { (Start / Checkin)
+ "name" : "Essen Hbf", (Name oder DS100)
+ "scheduledTime": 1556083680, (UNIX-Timestamp)
+ "realTime": 1556083680, (UNIX-Timestamp, optional, default == scheduledTime)
+ },
+ "toStation" : { (Ziel / Checkout)
+ "name" : "Essen Stadtwald", (Name oder DS100)
+ "scheduledTime": 1556083980, (UNIX-Timestamp)
+ "realTime": 1556083980, (UNIX-Timestamp, optional, default == scheduledTime)
+ },
+ "route" : [ (optionale Liste mit Unterwegshalten als Name oder DS100, darf keine Stationen vor Checkin oder nach Checkout beinhalten)
+ "Essen Hbf",
+ "Essen Süd",
+ "Essen Stadtwald"
+ ],
+ "comment" : "Beliebiger Text" (optionaler Freitext-Kommentar)
+ }
+
+
+ Antwort bei Erfolg (der Inhalt von "result" ist von dryRun unabhängig):
+
+
+ {
+ "success" : True,
+ "id" : 1234, (ID der eingetragenen Zugfahrt)
+ "result" : { ... } (Eingetragene Daten, Inhalt ist variabel)
+ }
+
+
+ Antwort bei Fehler:
+
+
+ {
+ "success" : False,
+ "error" : "Begründung"
+ }
--->