Commit 46fc64de authored by Birte Kristina Friesel's avatar Birte Kristina Friesel
Browse files

Add travel (checkin/checkout/undo) API

parent 5fe4174f
Loading
Loading
Loading
Loading
+26 −22
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ use DateTime::Format::Strptime;
use Encode qw(decode encode);
use Geo::Distance;
use JSON;
use List::Util qw(first);
use List::Util;
use List::MoreUtils qw(after_incl before_incl);
use Travel::Status::DE::DBWagenreihung;
use Travel::Status::DE::IRIS;
@@ -158,14 +158,14 @@ sub startup {
			return {
				status  => 1,
				history => 2,
				action  => 3,
				travel  => 3,
				import  => 4,
			};
		}
	);
	$self->attr(
		token_types => sub {
			return [qw(status history action import)];
			return [qw(status history travel import)];
		}
	);

@@ -425,21 +425,23 @@ sub startup {

	$self->helper(
		'checkin' => sub {
			my ( $self, $station, $train_id ) = @_;
			my ( $self, $station, $train_id, $uid ) = @_;

			$uid //= $self->current_user->{id};

			my $status = $self->get_departures( $station, 140, 40, 0 );
			if ( $status->{errstr} ) {
				return ( undef, $status->{errstr} );
			}
			else {
				my ($train)
				  = first { $_->train_id eq $train_id } @{ $status->{results} };
				my ($train) = List::Util::first { $_->train_id eq $train_id }
				@{ $status->{results} };
				if ( not defined $train ) {
					return ( undef, "Train ${train_id} not found" );
				}
				else {

					my $user = $self->get_user_status;
					my $user = $self->get_user_status($uid);
					if ( $user->{checked_in} or $user->{cancelled} ) {

						if (    $user->{train_id} eq $train_id
@@ -450,7 +452,7 @@ sub startup {
						}

						# Otherwise, someone forgot to check out first
						$self->checkout( $station, 1 );
						$self->checkout( $station, 1, $uid );
					}

					eval {
@@ -458,7 +460,7 @@ sub startup {
						$self->pg->db->insert(
							'in_transit',
							{
								user_id   => $self->current_user->{id},
								user_id   => $uid,
								cancelled => $train->departure_is_cancelled
								? 1
								: 0,
@@ -488,14 +490,12 @@ sub startup {
						);
					};
					if ($@) {
						my $uid = $self->current_user->{id};
						$self->app->log->error(
							"Checkin($uid): INSERT failed: $@");
						return ( undef, 'INSERT failed: ' . $@ );
					}
					$self->add_route_timestamps( $self->current_user->{id},
						$train, 1 );
					$self->run_hook( $self->current_user->{id}, 'checkin' );
					$self->add_route_timestamps( $uid, $train, 1 );
					$self->run_hook( $uid, 'checkin' );
					return ( $train, undef );
				}
			}
@@ -504,8 +504,8 @@ sub startup {

	$self->helper(
		'undo' => sub {
			my ( $self, $journey_id ) = @_;
			my $uid = $self->current_user->{id};
			my ( $self, $journey_id, $uid ) = @_;
			$uid //= $self->current_user->{id};

			if ( $journey_id eq 'in_transit' ) {
				eval {
@@ -627,8 +627,8 @@ sub startup {
			my $journey
			  = $db->select( 'in_transit', '*', { user_id => $uid } )
			  ->expand->hash;
			my ($train)
			  = first { $_->train_id eq $train_id } @{ $status->{results} };
			my ($train) = List::Util::first { $_->train_id eq $train_id }
			@{ $status->{results} };

          # When a checkout is triggered by a checkin, there is an edge case
          # with related stations.
@@ -641,8 +641,8 @@ sub startup {
          # well.
			if ( not $train ) {
				$status = $self->get_departures( $station, 120, 180, 1 );
				($train)
				  = first { $_->train_id eq $train_id } @{ $status->{results} };
				($train) = List::Util::first { $_->train_id eq $train_id }
				@{ $status->{results} };
			}

			# Store the intended checkout station regardless of this operation's
@@ -681,8 +681,11 @@ sub startup {

               # Arrival time via IRIS is unknown, so the train probably has not
               # arrived yet. Fall back to HAFAS.
				if ( my $station_data
					= first { $_->[0] eq $station } @{ $journey->{route} } )
				if (
					my $station_data
					= List::Util::first { $_->[0] eq $station }
					@{ $journey->{route} }
				  )
				{
					$station_data = $station_data->[1];
					if ( $station_data->{sched_arr} ) {
@@ -784,7 +787,7 @@ sub startup {
				return ( 0, undef );
			}
			$self->run_hook( $uid, 'update' );
			$self->add_route_timestamps( $self->current_user->{id}, $train, 0 );
			$self->add_route_timestamps( $uid, $train, 0 );
			return ( 1, undef );
		}
	);
@@ -3234,6 +3237,7 @@ sub startup {
	$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('/api/v1/travel')->to('api#travel_v1');
	$r->post('/action')->to('traveling#log_action');
	$r->post('/geolocation')->to('traveling#geolocation');
	$r->post('/list_departures')->to('traveling#redirect_to_station');
+161 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ package Travelynx::Controller::Api;
use Mojo::Base 'Mojolicious::Controller';

use DateTime;
use List::Util;
use Travel::Status::DE::IRIS::Stations;
use UUID::Tiny qw(:std);

@@ -165,6 +166,166 @@ sub get_v1 {
	}
}

sub travel_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->{'travel'} ) {
		$self->render(
			json => {
				success => \0,
				error   => 'Invalid token',
			},
		);
		return;
	}

	if ( not exists $payload->{action}
		or $payload->{action} !~ m{^(checkin|checkout|undo)$} )
	{
		$self->render(
			json => {
				success => \0,
				error   => 'Missing or invalid action',
			},
		);
		return;
	}

	if ( $payload->{action} eq 'checkin' ) {
		my $from_station = sanitize( q{}, $payload->{fromStation} );
		my $to_station   = sanitize( q{}, $payload->{toStation} );
		my $train_id;

		if ( exists $payload->{train}{id} ) {
			$train_id = sanitize( 0, $payload->{train}{id} );
		}
		else {
			my $train_type = sanitize( q{}, $payload->{train}{type} );
			my $train_no   = sanitize( q{}, $payload->{train}{no} );
			my $status = $self->get_departures( $from_station, 140, 40, 0 );
			if ( $status->{errstr} ) {
				$self->render(
					json => {
						success => \0,
						error   => 'Fehler am Abfahrtsbahnhof: '
						  . $status->{errstr},
						status => $self->get_user_status_json_v1($uid)
					}
				);
				return;
			}
			my ($train) = List::Util::first {
				$_->type eq $train_type and $_->train_no eq $train_no
			}
			@{ $status->{results} };
			if ( not defined $train ) {
				$self->render(
					json => {
						success => \0,
						error   => 'Fehler am Abfahrtsbahnhof: '
						  . $status->{errstr},
						status => $self->get_user_status_json_v1($uid)
					}
				);
				return;
			}
			$train_id = $train->train_id;
		}

		my ( $train, $error )
		  = $self->checkin( $from_station, $train_id, $uid );
		if ( $to_station and not $error ) {
			( $train, $error ) = $self->checkout( $to_station, 0, $uid );
		}
		if ($error) {
			$self->render(
				json => {
					success => \0,
					error   => $error,
					status  => $self->get_user_status_json_v1($uid)
				}
			);
		}
		else {
			$self->render(
				json => {
					success => \1,
					status  => $self->get_user_status_json_v1($uid)
				}
			);
		}
	}
	elsif ( $payload->{action} eq 'checkout' ) {
		my $to_station = sanitize( q{}, $payload->{toStation} );

		my ( $train, $error )
		  = $self->checkout( $to_station, $payload->{force} ? 1 : 0, $uid );
		if ($error) {
			$self->render(
				json => {
					success => \0,
					error   => $error,
					status  => $self->get_user_status_json_v1($uid)
				}
			);
		}
		else {
			$self->render(
				json => {
					success => \1,
					status  => $self->get_user_status_json_v1($uid)
				}
			);
		}
	}
	elsif ( $payload->{action} eq 'undo' ) {
		my $error = $self->undo( 'in_transit', $uid );
		if ($error) {
			$self->render(
				json => {
					success => \0,
					error   => $error,
					status  => $self->get_user_status_json_v1($uid)
				}
			);
		}
		else {
			$self->render(
				json => {
					success => \1,
					status  => $self->get_user_status_json_v1($uid)
				}
			);
		}
	}
}

sub import_v1 {
	my ($self) = @_;

+1 −1
Original line number Diff line number Diff line
@@ -183,7 +183,7 @@
				<td>
					%= form_for 'set_token' => begin
						%= csrf_field
						%= hidden_field 'token' => 'action'
						%= hidden_field 'token' => 'travel'
						<button class="btn waves-effect waves-light" type="submit" name="action" value="generate">
							Generieren
						</button>
+58 −6
Original line number Diff line number Diff line
@@ -64,17 +64,69 @@
		</p>
	</div>
</div>
<!--
<h3>History</h3>

<h2>Travel</h2>
<div class="row">
	<div class="col s12">
		<p>
			Coming soon.
			Checkin per API. Sobald eine Zielstation bekannt ist, erfolgt der
			Checkout wie beim Webinterface automatisch zehn Minuten nach Ankunft.
		</p>
		<p style="font-family: Monospace;">
			curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>"}' <%= $api_root %>/travel
		</p>
		<p>Payload zum Einchecken, optional mit Zielwahl:</p>
		<p style="font-family: Monospace;">
		{<br/>
			"token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
			"action" : "checkin",<br/>
			"train" : {<br/>
				"type" : "ICE",<br/>
				"no" : "1234",<br/>
			}<br/>
			"fromStation" : "Essen Hbf", (DS100 oder EVA-Nummer sind ebenfalls möglich)<br/>
			"toStation" : "Berlin Hbf" (optional, DS100 oder EVA-Nummer sind ebenfalls möglich)<br/>
		}
		</p>
		<p>Payload zur Wahl eines neuen Ziels, wenn bereits eingecheckt:</p>
		<p style="font-family: Monospace;">
		{<br/>
			"token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
			"action" : "checkout",<br/>
			"force" : True/False, (wenn True: Checkout jetzt durchführen und auftretende Fehler ignorieren. Kann zu Logeinträgen ohne Ankunftsdaten führen.)<br/>
			"toStation" : "Berlin Hbf" (DS100 oder EVA-Nummer sind ebenfalls möglich)<br/>
		}
		</p>
		<p>Payload zum Rückgängigmachen eines Checkins (nur während der Fahrt möglich):</p>
		<p style="font-family: Monospace;">
		{<br/>
			"token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/>
			"action" : "undo"<br/>
		}
		</p>
		<p>
			Antwort bei Erfolg:
		</p>
		<p style="font-family: Monospace;">
		{<br/>
			"success" : True,<br/>
			"status" : { aktueller Nutzerstatus gemäß Status-API }<br/>
		}
		</p>
		<p>
			Antwort bei Fehler:
		</p>
		<p style="font-family: Monospace;">
		{<br/>
			"success" : False,<br/>
			"error" : "Begründung",<br/>
			"status" : { aktueller Nutzerstatus gemäß Status-API }<br/>
		}
		</p>
	</div>
</div>-->
</div>

<h3>Import</h3>
<h2>Import</h2>
<div class="row">
	<div class="col s12">
		<p>
@@ -86,7 +138,7 @@
		<p>Payload (alle nicht als optional markierten Felder sind Pflicht):</p>
		<p style="font-family: Monospace;">
		{<br/>
			"token" : "<%= $token->{import} // 'TOKEN' %>",<br/>
			"token" : "<%= $uid %>-<%= $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/>