Add API entry point for journey import

This commit is contained in:
Daniel Friesel 2019-12-14 20:46:02 +01:00
parent 7e9a2ebfef
commit 5fe4174feb
4 changed files with 249 additions and 13 deletions

View file

@ -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');

View file

@ -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) = @_;
@ -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') ) {

View file

@ -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>

View file

@ -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>
-->