diff options
| -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> ---> | 
