Unverified Commit a9b5a189 authored by Birte Kristina Friesel's avatar Birte Kristina Friesel
Browse files

Preliminary DBRIS support (not user-accessible yet)

working:
* checkin
* checkout
* realtime data
* polylines
* carriage formation (long-distance only)

to do:
* geolocation
* redirects after checkout / undo
* traewelling sync
* use dbris by default
parent 5ef9ef68
Loading
Loading
Loading
Loading
+165 −5
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ use List::UtilsBy qw(uniq_by);
use List::MoreUtils qw(first_index);
use Travel::Status::DE::DBRIS::Formation;
use Travelynx::Helper::DBDB;
use Travelynx::Helper::DBRIS;
use Travelynx::Helper::HAFAS;
use Travelynx::Helper::IRIS;
use Travelynx::Helper::Sendmail;
@@ -216,6 +217,19 @@ sub startup {
		}
	);

	$self->helper(
		dbris => sub {
			my ($self) = @_;
			state $dbris = Travelynx::Helper::DBRIS->new(
				log        => $self->app->log,
				cache      => $self->app->cache_iris_rt,
				root_url   => $self->base_url_for('/')->to_abs,
				user_agent => $self->ua,
				version    => $self->app->config->{version},
			);
		}
	);

	$self->helper(
		hafas => sub {
			my ($self) = @_;
@@ -452,6 +466,9 @@ sub startup {
				return Mojo::Promise->reject('You are already checked in');
			}

			if ( $opt{dbris} ) {
				return $self->_checkin_dbris_p(%opt);
			}
			if ( $opt{hafas} ) {
				return $self->_checkin_hafas_p(%opt);
			}
@@ -530,6 +547,146 @@ sub startup {
		}
	);

	$self->helper(
		'_checkin_dbris_p' => sub {
			my ( $self, %opt ) = @_;

			my $station  = $opt{station};
			my $train_id = $opt{train_id};
			my $ts       = $opt{ts};
			my $uid      = $opt{uid} // $self->current_user->{id};
			my $db       = $opt{db}  // $self->pg->db;
			my $hafas;

			my $promise = Mojo::Promise->new;

			$self->dbris->get_journey_p(
				trip_id       => $train_id,
				with_polyline => 1
			)->then(
				sub {
					my ($journey) = @_;
					my $found;
					for my $stop ( $journey->route ) {
						if ( $stop->eva eq $station ) {
							$found = $stop;

							# Lines may serve the same stop several times.
							# Keep looking until the scheduled departure
							# matches the one passed while checking in.
							if ( $ts and $stop->sched_dep->epoch == $ts ) {
								last;
							}
						}
					}
					if ( not $found ) {
						$promise->reject(
"Did not find stop '$station' within journey '$train_id'"
						);
						return;
					}
					for my $stop ( $journey->route ) {
						$self->stations->add_or_update(
							stop  => $stop,
							db    => $db,
							dbris => 'bahn.de',
						);
					}
					eval {
						$self->in_transit->add(
							uid        => $uid,
							db         => $db,
							journey    => $journey,
							stop       => $found,
							data       => { trip_id => $train_id },
							backend_id => $self->stations->get_backend_id(
								dbris => 'bahn.de'
							),
						);
					};
					if ($@) {
						$self->app->log->error(
							"Checkin($uid): INSERT failed: $@");
						$promise->reject( 'INSERT failed: ' . $@ );
						return;
					}

					my $polyline;
					if ( $journey->polyline ) {
						my @station_list;
						my @coordinate_list;
						for my $coord ( $journey->polyline ) {
							if ( $coord->{stop} ) {
								push(
									@coordinate_list,
									[
										$coord->{lon}, $coord->{lat},
										$coord->{stop}->eva
									]
								);
								push( @station_list, $coord->{stop}->name );
							}
							else {
								push( @coordinate_list,
									[ $coord->{lon}, $coord->{lat} ] );
							}
						}

						# equal length → polyline only consists of straight
						# lines between stops. that's not helpful.
						if ( @station_list == @coordinate_list ) {
							$self->log->debug( 'Ignoring polyline for '
								  . $journey->train
								  . ' as it only consists of straight lines between stops.'
							);
						}
						else {
							$polyline = {
								from_eva => ( $journey->route )[0]->eva,
								to_eva   => ( $journey->route )[-1]->eva,
								coords   => \@coordinate_list,
							};
						}
					}

					if ($polyline) {
						$self->in_transit->set_polyline(
							uid      => $uid,
							db       => $db,
							polyline => $polyline,
						);
					}

					# mustn't be called during a transaction
					if ( not $opt{in_transaction} ) {
						$self->run_hook( $uid, 'checkin' );
						$self->add_wagonorder(
							uid          => $uid,
							train_id     => $train_id,
							is_departure => 1,
							eva          => $found->eva,
							datetime     => $found->sched_dep,
							train_type   => $journey->type,
							train_no     => $journey->number
						);
						$self->add_stationinfo( $uid, 1, $train_id,
							$found->eva );
					}

					$promise->resolve($journey);
				}
			)->catch(
				sub {
					my ($err) = @_;
					$promise->reject($err);
					return;
				}
			)->wait;

			return $promise;
		}
	);

	$self->helper(
		'_checkin_hafas_p' => sub {
			my ( $self, %opt ) = @_;
@@ -799,8 +956,8 @@ sub startup {
				return $promise->resolve( 0, 'race condition' );
			}

			if ( $user->{is_hafas} ) {
				return $self->_checkout_hafas_p(%opt);
			if ( $user->{is_dbris} or $user->{is_hafas} ) {
				return $self->_checkout_journey_p(%opt);
			}

			my $now     = DateTime->now( time_zone => 'Europe/Berlin' );
@@ -1049,7 +1206,7 @@ sub startup {
	);

	$self->helper(
		'_checkout_hafas_p' => sub {
		'_checkout_journey_p' => sub {
			my ( $self, %opt ) = @_;

			my $station = $opt{station};
@@ -1840,6 +1997,7 @@ sub startup {
					cancellation    => $latest_cancellation,
					backend_id      => $latest->{backend_id},
					backend_name    => $latest->{backend_name},
					is_dbris        => $latest->{is_dbris},
					is_iris         => $latest->{is_iris},
					is_hafas        => $latest->{is_hafas},
					journey_id      => $latest->{journey_id},
@@ -1902,7 +2060,9 @@ sub startup {
				comment => $status->{comment},
				backend => {
					id => $status->{backend_id},
					type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS',
					type => $status->{ds_dbris} ? 'DBRIS'
					: $status->{is_hafas} ? 'HAFAS'
					: 'IRIS-TTS',
					name => $status->{backend_name},
				},
				fromStation => {
+153 −0
Original line number Diff line number Diff line
@@ -2701,6 +2701,159 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;}
			}
		);
	},

	# v59 -> v60
	# Add bahn.de / DBRIS backend
	sub {
		my ($db) = @_;
		$db->insert(
			'backends',
			{
				iris  => 0,
				hafas => 0,
				efa   => 0,
				ris   => 1,
				name  => 'bahn.de',
			},
		);
		$db->query(
			qq{
				update schema_version set version = 60;
			}
		);
	},

	# v60 -> v61
	# Rename "ris" / "is_ris" to "dbris" / "is_dbris", as it is DB-specific
	sub {
		my ($db) = @_;
		$db->query(
			qq{
				drop view in_transit_str;
				drop view journeys_str;
				drop view users_with_backend;
				drop view follows_in_transit;
				alter table backends rename column ris to dbris;
				create view in_transit_str as select
					user_id,
					backend.iris as is_iris, backend.hafas as is_hafas,
					backend.efa as is_efa, backend.dbris as is_dbris,
					backend.name as backend_name, in_transit.backend_id as backend_id,
					train_type, train_line, train_no, train_id,
					extract(epoch from checkin_time) as checkin_ts,
					extract(epoch from sched_departure) as sched_dep_ts,
					extract(epoch from real_departure) as real_dep_ts,
					checkin_station_id as dep_eva,
					dep_station.ds100 as dep_ds100,
					dep_station.name as dep_name,
					dep_station.lat as dep_lat,
					dep_station.lon as dep_lon,
					extract(epoch from checkout_time) as checkout_ts,
					extract(epoch from sched_arrival) as sched_arr_ts,
					extract(epoch from real_arrival) as real_arr_ts,
					checkout_station_id as arr_eva,
					arr_station.ds100 as arr_ds100,
					arr_station.name as arr_name,
					arr_station.lat as arr_lat,
					arr_station.lon as arr_lon,
					polyline_id,
					polylines.polyline as polyline,
					visibility,
					coalesce(visibility, users.public_level & 127) as effective_visibility,
					cancelled, route, messages, user_data,
					dep_platform, arr_platform, data
					from in_transit
					left join polylines on polylines.id = polyline_id
					left join users on users.id = user_id
					left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source
					left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source
					left join backends as backend on in_transit.backend_id = backend.id
					;
				create view journeys_str as select
					journeys.id as journey_id, user_id,
					backend.iris as is_iris, backend.hafas as is_hafas,
					backend.efa as is_efa, backend.dbris as is_dbris,
					backend.name as backend_name, journeys.backend_id as backend_id,
					train_type, train_line, train_no, train_id,
					extract(epoch from checkin_time) as checkin_ts,
					extract(epoch from sched_departure) as sched_dep_ts,
					extract(epoch from real_departure) as real_dep_ts,
					checkin_station_id as dep_eva,
					dep_station.ds100 as dep_ds100,
					dep_station.name as dep_name,
					dep_station.lat as dep_lat,
					dep_station.lon as dep_lon,
					extract(epoch from checkout_time) as checkout_ts,
					extract(epoch from sched_arrival) as sched_arr_ts,
					extract(epoch from real_arrival) as real_arr_ts,
					checkout_station_id as arr_eva,
					arr_station.ds100 as arr_ds100,
					arr_station.name as arr_name,
					arr_station.lat as arr_lat,
					arr_station.lon as arr_lon,
					polylines.polyline as polyline,
					visibility,
					coalesce(visibility, users.public_level & 127) as effective_visibility,
					cancelled, edited, route, messages, user_data,
					dep_platform, arr_platform
					from journeys
					left join polylines on polylines.id = polyline_id
					left join users on users.id = user_id
					left join stations as dep_station on checkin_station_id = dep_station.eva and journeys.backend_id = dep_station.source
					left join stations as arr_station on checkout_station_id = arr_station.eva and journeys.backend_id = arr_station.source
					left join backends as backend on journeys.backend_id = backend.id
					;
				create view users_with_backend as select
					users.id as id, users.name as name, status, public_level,
					email, password, registered_at, last_seen,
					deletion_requested, deletion_notified, use_history,
					accept_follows, notifications, profile, backend_id, iris,
					hafas, efa, dbris, backend.name as backend_name
					from users
					left join backends as backend on users.backend_id = backend.id
					;
				create view follows_in_transit as select
					r1.subject_id as follower_id, user_id as followee_id,
					users.name as followee_name,
					train_type, train_line, train_no, train_id,
					backend.iris as is_iris, backend.hafas as is_hafas,
					backend.efa as is_efa, backend.dbris as is_dbris,
					backend.name as backend_name, in_transit.backend_id as backend_id,
					extract(epoch from checkin_time) as checkin_ts,
					extract(epoch from sched_departure) as sched_dep_ts,
					extract(epoch from real_departure) as real_dep_ts,
					checkin_station_id as dep_eva,
					dep_station.ds100 as dep_ds100,
					dep_station.name as dep_name,
					dep_station.lat as dep_lat,
					dep_station.lon as dep_lon,
					extract(epoch from checkout_time) as checkout_ts,
					extract(epoch from sched_arrival) as sched_arr_ts,
					extract(epoch from real_arrival) as real_arr_ts,
					checkout_station_id as arr_eva,
					arr_station.ds100 as arr_ds100,
					arr_station.name as arr_name,
					arr_station.lat as arr_lat,
					arr_station.lon as arr_lon,
					polyline_id,
					polylines.polyline as polyline,
					visibility,
					coalesce(visibility, users.public_level & 127) as effective_visibility,
					cancelled, route, messages, user_data,
					dep_platform, arr_platform, data
					from in_transit
					left join polylines on polylines.id = polyline_id
					left join users on users.id = user_id
					left join relations as r1 on r1.predicate = 1 and r1.object_id = user_id
					left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source
					left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source
					left join backends as backend on in_transit.backend_id = backend.id
					order by checkin_time desc
					;
				update schema_version set version = 61;
			}
		);
	},
);

sub sync_stations {
+105 −0
Original line number Diff line number Diff line
@@ -49,6 +49,111 @@ sub run {
		my $arr      = $entry->{arr_eva};
		my $train_id = $entry->{train_id};

		if ( $entry->{is_dbris} ) {

			eval {

				$self->app->dbris->get_journey_p( trip_id => $train_id )->then(
					sub {
						my ($journey) = @_;

						my $found_dep;
						my $found_arr;
						for my $stop ( $journey->route ) {
							if ( $stop->eva == $dep ) {
								$found_dep = $stop;
							}
							if ( $arr and $stop->eva == $arr ) {
								$found_arr = $stop;
								last;
							}
						}
						if ( not $found_dep ) {
							$self->app->log->debug(
								"Did not find $dep within journey $train_id");
							return;
						}

						if ( $found_dep->rt_dep ) {
							$self->app->in_transit->update_departure_dbris(
								uid      => $uid,
								journey  => $journey,
								stop     => $found_dep,
								dep_eva  => $dep,
								arr_eva  => $arr,
								train_id => $train_id,
							);
						}
						if (    $found_dep->sched_dep
							and $found_dep->dep->epoch > $now->epoch )
						{
							$self->app->add_wagonorder(
								uid          => $uid,
								train_id     => $train_id,
								is_departure => 1,
								eva          => $dep,
								datetime     => $found_dep->sched_dep,
								train_type   => $journey->type,
								train_no     => $journey->number,
							);
							$self->app->add_stationinfo( $uid, 1,
								$train_id, $found_dep->eva );
						}

						if ( $found_arr and $found_arr->rt_arr ) {
							$self->app->in_transit->update_arrival_dbris(
								uid     => $uid,
								journey => $journey,
								stop    => $found_arr,
								dep_eva => $dep,
								arr_eva => $arr
							);
							if ( $found_arr->arr->epoch - $now->epoch < 600 ) {
								$self->app->add_wagonorder(
									uid        => $uid,
									train_id   => $train_id,
									is_arrival => 1,
									eva        => $arr,
									datetime   => $found_arr->sched_dep,
									train_type => $journey->type,
									train_no   => $journey->number,
								);
								$self->app->add_stationinfo( $uid, 0,
									$train_id, $found_dep->eva,
									$found_arr->eva );
							}
						}
					}
				)->catch(
					sub {
						my ($err) = @_;
						$self->app->log->error(
"work($uid) @ DBRIS $entry->{backend_name}: journey: $err"
						);
					}
				)->wait;

				if (    $arr
					and $entry->{real_arr_ts}
					and $now->epoch - $entry->{real_arr_ts} > 600 )
				{
					$self->app->checkout_p(
						station => $arr,
						force   => 2,
						dep_eva => $dep,
						arr_eva => $arr,
						uid     => $uid
					)->wait;
				}
			};
			if ($@) {
				$errors += 1;
				$self->app->log->error(
					"work($uid) @ DBRIS $entry->{backend_name}: $@");
			}
			next;
		}

		if ( $entry->{is_hafas} ) {

			eval {
+12 −7
Original line number Diff line number Diff line
@@ -1066,9 +1066,17 @@ sub backend_form {
		if ( $backend->{iris} ) {
			$type                = 'IRIS-TTS';
			$backend->{name}     = 'IRIS';
			$backend->{longname} = 'Deutsche Bahn (IRIS-TTS)';
			$backend->{longname} = 'Deutsche Bahn: IRIS-TTS';
			$backend->{homepage} = 'https://www.bahn.de';
		}
		elsif ( $backend->{dbris} ) {
			$type                = 'DBRIS';
			$backend->{longname} = 'Deutsche Bahn: bahn.de';
			$backend->{homepage} = 'https://www.bahn.de';

			# not ready for production yet
			$type = undef;
		}
		elsif ( $backend->{hafas} ) {

			# These backends lack a journey endpoint or are no longer
@@ -1135,14 +1143,11 @@ sub backend_form {
		$backend->{type} = $type;
	}

	# These backends lack a journey endpoint and are useless for travelynx
	@backends
	  = grep { $_->{name} ne 'Resrobot' and $_->{name} ne 'TPG' } @backends;

	my $iris = shift @backends;

	@backends
	  = sort { $a->{name} cmp $b->{name} } grep { $_->{type} } @backends;
	@backends = map { $_->[1] }
	  sort { $a->[0] cmp $b->[0] }
	  map { [ lc( $_->{name} ), $_ ] } grep { $_->{type} } @backends;

	unshift( @backends, $iris );

+91 −12
Original line number Diff line number Diff line
@@ -42,6 +42,13 @@ sub get_connecting_trains_p {

	my $promise = Mojo::Promise->new;

	if ( $user->{backend_dbris} ) {

		# We do get a little bit of via information, so this might work in some
		# cases. But not reliably. Probably best to leave it out entirely then.
		return $promise->reject;
	}

	if ( $opt{eva} ) {
		if ( $use_history & 0x01 ) {
			$eva = $opt{eva};
@@ -106,6 +113,8 @@ sub get_connecting_trains_p {
	my $iris_promise = Mojo::Promise->new;
	my %via_count    = map { $_->{name} => 0 } @destinations;

	my $backend
	  = $self->stations->get_backend( backend_id => $opt{backend_id} );
	if ( $opt{backend_id} == 0 ) {
		$self->iris->get_departures_p(
			station      => $eva,
@@ -260,9 +269,11 @@ sub get_connecting_trains_p {
			}
		)->wait;
	}
	else {
		my $hafas_service
		  = $self->stations->get_hafas_name( backend_id => $opt{backend_id} );
	elsif ( $backend->{dbris} ) {
		...;
	}
	elsif ( $backend->{hafas} ) {
		my $hafas_service = $backend->{name};
		$self->hafas->get_departures_p(
			service    => $hafas_service,
			eva        => $eva,
@@ -524,10 +535,19 @@ sub geolocation {
		return;
	}

	my $hafas_service
	  = $self->stations->get_hafas_name( backend_id => $backend_id );
	my ( $dbris_service, $hafas_service );
	my $backend = $self->stations->get_backend( backend_id => $backend_id );
	if ( $backend->{dbris} ) {
		$dbris_service = $backend->{name};
	}
	elsif ( $backend->{hafas} ) {
		$hafas_service = $backend->{name};
	}

	if ($hafas_service) {
	if ($dbris_service) {
		...;
	}
	elsif ($hafas_service) {
		$self->render_later;

		my $agent = $self->ua;
@@ -588,6 +608,7 @@ sub geolocation {
			lon      => $_->[0][3],
			lat      => $_->[0][4],
			distance => $_->[1],
			dbris    => 0,
			hafas    => 0,
		}
	} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
@@ -656,6 +677,7 @@ sub travel_action {
		$promise->then(
			sub {
				return $self->checkin_p(
					dbris    => $params->{dbris},
					hafas    => $params->{hafas},
					station  => $params->{station},
					train_id => $params->{train},
@@ -687,7 +709,10 @@ sub travel_action {
				my ( $still_checked_in, undef ) = @_;
				if ( my $destination = $params->{dest} ) {
					my $station_link = '/s/' . $destination;
					if ( $status->{is_hafas} ) {
					if ( $status->{is_dbris} ) {
						$station_link .= '?dbris=' . $status->{backend_name};
					}
					elsif ( $status->{is_hafas} ) {
						$station_link .= '?hafas=' . $status->{backend_name};
					}
					$self->render(
@@ -723,7 +748,10 @@ sub travel_action {
			sub {
				my ( $still_checked_in, $error ) = @_;
				my $station_link = '/s/' . $params->{station};
				if ( $status->{is_hafas} ) {
				if ( $status->{is_dbris} ) {
					$station_link .= '?dbris=' . $status->{backend_name};
				}
				elsif ( $status->{is_hafas} ) {
					$station_link .= '?hafas=' . $status->{backend_name};
				}

@@ -774,7 +802,14 @@ sub travel_action {
		else {
			my $redir = '/';
			if ( $status->{checked_in} or $status->{cancelled} ) {
				if ( $status->{is_hafas} ) {
				if ( $status->{is_dbris} ) {
					$redir
					  = '/s/'
					  . $status->{dep_eva}
					  . '?dbris='
					  . $status->{backend_name};
				}
				elsif ( $status->{is_hafas} ) {
					$redir
					  = '/s/'
					  . $status->{dep_eva}
@@ -796,6 +831,7 @@ sub travel_action {
	elsif ( $params->{action} eq 'cancelled_from' ) {
		$self->render_later;
		$self->checkin_p(
			dbris    => $params->{dbris},
			hafas    => $params->{hafas},
			station  => $params->{station},
			train_id => $params->{train},
@@ -925,10 +961,19 @@ sub station {
		$timestamp = DateTime->now( time_zone => 'Europe/Berlin' );
	}

	my $dbris_service = $self->param('dbris')
	  // ( $user->{backend_dbris} ? $user->{backend_name} : undef );
	my $hafas_service = $self->param('hafas')
	  // ( $user->{backend_hafas} ? $user->{backend_name} : undef );
	my $promise;
	if ($hafas_service) {
	if ($dbris_service) {
		$promise = $self->dbris->get_departures_p(
			station    => $station,
			timestamp  => $timestamp,
			lookbehind => 30,
		);
	}
	elsif ($hafas_service) {
		$promise = $self->hafas->get_departures_p(
			service    => $hafas_service,
			eva        => $station,
@@ -954,7 +999,22 @@ sub station {
			my $now_within_range
			  = abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0;

			if ($hafas_service) {
			if ($dbris_service) {

				@results = map { $_->[0] }
				  sort { $b->[1] <=> $a->[1] }
				  map { [ $_, $_->dep->epoch ] } $status->results;

				$status = {
					station_eva      => $station,
					related_stations => [],
				};

				if ( $station =~ m{ [@] O = (?<name> [^@]+ ) [@] }x ) {
					$status->{station_name} = $+{name};
				}
			}
			elsif ($hafas_service) {

				@results = map { $_->[0] }
				  sort { $b->[1] <=> $a->[1] }
@@ -1039,6 +1099,7 @@ sub station {
						$self->render(
							'departures',
							user              => $user,
							dbris             => $dbris_service,
							hafas             => $hafas_service,
							eva               => $status->{station_eva},
							datetime          => $timestamp,
@@ -1058,6 +1119,7 @@ sub station {
						$self->render(
							'departures',
							user             => $user,
							dbris            => $dbris_service,
							hafas            => $hafas_service,
							eva              => $status->{station_eva},
							datetime         => $timestamp,
@@ -1076,6 +1138,7 @@ sub station {
				$self->render(
					'departures',
					user             => $user,
					dbris            => $dbris_service,
					hafas            => $hafas_service,
					eva              => $status->{station_eva},
					datetime         => $timestamp,
@@ -1174,8 +1237,24 @@ sub redirect_to_station {
	my ($self) = @_;
	my $station = $self->param('station');

	if ( $self->param('backend_dbris') ) {
		$self->render_later;
		$self->dbris->get_station_id_p($station)->then(
			sub {
				my ($dbris_station) = @_;
				$self->redirect_to( '/s/' . $dbris_station->{id} );
			}
		)->catch(
			sub {
				my ($err) = @_;
				$self->redirect_to('/');
			}
		)->wait;
	}
	else {
		$self->redirect_to("/s/${station}");
	}
}

sub cancelled {
	my ($self) = @_;
Loading