diff options
| author | Birte Kristina Friesel <birte.friesel@uos.de> | 2024-07-26 18:55:58 +0200 | 
|---|---|---|
| committer | Birte Kristina Friesel <birte.friesel@uos.de> | 2024-07-26 18:55:58 +0200 | 
| commit | 47f76da4f8cc31146d2834dfdf9731d330288c9d (patch) | |
| tree | 3597f89cfb6b0c3bd881767c098f616e8ee1f1a5 | |
| parent | 7811520a30657e2bc98873f296ae1f8fcff51dc0 (diff) | |
Multi-backend support
Squashed commit of the following:
commit 92518024ba295456358618c0e8180bd8e996fdf1
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:39:46 2024 +0200
    add_or_update station: remove superfluos 'new backend id := old backend id'
commit df21c20c6e4c86454f8a9ac69121404415217f2a
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:35:51 2024 +0200
    revert connection targets min_count to 3
commit be335cef07d0b42874f5fc1de4a1d13396e8e807
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:20:05 2024 +0200
    mention backend selection in API documentation
commit 9f41828fb4f18fd707e0087def3032e8d4c8d7d8
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:19:23 2024 +0200
    use_history: not all backends provide route data in departure monitor
commit 09714b4d89684b8331d0e96f564a4c7432318f70
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:11:44 2024 +0200
    disambiguation: pass correct hafas parameter
commit 8cdf1120fc32155dc6525be64601b7c10a9c7f52
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:11:28 2024 +0200
    _checked_in: hide Zuglauf link for non-db checkins
commit 7455653f541198e0e0a6d11aed421487ffdb6285
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:01:47 2024 +0200
    debug output
commit b9cda07f85601a58ea32dbdacdd5399f302db52b
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 19:09:07 2024 +0200
    fix remaining get_connection_targets / get_connecting_trains_p invocations
commit 2759d7258c37c7498905cfe19f6b4c4f6d16bd21
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Wed Jul 24 20:50:12 2024 +0200
    support non-DB HAFAS backends (WiP)
33 files changed, 936 insertions, 383 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 0c5a5ae..e416be3 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -448,7 +448,7 @@ sub startup {  				return Mojo::Promise->reject('You are already checked in');  			} -			if ( $train_id =~ m{[|]} ) { +			if ( $opt{hafas} ) {  				return $self->_checkin_hafas_p(%opt);  			} @@ -482,7 +482,9 @@ sub startup {  							db            => $db,  							departure_eva => $eva,  							train         => $train, -							route => [ $self->iris->route_diff($train) ], +							route      => [ $self->iris->route_diff($train) ], +							backend_id => +							  $self->stations->get_backend_id( iris => 1 ),  						);  					};  					if ($@) { @@ -530,6 +532,7 @@ sub startup {  			my $promise = Mojo::Promise->new;  			$self->hafas->get_journey_p( +				service       => $opt{hafas},  				trip_id       => $train_id,  				with_polyline => 1  			)->then( @@ -551,17 +554,21 @@ sub startup {  					}  					for my $stop ( $journey->route ) {  						$self->stations->add_or_update( -							stop => $stop, -							db   => $db, +							stop  => $stop, +							db    => $db, +							hafas => $opt{hafas},  						);  					}  					eval {  						$self->in_transit->add( -							uid     => $uid, -							db      => $db, -							journey => $journey, -							stop    => $found, -							data    => { trip_id => $journey->id } +							uid        => $uid, +							db         => $db, +							journey    => $journey, +							stop       => $found, +							data       => { trip_id => $journey->id }, +							backend_id => $self->stations->get_backend_id( +								hafas => $opt{hafas} +							),  						);  					};  					if ($@) { @@ -620,8 +627,8 @@ sub startup {  					# mustn't be called during a transaction  					if ( not $opt{in_transaction} ) {  						$self->run_hook( $uid, 'checkin' ); -						if ( $journey->class <= 16 ) { -							$self->app->add_wagonorder( $uid, 1, $journey->id, +						if ( $opt{hafas} eq 'DB' and $journey->class <= 16 ) { +							$self->add_wagonorder( $uid, 1, $journey->id,  								$found->sched_dep, $journey->number );  							$self->add_stationinfo( $uid, 1, $journey->id,  								$found->loc->eva ); @@ -744,6 +751,7 @@ sub startup {  			my $db           = $opt{db}  // $self->pg->db;  			my $user         = $self->get_user_status( $uid, $db );  			my $train_id     = $user->{train_id}; +			my $hafas        = $opt{hafas};  			my $promise = Mojo::Promise->new; @@ -765,7 +773,7 @@ sub startup {  				return $promise->resolve( 0, 'race condition' );  			} -			if ( $train_id =~ m{[|]} ) { +			if ( $user->{is_hafas} ) {  				return $self->_checkout_hafas_p(%opt);  			} @@ -1736,7 +1744,8 @@ sub startup {  			if ( $latest_cancellation and $latest_cancellation->{cancelled} ) {  				if (  					my $station = $self->stations->get_by_eva( -						$latest_cancellation->{dep_eva} +						$latest_cancellation->{dep_eva}, +						backend_id => $latest_cancellation->{backend_id},  					)  				  )  				{ @@ -1745,7 +1754,8 @@ sub startup {  				}  				if (  					my $station = $self->stations->get_by_eva( -						$latest_cancellation->{arr_eva} +						$latest_cancellation->{arr_eva}, +						backend_id => $latest_cancellation->{backend_id},  					)  				  )  				{ @@ -1760,14 +1770,20 @@ sub startup {  			if ($latest) {  				my $ts          = $latest->{checkout_ts};  				my $action_time = epoch_to_dt($ts); -				if ( my $station -					= $self->stations->get_by_eva( $latest->{dep_eva} ) ) +				if ( +					my $station = $self->stations->get_by_eva( +						$latest->{dep_eva}, backend_id => $latest->{backend_id} +					) +				  )  				{  					$latest->{dep_ds100} = $station->{ds100};  					$latest->{dep_name}  = $station->{name};  				} -				if ( my $station -					= $self->stations->get_by_eva( $latest->{arr_eva} ) ) +				if ( +					my $station = $self->stations->get_by_eva( +						$latest->{arr_eva}, backend_id => $latest->{backend_id} +					) +				  )  				{  					$latest->{arr_ds100} = $station->{ds100};  					$latest->{arr_name}  = $station->{name}; @@ -1776,6 +1792,10 @@ sub startup {  					checked_in      => 0,  					cancelled       => 0,  					cancellation    => $latest_cancellation, +					backend_id      => $latest->{backend_id}, +					backend_name    => $latest->{backend_name}, +					is_iris         => $latest->{is_iris}, +					is_hafas        => $latest->{is_hafas},  					journey_id      => $latest->{journey_id},  					timestamp       => $action_time,  					timestamp_delta => $now->epoch - $action_time->epoch, @@ -1833,7 +1853,12 @@ sub startup {  					     $status->{checked_in}  					  or $status->{cancelled}  				) ? \1 : \0, -				comment     => $status->{comment}, +				comment => $status->{comment}, +				backend => { +					id   => $status->{backend_id}, +					type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS', +					name => $status->{backend_name}, +				},  				fromStation => {  					ds100         => $status->{dep_ds100},  					name          => $status->{dep_name}, @@ -1992,6 +2017,7 @@ sub startup {  "Eingecheckt in $traewelling->{line} nach $traewelling->{arr_name}",  						status_id => $traewelling->{status_id},  					); +  					$self->traewelling->set_latest_pull_status_id(  						uid       => $uid,  						status_id => $traewelling->{status_id}, @@ -2324,6 +2350,7 @@ sub startup {  	$authed_r->get('/account/password')->to('account#password_form');  	$authed_r->get('/account/mail')->to('account#change_mail');  	$authed_r->get('/account/name')->to('account#change_name'); +	$authed_r->get('/account/select_backend')->to('account#backend_form');  	$authed_r->get('/export.json')->to('account#json_export');  	$authed_r->get('/history.json')->to('traveling#json_history');  	$authed_r->get('/history.csv')->to('traveling#csv_history'); @@ -2345,6 +2372,7 @@ sub startup {  	$authed_r->post('/account/hooks')->to('account#webhook');  	$authed_r->post('/account/traewelling')->to('traewelling#settings');  	$authed_r->post('/account/insight')->to('account#insight'); +	$authed_r->post('/account/select_backend')->to('account#change_backend');  	$authed_r->post('/journey/add')->to('traveling#add_journey_form');  	$authed_r->post('/journey/comment')->to('traveling#comment_form');  	$authed_r->post('/journey/visibility')->to('traveling#visibility_form'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index ae17f64..880dfb4 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -8,6 +8,7 @@ use Mojo::Base 'Mojolicious::Command';  use DateTime;  use File::Slurp qw(read_file);  use JSON; +use Travel::Status::DE::HAFAS;  use Travel::Status::DE::IRIS::Stations;  has description => 'Initialize or upgrade database layout'; @@ -1918,7 +1919,7 @@ my @migrations = (  	# v49 -> v50  	# travelynx 2.0 introduced proper HAFAS support, so there is no need for -	# the 'FYI, here is some hAFAS data' kludge anymore. +	# the 'FYI, here is some HAFAS data' kludge anymore.  	sub {  		my ($db) = @_;  		$db->query( @@ -2310,6 +2311,235 @@ my @migrations = (  		);  	}, +	# v54 -> v55 +	# do not share stations between backends +	sub { +		my ($db) = @_; +		$db->query( +			qq{ +				alter table schema_version add column hafas varchar(12); +				alter table users drop column external_services; +				alter table users add column backend_id smallint references backends (id) default 1; +				alter table stations drop constraint stations_pkey; +				alter table stations add unique (eva, source); +				create index eva_by_source on stations (eva, source); +				create index eva on stations (eva); +				alter table related_stations drop constraint related_stations_eva_meta_key; +				drop index rel_eva; +				alter table related_stations add column backend_id smallint; +				update related_stations set backend_id = 1; +				alter table related_stations alter column backend_id set not null; +				alter table related_stations add constraint backend_fk foreign key (backend_id) references backends (id); +				alter table related_stations add unique (eva, meta, backend_id); +				create index related_stations_eva_backend_key on related_stations (eva, backend_id); +			} +		); + +		# up until now, IRIS and DB HAFAS shared stations, with IRIS taking +		# preference.  As of v2.7, this is no longer the case. However, old DB +		# HAFAS journeys may still reference IRIS-specific stations. So, we +		# make all IRIS stations available as DB HAFAS stations as well. +		my $total +		  = $db->select( 'stations', 'count(*) as count', { source => 0 } ) +		  ->hash->{count}; +		my $count = 0; + +		# Caveat: If this is a fresh installation, there are no IRIS stations +		# in the database yet. So we have to populate it first. +		if ( not $total ) { +			say +'Preparing to untangle IRIS / HAFAS stations, this may take a while ...'; +			$total = scalar Travel::Status::DE::IRIS::Stations::get_stations(); +			for my $s ( Travel::Status::DE::IRIS::Stations::get_stations() ) { +				my ( $ds100, $name, $eva, $lon, $lat ) = @{$s}; +				if ( $ENV{__TRAVELYNX_TEST_MINI_IRIS} +					and ( $eva < 8000000 or $eva > 8000100 ) ) +				{ +					next; +				} +				$db->insert( +					'stations', +					{ +						eva      => $eva, +						ds100    => $ds100, +						name     => $name, +						lat      => $lat, +						lon      => $lon, +						source   => 0, +						archived => 0 +					}, +				); +				if ( $count++ % 1000 == 0 ) { +					printf( "    %2.0f%% complete\n", $count * 100 / $total ); +				} +			} +			$count = 0; +		} + +		say 'Untangling IRIS / HAFAS stations, this may take a while ...'; +		my $res = $db->query( +			qq{ +				select eva, ds100, name, lat, lon, archived +				from stations +				where source = 0; +			} +		); +		while ( my $row = $res->hash ) { +			$db->insert( +				'stations', +				{ +					eva      => $row->{eva}, +					ds100    => $row->{ds100}, +					name     => $row->{name}, +					lat      => $row->{lat}, +					lon      => $row->{lon}, +					archived => $row->{archived}, +					source   => 1, +				} +			); +			if ( $count++ % 1000 == 0 ) { +				printf( "    %2.0f%% complete\n", $count * 100 / $total ); +			} +		} +		$db->query( +			qq{ +				alter table in_transit add constraint in_transit_checkin_eva_fk +					foreign key (checkin_station_id, backend_id) +					references stations (eva, source); +				alter table in_transit add constraint in_transit_checkout_eva_fk +					foreign key (checkout_station_id, backend_id) +					references stations (eva, source); +				alter table journeys add constraint journeys_checkin_eva_fk +					foreign key (checkin_station_id, backend_id) +					references stations (eva, source); +				alter table journeys add constraint journeys_checkout_eva_fk +					foreign key (checkout_station_id, backend_id) +					references stations (eva, source); +				drop view in_transit_str; +				drop view journeys_str; +				drop view follows_in_transit; +				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.ris as is_ris, +					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.ris as is_ris, +					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 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, +					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 +					order by checkin_time desc +					; +				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, ris, backend.name as backend_name +					from users +					left join backends as backend on users.backend_id = backend.id +					; +				update schema_version set version = 55; +				update schema_version set hafas = '0'; +			} +		); +		say +		  'This travelynx instance now has support for non-DB HAFAS backends.'; +		say +'If the migration fails due to a deadlock, re-run it after stopping all background workers'; +	},  );  sub sync_stations { @@ -2341,7 +2571,7 @@ sub sync_stations {  			},  			{  				on_conflict => \ -'(eva) do update set archived = false, source = 0, ds100 = EXCLUDED.ds100, name=EXCLUDED.name, lat=EXCLUDED.lat, lon=EXCLUDED.lon' +'(eva, source) do update set archived = false, source = 0, ds100 = EXCLUDED.ds100, name=EXCLUDED.name, lat=EXCLUDED.lat, lon=EXCLUDED.lon'  			}  		);  		if ( $count++ % 1000 == 0 ) { @@ -2500,6 +2730,26 @@ sub sync_stations {  	}  } +sub sync_backends { +	my ($db) = @_; +	for my $service ( Travel::Status::DE::HAFAS::get_services()) { +		$db->insert( +			'backends', +			{ +				iris => 0, +				hafas => 1, +				efa => 0, +				ris => 0, +				name => $service->{shortname}, +			}, +			{ on_conflict => undef } +		); +	} + +	$db->update( 'schema_version', +		{ hafas => $Travel::Status::DE::HAFAS::VERSION } ); +} +  sub setup_db {  	my ($db) = @_;  	my $tx = $db->begin; @@ -2566,9 +2816,9 @@ sub migrate_db {  	}  	my $iris_version = get_schema_version( $db, 'iris' ); -	say "Found IRIS station database v${iris_version}"; +	say "Found IRIS station table v${iris_version}";  	if ( $iris_version eq $Travel::Status::DE::IRIS::Stations::VERSION ) { -		say 'Station database is up-to-date'; +		say 'Station table is up-to-date';  	}  	else {  		eval { @@ -2587,6 +2837,18 @@ sub migrate_db {  		}  	} +	my $hafas_version = get_schema_version( $db, 'hafas' ); +	say "Found backend table for HAFAS v${hafas_version}"; +	if ( $hafas_version eq $Travel::Status::DE::HAFAS::VERSION ) { +		say 'Backend table is up-to-date'; +	} +	else { +			say +"Synchronizing with Travel::Status::DE::HAFAS $Travel::Status::DE::HAFAS::VERSION"; +		sync_backends($db); +	} + +  	$db->update( 'schema_version',  		{ travelynx => $self->app->config->{version} } ); diff --git a/lib/Travelynx/Command/dumpconfig.pm b/lib/Travelynx/Command/dumpconfig.pm index 600ffb0..2c308c9 100644 --- a/lib/Travelynx/Command/dumpconfig.pm +++ b/lib/Travelynx/Command/dumpconfig.pm @@ -1,4 +1,5 @@  package Travelynx::Command::dumpconfig; +  # Copyright (C) 2020-2023 Birte Kristina Friesel  #  # SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 129ff45..44d780a 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -47,9 +47,12 @@ sub run {  		my $arr      = $entry->{arr_eva};  		my $train_id = $entry->{train_id}; -		if ( $train_id =~ m{[|]} ) { +		if ( $entry->{is_hafas} ) { -			$self->app->hafas->get_journey_p( trip_id => $train_id )->then( +			$self->app->hafas->get_journey_p( +				trip_id => $train_id, +				service => $entry->{backend_name} +			)->then(  				sub {  					my ($journey) = @_; @@ -135,6 +138,9 @@ sub run {  			next;  		} +		# TODO irgendwo ist hier ne race condition wo ein neuer checkin (in HAFAS) mit IRIS-Daten überschrieben wird. +		# Die ganzen updates brauchen wirklich mal sanity checks mit train id ... +  		# Note: IRIS data is not always updated in real-time. Both departure and  		# arrival delays may take several minutes to appear, especially in case  		# of large-scale disturbances. We work around this by continuing to diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 5c101b1..b7b2b78 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -999,6 +999,52 @@ sub password_form {  	$self->render('change_password');  } +sub backend_form { +	my ($self) = @_; +	my $user = $self->current_user; + +	my @backends = $self->stations->get_backends; + +	for my $backend (@backends) { +		my $type = 'UNKNOWN'; +		if ( $backend->{iris} ) { +			$type = 'IRIS-TTS'; +			$backend->{name} = 'DB'; +		} +		elsif ( $backend->{hafas} ) { +			$type = 'HAFAS'; +			$backend->{longname} +			  = $self->hafas->get_service( $backend->{name} )->{name}; +		} +		$backend->{type} = $type; +	} + +	$self->render( +		'select_backend', +		backends    => \@backends, +		user        => $user, +		redirect_to => $self->req->param('redirect_to') // '/', +	); +} + +sub change_backend { +	my ($self) = @_; + +	my $backend_id = $self->req->param('backend'); +	my $redir      = $self->req->param('redirect_to') // '/'; + +	if ( $backend_id !~ m{ ^ \d+ $ }x ) { +		$self->redirect_to($redir); +	} + +	$self->users->set_backend( +		uid        => $self->current_user->{id}, +		backend_id => $backend_id, +	); + +	$self->redirect_to($redir); +} +  sub change_password {  	my ($self)       = @_;  	my $old_password = $self->req->param('oldpw'); diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index 687243d..5fbfb3e 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -117,6 +117,7 @@ sub travel_v1 {  				deprecated => \0,  				error      => 'Malformed JSON',  			}, +			status => 400,  		);  		return;  	} @@ -130,6 +131,7 @@ sub travel_v1 {  				deprecated => \0,  				error      => 'Malformed token',  			}, +			status => 400,  		);  		return;  	} @@ -143,6 +145,7 @@ sub travel_v1 {  				deprecated => \0,  				error      => 'Malformed token',  			}, +			status => 400,  		);  		return;  	} @@ -155,6 +158,7 @@ sub travel_v1 {  				deprecated => \0,  				error      => 'Invalid token',  			}, +			status => 400,  		);  		return;  	} @@ -169,6 +173,7 @@ sub travel_v1 {  				error      => 'Missing or invalid action',  				status     => $self->get_user_status_json_v1( uid => $uid )  			}, +			status => 400,  		);  		return;  	} @@ -177,7 +182,8 @@ sub travel_v1 {  		my $from_station = sanitize( q{}, $payload->{fromStation} );  		my $to_station   = sanitize( q{}, $payload->{toStation} );  		my $train_id; -		my $hafas = exists $payload->{train}{journeyID} ? 1 : 0; +		my $hafas = sanitize(undef, $payload->{hafas}); +		$hafas //= exists $payload->{train}{journeyID} ? 'DB' : undef;  		if (  			not( @@ -195,11 +201,12 @@ sub travel_v1 {  					error      => 'Missing fromStation or train data',  					status     => $self->get_user_status_json_v1( uid => $uid )  				}, +			status => 400,  			);  			return;  		} -		if ( not $hafas and not $self->stations->search($from_station) ) { +		if ( not $hafas and not $self->stations->search($from_station, iris => 1) ) {  			$self->render(  				json => {  					success    => \0, @@ -207,13 +214,14 @@ sub travel_v1 {  					error      => 'Unknown fromStation',  					status     => $self->get_user_status_json_v1( uid => $uid )  				}, +			status => 400,  			);  			return;  		}  		if (    $to_station  			and not $hafas -			and not $self->stations->search($to_station) ) +			and not $self->stations->search($to_station, iris => 1) )  		{  			$self->render(  				json => { @@ -222,6 +230,7 @@ sub travel_v1 {  					error      => 'Unknown toStation',  					status     => $self->get_user_status_json_v1( uid => $uid )  				}, +			status => 400,  			);  			return;  		} @@ -273,7 +282,8 @@ sub travel_v1 {  				return $self->checkin_p(  					station  => $from_station,  					train_id => $train_id, -					uid      => $uid +					uid      => $uid, +					hafas    => $hafas,  				);  			}  		)->then( @@ -654,10 +664,13 @@ sub autocomplete {  	$self->res->headers->cache_control('max-age=86400, immutable'); +	my $backend_id = $self->param('backend_id') // 1; +  	my $output  	  = "document.addEventListener('DOMContentLoaded',function(){M.Autocomplete.init(document.querySelectorAll('.autocomplete'),{\n";  	$output .= 'minLength:3,limit:50,data:'; -	$output .= encode_json( $self->stations->get_for_autocomplete ); +	$output +	  .= encode_json( $self->stations->get_for_autocomplete( backend_id => $backend_id ) );  	$output .= "\n});});\n";  	$self->render( diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 2b86688..6b421d1 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -24,10 +24,15 @@ sub has_str_in_list {  	return;  } +# when called with "eva" provided: look up connections from eva, either +# for provided backend_id / hafas or (if not provided) for user backend id. +# When calld without "eva": look up connections from current/latest arrival +# eva, using the checkin's backend id.  sub get_connecting_trains_p {  	my ( $self, %opt ) = @_; -	my $uid         = $opt{uid} //= $self->current_user->{id}; +	my $user        = $self->current_user; +	my $uid         = $opt{uid} //= $user->{id};  	my $use_history = $self->users->use_history( uid => $uid );  	my ( $eva, $exclude_via, $exclude_train_id, $exclude_before ); @@ -43,10 +48,20 @@ sub get_connecting_trains_p {  		elsif ( $opt{destination_name} ) {  			$eva = $opt{eva};  		} +		if ( not defined $opt{backend_id} ) { +			if ( $opt{hafas} ) { +				$opt{backend_id} +				  = $self->stations->get_backend_id( hafas => $opt{hafas} ); +			} +			else { +				$opt{backend_id} = $user->{backend_id}; +			} +		}  	}  	else {  		if ( $use_history & 0x02 ) {  			my $status = $self->get_user_status; +			$opt{backend_id}  = $status->{backend_id};  			$eva              = $status->{arr_eva};  			$exclude_via      = $status->{dep_name};  			$exclude_train_id = $status->{train_id}; @@ -65,10 +80,12 @@ sub get_connecting_trains_p {  		return $promise->reject;  	} -	my ( $dest_ids, $destinations ) -	  = $self->journeys->get_connection_targets(%opt); +	$self->log->debug( +		"get_connecting_trains_p(backend_id => $opt{backend_id}, eva => $eva)"); -	my @destinations = uniq_by { $_->{name} } @{$destinations}; +	my @destinations = $self->journeys->get_connection_targets(%opt); + +	@destinations = uniq_by { $_->{name} } @destinations;  	if ($exclude_via) {  		@destinations = grep { $_->{name} ne $exclude_via } @destinations; @@ -78,11 +95,8 @@ sub get_connecting_trains_p {  		return $promise->reject;  	} -	my $iris_eva = $eva; -	if ( $eva < 8000000 ) { -		$iris_eva = ( List::Util::first { $_ >= 8000000 } @{$dest_ids} ) -		  // $eva; -	} +	$self->log->debug( 'get_connection_targets returned ' +		  . join( q{, }, map { $_->{name} } @destinations ) );  	my $can_check_in = not $arr_epoch || ( $arr_countdown // 1 ) < 0;  	my $lookahead @@ -91,11 +105,9 @@ sub get_connecting_trains_p {  	my $iris_promise = Mojo::Promise->new;  	my %via_count    = map { $_->{name} => 0 } @destinations; -	if ( $iris_eva >= 8000000 -		and List::Util::any { $_->{eva} >= 8000000 } @destinations ) -	{ +	if ( $opt{backend_id} == 0 ) {  		$self->iris->get_departures_p( -			station      => $iris_eva, +			station      => $eva,  			lookbehind   => 10,  			lookahead    => $lookahead,  			with_related => 1 @@ -103,7 +115,7 @@ sub get_connecting_trains_p {  			sub {  				my ($stationboard) = @_;  				if ( $stationboard->{errstr} ) { -					$iris_promise->resolve( [] ); +					$promise->resolve( [], [] );  					return;  				} @@ -237,105 +249,30 @@ sub get_connecting_trains_p {  					}  				} -				$iris_promise->resolve( [ @results, @cancellations ] ); +				$promise->resolve( [ @results, @cancellations ], [] );  				return;  			}  		)->catch(  			sub { -				$iris_promise->resolve( [] ); +				$promise->resolve( [], [] );  				return;  			}  		)->wait;  	}  	else { -		$iris_promise->resolve( [] ); -	} - -	my $hafas_promise = Mojo::Promise->new; -	$self->hafas->get_departures_p( -		eva        => $eva, -		lookbehind => 10, -		lookahead  => $lookahead -	)->then( -		sub { -			my ($status) = @_; -			$hafas_promise->resolve( [ $status->results ] ); -			return; -		} -	)->catch( -		sub { -			# HAFAS data is optional. -			# Errors are logged by get_json_p and can be silently ignored here. -			$hafas_promise->resolve( [] ); -			return; -		} -	)->wait; - -	Mojo::Promise->all( $iris_promise, $hafas_promise )->then( -		sub { -			my ( $iris, $hafas ) = @_; -			my @iris_trains      = @{ $iris->[0] }; -			my @all_hafas_trains = @{ $hafas->[0] }; -			my @hafas_trains; - -			# We've already got a list of connecting trains; this function -			# only adds further information to them. We ignore errors, as -			# partial data is better than no data. -			eval { -				for my $iris_train (@iris_trains) { -					if ( $iris_train->[0]->departure_is_cancelled ) { -						for my $hafas_train (@all_hafas_trains) { -							if (    $hafas_train->number -								and $hafas_train->number -								== $iris_train->[0]->train_no ) -							{ -								$hafas_train->{iris_seen} = 1; -								next; -							} -						} -						next; -					} -					for my $hafas_train (@all_hafas_trains) { -						if (    $hafas_train->number -							and $hafas_train->number -							== $iris_train->[0]->train_no ) -						{ -							$hafas_train->{iris_seen} = 1; -							if (    $hafas_train->load -								and $hafas_train->load->{SECOND} ) -							{ -								$iris_train->[3] = $hafas_train->load; -							} -							for my $stop ( $hafas_train->route ) { -								if (    $stop->loc->name -									and $stop->loc->name eq -									$iris_train->[1]->{name} -									and $stop->arr ) -								{ -									$iris_train->[2] = $stop->arr; -									if ( $iris_train->[0]->departure_delay -										and not $stop->arr_delay ) -									{ -										$iris_train->[2] -										  ->add( minutes => $iris_train->[0] -											  ->departure_delay ); -									} -									last; -								} -							} -							last; -						} -					} -				} +		my $hafas_service +		  = $self->stations->get_hafas_name( backend_id => $opt{backend_id} ); +		$self->hafas->get_departures_p( +			service    => $hafas_service, +			eva        => $eva, +			lookbehind => 10, +			lookahead  => $lookahead +		)->then( +			sub { +				my ($status) = @_; +				my @hafas_trains; +				my @all_hafas_trains = $status->results;  				for my $hafas_train (@all_hafas_trains) { -					if ( $hafas_train->{iris_seen} ) { -						next; -					} -					if ( $hafas_train->station_eva >= 8000000 ) { - -						# better safe than sorry, for now -						next; -					}  					for my $stop ( $hafas_train->route ) {  						for my $dest (@destinations) {  							if (    $stop->loc->name @@ -353,30 +290,30 @@ sub get_connecting_trains_p {  								}  								if ( $departure->epoch >= $exclude_before ) {  									$via_count{ $dest->{name} }++; -									push( @hafas_trains, -										[ $hafas_train, $dest, $arrival ] ); +									push( +										@hafas_trains, +										[ +											$hafas_train, $dest, +											$arrival,     $hafas_service +										] +									);  								}  							}  						}  					}  				} -			}; -			if ($@) { -				$self->app->log->error( -					"get_connecting_trains_p($uid): IRIS/HAFAS merge failed: $@" -				); +				$promise->resolve( [], \@hafas_trains ); +				return;  			} - -			$promise->resolve( \@iris_trains, \@hafas_trains ); -			return; -		} -	)->catch( -		sub { -			my ($err) = @_; -			$promise->reject($err); -			return; -		} -	)->wait; +		)->catch( +			sub { +				my ($err) = @_; +				$self->log->debug("get_connection_trains: hafas: $err"); +				$promise->resolve( [], [] ); +				return; +			} +		)->wait; +	}  	return $promise;  } @@ -394,7 +331,8 @@ sub compute_effective_visibility {  sub homepage {  	my ($self) = @_;  	if ( $self->is_user_authenticated ) { -		my $uid      = $self->current_user->{id}; +		my $user     = $self->current_user; +		my $uid      = $user->{id};  		my $status   = $self->get_user_status;  		my @timeline = $self->in_transit->get_timeline(  			uid   => $uid, @@ -405,7 +343,7 @@ sub homepage {  		if ( $status->{checked_in} ) {  			my $journey_visibility  			  = $self->compute_effective_visibility( -				$self->current_user->{default_visibility_str}, +				$user->{default_visibility_str},  				$status->{visibility_str} );  			if ( defined $status->{arrival_countdown}  				and $status->{arrival_countdown} < ( 40 * 60 ) ) @@ -416,6 +354,7 @@ sub homepage {  						my ( $connections_iris, $connections_hafas ) = @_;  						$self->render(  							'landingpage', +							user               => $user,  							user_status        => $status,  							journey_visibility => $journey_visibility,  							connections_iris   => $connections_iris, @@ -427,6 +366,7 @@ sub homepage {  					sub {  						$self->render(  							'landingpage', +							user               => $user,  							user_status        => $status,  							journey_visibility => $journey_visibility,  						); @@ -438,6 +378,7 @@ sub homepage {  			else {  				$self->render(  					'landingpage', +					user               => $user,  					user_status        => $status,  					journey_visibility => $journey_visibility,  				); @@ -451,10 +392,12 @@ sub homepage {  		}  		$self->render(  			'landingpage', +			user              => $user,  			user_status       => $status,  			recent_targets    => \@recent_targets,  			with_autocomplete => 1, -			with_geolocation  => 1 +			with_geolocation  => 1, +			backend_id        => $user->{backend_id},  		);  		$self->users->mark_seen( uid => $uid );  	} @@ -515,6 +458,7 @@ sub status_card {  	elsif ( $status->{cancellation} ) {  		$self->render_later;  		$self->get_connecting_trains_p( +			backend_id       => $status->{backend_id},  			eva              => $status->{cancellation}{dep_eva},  			destination_name => $status->{cancellation}{arr_name}  		)->then( @@ -563,14 +507,63 @@ sub status_card {  sub geolocation {  	my ($self) = @_; -	my $lon = $self->param('lon'); -	my $lat = $self->param('lat'); +	my $lon        = $self->param('lon'); +	my $lat        = $self->param('lat'); +	my $backend_id = $self->param('backend') // 0; -	if ( not $lon or not $lat ) { +	if ( not $lon or not $lat or $backend_id !~ m{ ^ \d+ $ }x ) {  		$self->render( json => { error => 'Invalid lon/lat received' } );  		return;  	} -	$self->render_later; + +	my $hafas_service +	  = $self->stations->get_hafas_name( backend_id => $backend_id ); + +	if ($hafas_service) { +		$self->render_later; + +		Travel::Status::DE::HAFAS->new_p( +			promise    => 'Mojo::Promise', +			user_agent => $self->ua, +			service    => $hafas_service, +			geoSearch  => { +				lat => $lat, +				lon => $lon +			} +		)->then( +			sub { +				my ($hafas) = @_; +				my @hafas = map { +					{ +						name     => $_->name, +						eva      => $_->eva, +						distance => $_->distance_m / 1000, +						hafas    => $hafas_service +					} +				} $hafas->results; +				if ( @hafas > 10 ) { +					@hafas = @hafas[ 0 .. 9 ]; +				} +				$self->render( +					json => { +						candidates => [@hafas], +					} +				); +			} +		)->catch( +			sub { +				my ($err) = @_; +				$self->render( +					json => { +						candidates => [], +						warning    => $err, +					} +				); +			} +		)->wait; + +		return; +	}  	my @iris = map {  		{ @@ -580,7 +573,6 @@ sub geolocation {  			lon      => $_->[0][3],  			lat      => $_->[0][4],  			distance => $_->[1], -			hafas    => 0,  		}  	} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,  		$lat, 10 ); @@ -588,48 +580,12 @@ sub geolocation {  	if ( @iris > 5 ) {  		@iris = @iris[ 0 .. 4 ];  	} - -	Travel::Status::DE::HAFAS->new_p( -		promise    => 'Mojo::Promise', -		user_agent => $self->ua, -		geoSearch  => { -			lat => $lat, -			lon => $lon -		} -	)->then( -		sub { -			my ($hafas) = @_; -			my @hafas = map { -				{ -					name     => $_->name, -					eva      => $_->eva, -					distance => $_->distance_m / 1000, -					hafas    => 'DB' -				} -			} $hafas->results; -			if ( @hafas > 10 ) { -				@hafas = @hafas[ 0 .. 9 ]; -			} -			my @results = map { $_->[0] } -			  sort { $a->[1] <=> $b->[1] } -			  map { [ $_, $_->{distance} ] } ( @iris, @hafas ); -			$self->render( -				json => { -					candidates => [@results], -				} -			); -		} -	)->catch( -		sub { -			my ($err) = @_; -			$self->render( -				json => { -					candidates => [@iris], -					warning    => $err, -				} -			); +	$self->render( +		json => { +			candidates => [@iris],  		} -	)->wait; +	); +  }  sub travel_action { @@ -684,6 +640,7 @@ sub travel_action {  		$promise->then(  			sub {  				return $self->checkin_p( +					hafas    => $params->{hafas},  					station  => $params->{station},  					train_id => $params->{train}  				); @@ -713,8 +670,8 @@ sub travel_action {  				my ( $still_checked_in, undef ) = @_;  				if ( my $destination = $params->{dest} ) {  					my $station_link = '/s/' . $destination; -					if ( $status->{train_id} =~ m{[|]} ) { -						$station_link .= '?hafas=DB'; +					if ( $status->{is_hafas} ) { +						$station_link .= '?hafas=' . $status->{backend_name};  					}  					$self->render(  						json => { @@ -749,8 +706,8 @@ sub travel_action {  			sub {  				my ( $still_checked_in, $error ) = @_;  				my $station_link = '/s/' . $params->{station}; -				if ( $status->{train_id} =~ m{[|]} ) { -					$station_link .= '?hafas=DB'; +				if ( $status->{is_hafas} ) { +					$station_link .= '?hafas=' . $status->{backend_name};  				}  				if ($error) { @@ -800,8 +757,12 @@ sub travel_action {  		else {  			my $redir = '/';  			if ( $status->{checked_in} or $status->{cancelled} ) { -				if ( $status->{train_id} =~ m{[|]} ) { -					$redir = '/s/' . $status->{dep_eva} . '?hafas=DB'; +				if ( $status->{is_hafas} ) { +					$redir +					  = '/s/' +					  . $status->{dep_eva} +					  . '?hafas=' +					  . $status->{backend_name};  				}  				else {  					$redir = '/s/' . $status->{dep_ds100}; @@ -818,6 +779,7 @@ sub travel_action {  	elsif ( $params->{action} eq 'cancelled_from' ) {  		$self->render_later;  		$self->checkin_p( +			hafas    => $params->{hafas},  			station  => $params->{station},  			train_id => $params->{train}  		)->then( @@ -920,7 +882,8 @@ sub station {  	my $train     = $self->param('train');  	my $trip_id   = $self->param('trip_id');  	my $timestamp = $self->param('timestamp'); -	my $uid       = $self->current_user->{id}; +	my $user      = $self->current_user; +	my $uid       = $user->{id};  	my @timeline = $self->in_transit->get_timeline(  		uid   => $uid, @@ -928,7 +891,6 @@ sub station {  	);  	my %checkin_by_train;  	for my $checkin (@timeline) { -		say $checkin->{train_id};  		push( @{ $checkin_by_train{ $checkin->{train_id} } }, $checkin );  	}  	$self->stash( checkin_by_train => \%checkin_by_train ); @@ -945,10 +907,12 @@ sub station {  		$timestamp = DateTime->now( time_zone => 'Europe/Berlin' );  	} -	my $use_hafas = $self->param('hafas'); +	my $hafas_service = $self->param('hafas') +	  // ( $user->{backend_hafas} ? $user->{backend_name} : undef );  	my $promise; -	if ($use_hafas) { +	if ($hafas_service) {  		$promise = $self->hafas->get_departures_p( +			service    => $hafas_service,  			eva        => $station,  			timestamp  => $timestamp,  			lookbehind => 30, @@ -966,27 +930,21 @@ sub station {  	$promise->then(  		sub {  			my ($status) = @_; -			my $api_link;  			my @results;  			my $now = $self->now->epoch;  			my $now_within_range  			  = abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0; -			if ($use_hafas) { - -				my $iris_eva = List::Util::min grep { $_ >= 1000000 } -				  @{ $status->station->{evas} // [] }; -				if ($iris_eva) { -					$api_link = '/s/' . $iris_eva; -				} +			if ($hafas_service) {  				@results = map { $_->[0] }  				  sort { $b->[1] <=> $a->[1] }  				  map { [ $_, $_->datetime->epoch ] } $status->results;  				$self->stations->add_meta( -					eva  => $status->station->{eva}, -					meta => $status->station->{evas} // [] +					eva   => $status->station->{eva}, +					meta  => $status->station->{evas} // [], +					hafas => $hafas_service,  				);  				$status = {  					station_eva  => $status->station->{eva}, @@ -999,8 +957,6 @@ sub station {  			}  			else { -				$api_link = '/s/' . $status->{station_eva} . '?hafas=DB'; -  				# You can't check into a train which terminates here  				@results = grep { $_->departure } @{ $status->{results} }; @@ -1029,10 +985,10 @@ sub station {  			}  			my $connections_p; -			if ( $trip_id and $use_hafas ) { +			if ( $trip_id and $hafas_service ) {  				@results = grep { $_->id eq $trip_id } @results;  			} -			elsif ( $train and not $use_hafas ) { +			elsif ( $train and not $hafas_service ) {  				@results  				  = grep { $_->type . ' ' . $_->train_no eq $train } @results;  			} @@ -1044,12 +1000,15 @@ sub station {  					$connections_p = $self->get_connecting_trains_p(  						eva => $user_status->{cancellation}{dep_eva},  						destination_name => -						  $user_status->{cancellation}{arr_name} +						  $user_status->{cancellation}{arr_name}, +						hafas => $hafas_service,  					);  				}  				else {  					$connections_p = $self->get_connecting_trains_p( -						eva => $status->{station_eva} ); +						eva   => $status->{station_eva}, +						hafas => $hafas_service +					);  				}  			} @@ -1059,18 +1018,18 @@ sub station {  						my ( $connections_iris, $connections_hafas ) = @_;  						$self->render(  							'departures', +							user              => $user, +							hafas             => $hafas_service,  							eva               => $status->{station_eva},  							datetime          => $timestamp,  							now_in_range      => $now_within_range,  							results           => \@results, -							hafas             => $use_hafas,  							station           => $status->{station_name},  							related_stations  => $status->{related_stations},  							user_status       => $user_status,  							can_check_out     => $can_check_out,  							connections_iris  => $connections_iris,  							connections_hafas => $connections_hafas, -							api_link          => $api_link,  							title => "travelynx: $status->{station_name}",  						);  					} @@ -1078,16 +1037,16 @@ sub station {  					sub {  						$self->render(  							'departures', +							user             => $user, +							hafas            => $hafas_service,  							eva              => $status->{station_eva},  							datetime         => $timestamp,  							now_in_range     => $now_within_range,  							results          => \@results, -							hafas            => $use_hafas,  							station          => $status->{station_name},  							related_stations => $status->{related_stations},  							user_status      => $user_status,  							can_check_out    => $can_check_out, -							api_link         => $api_link,  							title => "travelynx: $status->{station_name}",  						);  					} @@ -1096,16 +1055,16 @@ sub station {  			else {  				$self->render(  					'departures', +					user             => $user, +					hafas            => $hafas_service,  					eva              => $status->{station_eva},  					datetime         => $timestamp,  					now_in_range     => $now_within_range,  					results          => \@results, -					hafas            => $use_hafas,  					station          => $status->{station_name},  					related_stations => $status->{related_stations},  					user_status      => $user_status,  					can_check_out    => $can_check_out, -					api_link         => $api_link,  					title            => "travelynx: $status->{station_name}",  				);  			} @@ -1120,15 +1079,22 @@ sub station {  					status      => 300,  				);  			} -			elsif ( $use_hafas and $status and $status->errcode eq 'LOCATION' ) +			elsif ( $hafas_service +				and $status +				and $status->errcode eq 'LOCATION' )  			{ -				$self->hafas->search_location_p( query => $station )->then( +				$self->hafas->search_location_p( +					service => $hafas_service, +					query   => $station +				)->then(  					sub {  						my ($hafas2) = @_;  						my @suggestions = $hafas2->results;  						if ( @suggestions == 1 ) { -							$self->redirect_to( -								'/s/' . $suggestions[0]->eva . '?hafas=DB' ); +							$self->redirect_to( '/s/' +								  . $suggestions[0]->eva +								  . '?hafas=' +								  . $hafas_service );  						}  						else {  							$self->render( @@ -1169,17 +1135,7 @@ sub redirect_to_station {  	my ($self) = @_;  	my $station = $self->param('station'); -	if ( my $s = $self->app->stations->search($station) ) { -		if ( $s->{source} == 1 ) { -			$self->redirect_to("/s/${station}?hafas=DB"); -		} -		else { -			$self->redirect_to("/s/${station}"); -		} -	} -	else { -		$self->redirect_to("/s/${station}?hafas=DB"); -	} +	$self->redirect_to("/s/${station}");  }  sub cancelled { diff --git a/lib/Travelynx/Helper/HAFAS.pm b/lib/Travelynx/Helper/HAFAS.pm index 1569208..40bc636 100644 --- a/lib/Travelynx/Helper/HAFAS.pm +++ b/lib/Travelynx/Helper/HAFAS.pm @@ -97,6 +97,7 @@ sub get_departures_p {  		: DateTime->now( time_zone => 'Europe/Berlin' )  	)->subtract( minutes => $opt{lookbehind} );  	return Travel::Status::DE::HAFAS->new_p( +		service    => $opt{service},  		station    => $opt{eva},  		datetime   => $when,  		lookahead  => $opt{lookahead} + $opt{lookbehind}, @@ -111,6 +112,7 @@ sub search_location_p {  	my ( $self, %opt ) = @_;  	return Travel::Status::DE::HAFAS->new_p( +		service        => $opt{service},  		locationSearch => $opt{query},  		cache          => $self->{realtime_cache},  		promise        => 'Mojo::Promise', @@ -128,6 +130,7 @@ sub get_tripid_p {  	$train_desc =~ s{^- }{};  	Travel::Status::DE::HAFAS->new_p( +		service      => $opt{service},  		journeyMatch => $train_desc,  		datetime     => $train->start,  		cache        => $self->{realtime_cache}, @@ -175,6 +178,7 @@ sub get_journey_p {  	my $now     = DateTime->now( time_zone => 'Europe/Berlin' );  	Travel::Status::DE::HAFAS->new_p( +		service => $opt{service},  		journey => {  			id => $opt{trip_id},  		}, @@ -212,6 +216,7 @@ sub get_route_p {  	my $now     = DateTime->now( time_zone => 'Europe/Berlin' );  	Travel::Status::DE::HAFAS->new_p( +		service => $opt{service},  		journey => {  			id => $opt{trip_id}, diff --git a/lib/Travelynx/Helper/Sendmail.pm b/lib/Travelynx/Helper/Sendmail.pm index baa1156..54829c8 100644 --- a/lib/Travelynx/Helper/Sendmail.pm +++ b/lib/Travelynx/Helper/Sendmail.pm @@ -9,7 +9,7 @@ use warnings;  use 5.020; -use Encode qw(encode); +use Encode                qw(encode);  use Email::Sender::Simple qw(try_to_sendmail);  use MIME::Entity; diff --git a/lib/Travelynx/Helper/Traewelling.pm b/lib/Travelynx/Helper/Traewelling.pm index d688004..100a799 100644 --- a/lib/Travelynx/Helper/Traewelling.pm +++ b/lib/Travelynx/Helper/Traewelling.pm @@ -116,6 +116,7 @@ sub get_status_p {  					my $category = $status->{train}{category};  					my $linename = $status->{train}{lineName}; +					my $train_no = $status->{train}{journeyNumber};  					my $trip_id  = $status->{train}{hafasId};  					my ( $train_type, $train_line ) = split( qr{ }, $linename );  					$promise->resolve( @@ -133,6 +134,7 @@ sub get_status_p {  							arr_ds100  => $arr_ds100,  							arr_name   => $arr_name,  							trip_id    => $trip_id, +							train_no   => $train_no,  							train_type => $train_type,  							line       => $linename,  							line_no    => $train_line, diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index f5a1f6d..b9fbabc 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -93,6 +93,7 @@ sub add {  	my $uid                = $opt{uid};  	my $db                 = $opt{db} // $self->{pg}->db; +	my $backend_id         = $opt{backend_id};  	my $train              = $opt{train};  	my $journey            = $opt{journey};  	my $stop               = $opt{stop}; @@ -103,8 +104,6 @@ sub add {  	my $json = JSON->new;  	if ($train) { -		my $backend_id -		  = $db->select( 'backends', ['id'], { iris => 1 } )->hash->{id};  		$db->insert(  			'in_transit',  			{ @@ -136,14 +135,6 @@ sub add {  		);  	}  	elsif ( $journey and $stop ) { -		my $backend_id = $db->select( -			'backends', -			['id'], -			{ -				hafas => 1, -				name  => 'DB' -			} -		)->hash->{id};  		my @route;  		my $product = $journey->product_at( $stop->loc->eva )  		  // $journey->product; @@ -440,17 +431,20 @@ sub get_all_active {  	  ->hashes->each;  } -sub get_checkout_station_id { +sub get_checkout_ids {  	my ( $self, %opt ) = @_;  	my $uid = $opt{uid};  	my $db  = $opt{db} // $self->{pg}->db; -	my $status = $db->select( 'in_transit', ['checkout_station_id'], -		{ user_id => $uid } )->hash; +	my $status = $db->select( +		'in_transit', +		[ 'checkout_station_id', 'backend_id' ], +		{ user_id => $uid } +	)->hash;  	if ($status) { -		return $status->{checkout_station_id}; +		return $status->{checkout_station_id}, $status->{backend_id};  	}  	return;  } @@ -819,7 +813,6 @@ sub update_arrival_hafas {  	my $stop    = $opt{stop};  	my $json    = JSON->new; -	# TODO use old rt data if available  	my @route;  	for my $j_stop ( $journey->route ) {  		push( diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index b067d78..e47df58 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -545,7 +545,7 @@ sub get {  	my @select  	  = ( -		qw(journey_id is_iris is_hafas backend_name train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) +		qw(journey_id is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility)  	  );  	my %where = (  		user_id   => $uid, @@ -606,6 +606,7 @@ sub get {  			is_iris              => $entry->{is_iris},  			is_hafas             => $entry->{is_hafas},  			backend_name         => $entry->{backend_name}, +			backend_id           => $entry->{backend_id},  			type                 => $entry->{train_type},  			line                 => $entry->{train_line},  			no                   => $entry->{train_no}, @@ -665,7 +666,10 @@ sub get {  			my $rename = $self->{renamed_station};  			for my $stop ( @{ $ref->{route} } ) {  				if ( $stop->[0] =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) { -					if ( my $s = $self->{stations}->get_by_eva($1) ) { +					if ( my $s +						= $self->{stations} +						->get_by_eva( $1, backend_id => $ref->{backend_id} ) ) +					{  						$stop->[0] = $s->{name};  					}  				} @@ -800,14 +804,14 @@ sub get_oldest_ts {  	return undef;  } -sub get_latest_checkout_station_id { +sub get_latest_checkout_ids {  	my ( $self, %opt ) = @_;  	my $uid = $opt{uid};  	my $db  = $opt{db} // $self->{pg}->db;  	my $res_h = $db->select(  		'journeys', -		['checkout_station_id'], +		[ 'checkout_station_id', 'backend_id', ],  		{  			user_id   => $uid,  			cancelled => 0 @@ -822,7 +826,7 @@ sub get_latest_checkout_station_id {  		return;  	} -	return $res_h->{checkout_station_id}; +	return $res_h->{checkout_station_id}, $res_h->{backend_id};  }  sub get_latest_checkout_stations { @@ -833,7 +837,10 @@ sub get_latest_checkout_stations {  	my $res = $db->select(  		'journeys_str', -		[ 'arr_name', 'arr_eva', 'train_id' ], +		[ +			'arr_name',     'arr_eva', 'train_id', 'backend_id', +			'backend_name', 'is_hafas' +		],  		{  			user_id   => $uid,  			cancelled => 0 @@ -854,9 +861,10 @@ sub get_latest_checkout_stations {  		push(  			@ret,  			{ -				name  => $row->{arr_name}, -				eva   => $row->{arr_eva}, -				hafas => ( $row->{train_id} =~ m{[|]} ? 'DB' : 0 ), +				name       => $row->{arr_name}, +				eva        => $row->{arr_eva}, +				hafas      => $row->{is_hafas} ? $row->{backend_name} : 0, +				backend_id => $row->{backend_id},  			}  		);  	} @@ -1726,28 +1734,29 @@ sub get_stats {  	return $stats;  } -sub get_latest_dest_id { +sub get_latest_dest_ids {  	my ( $self, %opt ) = @_;  	my $uid = $opt{uid};  	my $db  = $opt{db} // $self->{pg}->db;  	if ( -		my $id = $self->{in_transit}->get_checkout_station_id( +		my ( $id, $backend_id ) = $self->{in_transit}->get_checkout_ids(  			uid => $uid,  			db  => $db  		)  	  )  	{ -		return $id; +		return ( $id, $backend_id );  	} -	return $self->get_latest_checkout_station_id( +	return $self->get_latest_checkout_ids(  		uid => $uid,  		db  => $db  	);  } +# Returns a listref of {eva, name} hashrefs for the specified backend.  sub get_connection_targets {  	my ( $self, %opt ) = @_; @@ -1756,21 +1765,29 @@ sub get_connection_targets {  	  // DateTime->now( time_zone => 'Europe/Berlin' )->subtract( months => 4 );  	my $db        = $opt{db} //= $self->{pg}->db;  	my $min_count = $opt{min_count} // 3; +	my $dest_id   = $opt{eva};  	if ( $opt{destination_name} ) { -		return ( -			[], -			[ { eva => $opt{eva}, name => $opt{destination_name} } ] -		); +		return [ { eva => $opt{eva}, name => $opt{destination_name} } ];  	} -	my $dest_id = $opt{eva} // $self->get_latest_dest_id(%opt); +	my $backend_id = $opt{backend_id}; + +	if ( not $dest_id ) { +		( $dest_id, $backend_id ) = $self->get_latest_dest_ids(%opt); +	}  	if ( not $dest_id ) { -		return ( [], [] ); +		return [];  	} -	my $dest_ids = [ $dest_id, $self->{stations}->get_meta( eva => $dest_id ) ]; +	my $dest_ids = [ +		$dest_id, +		$self->{stations}->get_meta( +			eva        => $dest_id, +			backend_id => $backend_id, +		) +	];  	my $res = $db->select(  		'journeys', @@ -1778,7 +1795,8 @@ sub get_connection_targets {  		{  			user_id            => $uid,  			checkin_station_id => $dest_ids, -			real_departure     => { '>', $threshold } +			real_departure     => { '>', $threshold }, +			backend_id         => $opt{backend_id},  		},  		{  			group_by => ['checkout_station_id'], @@ -1788,8 +1806,11 @@ sub get_connection_targets {  	my @destinations  	  = $res->hashes->grep( sub { shift->{count} >= $min_count } )  	  ->map( sub { shift->{dest} } )->each; -	@destinations = $self->{stations}->get_by_evas(@destinations); -	return ( $dest_ids, \@destinations ); +	@destinations = $self->{stations}->get_by_evas( +		backend_id => $opt{backend_id}, +		evas       => [@destinations] +	); +	return @destinations;  }  sub update_visibility { diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index ad219e0..7215aa5 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -14,38 +14,125 @@ sub new {  	return bless( \%opt, $class );  } -sub add_or_update { +sub get_backend_id {  	my ( $self, %opt ) = @_; -	my $stop   = $opt{stop}; -	my $loc    = $stop->loc; -	my $source = 1; -	my $db     = $opt{db} // $self->{pg}->db; - -	if ( my $s = $self->get_by_eva( $loc->eva, db => $db ) ) { -		if ( $source == 1 and $s->{source} == 0 and not $s->{archived} ) { -			return; + +	if ( $opt{iris} ) { + +		# special case +		return 0; +	} +	if ( $opt{hafas} and $self->{backend_id}{hafas}{ $opt{hafas} } ) { +		return $self->{backend_id}{hafas}{ $opt{hafas} }; +	} + +	my $db         = $opt{db} // $self->{pg}->db; +	my $backend_id = 0; + +	if ( $opt{hafas} ) { +		$backend_id = $db->select( +			'backends', +			['id'], +			{ +				hafas => 1, +				name  => $opt{hafas} +			} +		)->hash->{id}; +		$self->{backend_id}{hafas}{ $opt{hafas} } = $backend_id; +	} + +	return $backend_id; +} + +sub get_hafas_name { +	my ( $self, %opt ) = @_; + +	if ( exists $self->{hafas_name}{ $opt{backend_id} } ) { +		return $self->{hafas_name}{ $opt{backend_id} }; +	} + +	my $db = $opt{db} // $self->{pg}->db; +	my $hafas_name; +	my $ret = $db->select( +		'backends', +		['name'], +		{ +			hafas => 1, +			id    => $opt{backend_id},  		} -		$db->update( +	)->hash; + +	if ($ret) { +		$hafas_name = $ret->{name}; +	} + +	$self->{hafas_name}{ $opt{backend_id} } = $hafas_name; + +	return $hafas_name; +} + +sub get_backends { +	my ( $self, %opt ) = @_; + +	$opt{db} //= $self->{pg}->db; + +	my $res = $opt{db}->select( 'backends', [ 'id', 'name', 'iris', 'hafas' ] ); +	my @ret; + +	while ( my $row = $res->hash ) { +		push( +			@ret, +			{ +				id    => $row->{id}, +				name  => $row->{name}, +				iris  => $row->{iris}, +				hafas => $row->{hafas}, +			} +		); +	} + +	return @ret; +} + +sub add_or_update { +	my ( $self, %opt ) = @_; +	my $stop = $opt{stop}; +	my $loc  = $stop->loc; +	$opt{db} //= $self->{pg}->db; + +	$opt{backend_id} //= $self->get_backend_id(%opt); + +	if ( +		my $s = $self->get_by_eva( +			$loc->eva, +			db         => $opt{db}, +			backend_id => $opt{backend_id} +		) +	  ) +	{ +		$opt{db}->update(  			'stations',  			{  				name     => $loc->name,  				lat      => $loc->lat,  				lon      => $loc->lon, -				source   => $source,  				archived => 0  			}, -			{ eva => $loc->eva } +			{ +				eva    => $loc->eva, +				source => $opt{backend_id} +			}  		);  		return;  	} -	$db->insert( +	$opt{db}->insert(  		'stations',  		{  			eva      => $loc->eva,  			name     => $loc->name,  			lat      => $loc->lat,  			lon      => $loc->lon, -			source   => $source, +			source   => $opt{backend_id},  			archived => 0  		}  	); @@ -53,17 +140,20 @@ sub add_or_update {  sub add_meta {  	my ( $self, %opt ) = @_; -	my $db   = $opt{db} // $self->{pg}->db;  	my $eva  = $opt{eva};  	my @meta = @{ $opt{meta} }; +	$opt{db}         //= $self->{pg}->db; +	$opt{backend_id} //= $self->get_backend_id(%opt); +  	for my $meta (@meta) {  		if ( $meta != $eva ) { -			$db->insert( +			$opt{db}->insert(  				'related_stations',  				{ -					eva  => $eva, -					meta => $meta +					eva        => $eva, +					meta       => $meta, +					backend_id => $opt{backend_id},  				},  				{ on_conflict => undef }  			); @@ -82,7 +172,16 @@ sub get_meta {  	my $db  = $opt{db} // $self->{pg}->db;  	my $eva = $opt{eva}; -	my $res = $db->select( 'related_stations', ['meta'], { eva => $eva } ); +	$opt{backend_id} //= $self->get_backend_id( %opt, db => $db ); + +	my $res = $db->select( +		'related_stations', +		['meta'], +		{ +			eva        => $eva, +			backend_id => $opt{backend_id} +		} +	);  	my @ret;  	while ( my $row = $res->hash ) { @@ -93,9 +192,12 @@ sub get_meta {  }  sub get_for_autocomplete { -	my ($self) = @_; +	my ( $self, %opt ) = @_; + +	$opt{backend_id} //= $self->get_backend_id(%opt); -	my $res = $self->{pg}->db->select( 'stations', ['name'] ); +	my $res = $self->{pg} +	  ->db->select( 'stations', ['name'], { source => $opt{backend_id} } );  	my %ret;  	while ( my $row = $res->hash ) { @@ -113,18 +215,34 @@ sub get_by_eva {  		return;  	} -	my $db = $opt{db} // $self->{pg}->db; +	$opt{db}         //= $self->{pg}->db; +	$opt{backend_id} //= $self->get_backend_id(%opt); -	return $db->select( 'stations', '*', { eva => $eva } )->hash; +	return $opt{db}->select( +		'stations', +		'*', +		{ +			eva    => $eva, +			source => $opt{backend_id} +		} +	)->hash;  }  # Fast  sub get_by_evas { -	my ( $self, @evas ) = @_; +	my ( $self, %opt ) = @_; -	my @ret -	  = $self->{pg}->db->select( 'stations', '*', { eva => { '=', \@evas } } ) -	  ->hashes->each; +	$opt{db}         //= $self->{pg}->db; +	$opt{backend_id} //= $self->get_backend_id(%opt); + +	my @ret = $self->{pg}->db->select( +		'stations', +		'*', +		{ +			eva    => { '=', $opt{evas} }, +			source => $opt{backend_id} +		} +	)->hashes->each;  	return @ret;  } @@ -132,10 +250,18 @@ sub get_by_evas {  sub get_by_name {  	my ( $self, $name, %opt ) = @_; -	my $db = $opt{db} // $self->{pg}->db; +	$opt{db}         //= $self->{pg}->db; +	$opt{backend_id} //= $self->get_backend_id(%opt); -	return $db->select( 'stations', '*', { name => $name }, { limit => 1 } ) -	  ->hash; +	return $opt{db}->select( +		'stations', +		'*', +		{ +			name   => $name, +			source => $opt{backend_id} +		}, +		{ limit => 1 } +	)->hash;  }  # Slow @@ -152,16 +278,27 @@ sub get_by_names {  sub get_by_ds100 {  	my ( $self, $ds100, %opt ) = @_; -	my $db = $opt{db} // $self->{pg}->db; +	$opt{db}         //= $self->{pg}->db; +	$opt{backend_id} //= $self->get_backend_id(%opt); -	return $db->select( 'stations', '*', { ds100 => $ds100 }, { limit => 1 } ) -	  ->hash; +	return $opt{db}->select( +		'stations', +		'*', +		{ +			ds100  => $ds100, +			source => $opt{backend_id} +		}, +		{ limit => 1 } +	)->hash;  }  # Can be slow  sub search {  	my ( $self, $identifier, %opt ) = @_; +	$opt{db}         //= $self->{pg}->db; +	$opt{backend_id} //= $self->get_backend_id(%opt); +  	if ( $identifier =~ m{ ^ \d+ $ }x ) {  		return $self->get_by_eva( $identifier, %opt )  		  // $self->get_by_ds100( $identifier, %opt ) diff --git a/lib/Travelynx/Model/Traewelling.pm b/lib/Travelynx/Model/Traewelling.pm index 25648cc..c460b1a 100644 --- a/lib/Travelynx/Model/Traewelling.pm +++ b/lib/Travelynx/Model/Traewelling.pm @@ -224,6 +224,7 @@ sub get_pushable_accounts {  			join in_transit_str as i on t.user_id = i.user_id  			where t.push_sync = True  			and i.arr_eva is not null +			and i.backend_id <= 1  			and i.cancelled = False  		}  	); diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 4d90d92..7d3777b 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -205,6 +205,13 @@ sub get_privacy_by {  	return;  } +sub set_backend { +	my ( $self, %opt ) = @_; +	$opt{db} //= $self->{pg}->db; + +	$opt{db}->update('users', {backend_id => $opt{backend_id}}, {id => $opt{uid}}); +} +  sub set_privacy {  	my ( $self, %opt ) = @_;  	my $db           = $opt{db} // $self->{pg}->db; @@ -401,12 +408,13 @@ sub get {  	my $uid = $opt{uid};  	my $user = $db->select( -		'users', +		'users_with_backend',  		'id, name, status, public_level, email, '  		  . 'accept_follows, notifications, '  		  . 'extract(epoch from registered_at) as registered_at_ts, '  		  . 'extract(epoch from last_seen) as last_seen_ts, ' -		  . 'extract(epoch from deletion_requested) as deletion_requested_ts', +		  . 'extract(epoch from deletion_requested) as deletion_requested_ts, ' +		  . 'backend_id, backend_name, hafas',  		{ id => $uid }  	)->hash;  	if ($user) { @@ -443,6 +451,9 @@ sub get {  				time_zone => 'Europe/Berlin'  			  )  			: undef, +			backend_id    => $user->{backend_id}, +			backend_name  => $user->{backend_name}, +			backend_hafas => $user->{hafas},  		};  	}  	return undef; diff --git a/public/static/js/geolocation.js b/public/static/js/geolocation.js index 26ca349..985c15c 100644 --- a/public/static/js/geolocation.js +++ b/public/static/js/geolocation.js @@ -62,7 +62,8 @@ $(document).ready(function() {  	};  	const processLocation = function(loc) { -		$.post('/geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude}, processResult); +		const backend = $('div.geolocation > button').data('backend'); +		$.post('/geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude, backend: backend}, processResult);  	};  	const processError = function(error) { diff --git a/public/static/js/travelynx-actions.js b/public/static/js/travelynx-actions.js index 48e878f..5f58f29 100644 --- a/public/static/js/travelynx-actions.js +++ b/public/static/js/travelynx-actions.js @@ -191,6 +191,7 @@ function tvly_reg_handlers() {  		var link = $(this);  		var req = {  			action: 'checkin', +			hafas: link.data('hafas'),  			station: link.data('station'),  			train: link.data('train'),  			dest: link.data('dest'), @@ -202,6 +203,7 @@ function tvly_reg_handlers() {  		var link = $(this);  		var req = {  			action: 'checkout', +			hafas: link.data('hafas'),  			station: link.data('station'),  			force: link.data('force'),  		}; @@ -232,6 +234,7 @@ function tvly_reg_handlers() {  		var link = $(this);  		var req = {  			action: 'cancelled_from', +			hafas: link.data('hafas'),  			station: link.data('station'),  			ts: link.data('ts'),  			train: link.data('train'), @@ -242,6 +245,7 @@ function tvly_reg_handlers() {  		var link = $(this);  		var req = {  			action: 'cancelled_to', +			hafas: link.data('hafas'),  			station: link.data('station'),  			force: true,  		}; diff --git a/t/22-transit-visibility.t b/t/22-transit-visibility.t index 7e995c5..90f57d1 100644 --- a/t/22-transit-visibility.t +++ b/t/22-transit-visibility.t @@ -341,6 +341,7 @@ $t->app->in_transit->add(  	departure_eva => 8000001,  	train         => $train_dep,  	route         => [], +	backend_id    => $t->app->stations->get_backend_id( iris => 1 ),  );  $t->app->in_transit->set_arrival_eva(  	uid         => $uid1, diff --git a/t/23-journey-visibility.t b/t/23-journey-visibility.t index 2124940..58b305a 100644 --- a/t/23-journey-visibility.t +++ b/t/23-journey-visibility.t @@ -303,6 +303,7 @@ $t->app->in_transit->add(  	departure_eva => 8000001,  	train         => $train_dep,  	route         => [], +	backend_id    => $t->app->stations->get_backend_id( iris => 1 ),  );  $t->app->in_transit->set_arrival_eva(  	uid         => $uid1, diff --git a/t/24-past-visibility.t b/t/24-past-visibility.t index 51c8081..935ab6c 100644 --- a/t/24-past-visibility.t +++ b/t/24-past-visibility.t @@ -266,6 +266,7 @@ $t->app->in_transit->add(  	departure_eva => 8000001,  	train         => $train_dep,  	route         => [], +	backend_id    => $t->app->stations->get_backend_id( iris => 1 ),  );  $t->app->in_transit->set_arrival_eva(  	uid         => $uid1, diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index 19a94bb..2d71d7e 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -343,9 +343,14 @@  				% else {  					% $url = $url . $journey->{train_type} . ' ' . $journey->{train_no} . '/' . $journey->{sched_departure}->epoch . '000?station=' . $journey->{dep_eva};  				% } -				<a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a> +				% if ($journey->{backend_id} <= 1) { +					<a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a> +				% } +				% else { +					  +				% }  				% if ($journey->{extra_data}{trip_id}) { -					<a class="right" style="margin-right: 0;" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?from=<%= $journey->{dep_name} %>&to=<%= $journey->{arr_name} %>&dark=<%= (session('theme') and session('theme') eq 'dark') ? 1 : 0 %>"><i class="material-icons left" aria-hidden="true">map</i> Karte</a> +					<a class="right" style="margin-right: 0;" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?hafas=<%= $journey->{backend_name} // 'DB' %>&from=<%= $journey->{dep_name} %>&to=<%= $journey->{arr_name} %>&dark=<%= (session('theme') and session('theme') eq 'dark') ? 1 : 0 %>"><i class="material-icons left" aria-hidden="true">map</i> Karte</a>  				% }  			</div>  		</div> diff --git a/templates/_checked_out.html.ep b/templates/_checked_out.html.ep index 5a944dc..21db335 100644 --- a/templates/_checked_out.html.ep +++ b/templates/_checked_out.html.ep @@ -3,7 +3,7 @@  		<span class="card-title">Ausgecheckt</span>  		<p>Aus  			%= include '_format_train', journey => $journey -			bis <a href="/s/<%= $journey->{arr_eva} %>?hafas=<%= $journey->{train_id} =~ m{[|]} ? 1 : 0 %>"><%= $journey->{arr_name} %></a></p> +			bis <a href="/s/<%= $journey->{arr_eva} %>?hafas=<%= $journey->{is_hafas} ? $journey->{backend_name} : q{} %>"><%= $journey->{arr_name} %></a></p>  		% if (@{stash('connections_iris') // [] } or @{stash('connections_hafas') // []}) {  			<span class="card-title" style="margin-top: 2ex;">Verbindungen</span>  			<p>Fahrt auswählen zum Einchecken mit Zielwahl.</p> diff --git a/templates/_connections_hafas.html.ep b/templates/_connections_hafas.html.ep index dcf7ec9..43198e2 100644 --- a/templates/_connections_hafas.html.ep +++ b/templates/_connections_hafas.html.ep @@ -1,6 +1,6 @@  <ul class="collection departures connections">  	% for my $res (@{$connections}) { -		% my ($train, $via, $via_arr) = @{$res}; +		% my ($train, $via, $via_arr, $hafas_service) = @{$res};  		% $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{};  		% my $row_class = '';  		% my $link_class = 'action-checkin'; @@ -10,6 +10,7 @@  		% }  		% if ($checkin_from) {  			<li class="collection-item <%= $row_class %> <%= $link_class %>" +			data-hafas="<%= $hafas_service %>"  			data-station="<%= $train->station_eva %>"  			data-train="<%= $train->id %>"  			data-ts="<%= ($train->sched_datetime // $train->datetime)->epoch %>" diff --git a/templates/_departures_hafas.html.ep b/templates/_departures_hafas.html.ep index 9e4d7a4..012db61 100644 --- a/templates/_departures_hafas.html.ep +++ b/templates/_departures_hafas.html.ep @@ -18,6 +18,7 @@  		</li>  	% }  	<li class="collection-item <%= $link_class %> <%= $row_class %>" +		data-hafas="<%= $hafas %>"  		data-station="<%= $result->station_eva %>"  		data-train="<%= $result->id %>"  		data-ts="<%= ($result->sched_datetime // $result->datetime)->epoch %>" diff --git a/templates/api_documentation.html.ep b/templates/api_documentation.html.ep index 9c9ee1f..d8fc66f 100644 --- a/templates/api_documentation.html.ep +++ b/templates/api_documentation.html.ep @@ -30,6 +30,11 @@  			"actionTime" : 1234567, (UNIX-Timestamp des letzten Checkin/Checkout)<br/>  			"checkedIn" : true / false,<br/>  			"comment": "Kommentar",<br/> +			"backend": {<br/> +				"id": 1,<br/> +				"name": "DB",<br/> +				"type": "HAFAS",<br/> +			},<br/>  			"fromStation" : { (letzter Checkin)<br/>  				"name" : "Essen Hbf",<br/>  				"ds100" : "EE", (ggf. null)<br/> @@ -122,6 +127,7 @@  		{<br/>  			"token" : "<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>",<br/>  			"action" : "checkin",<br/> +			"hafas" : "DB", (HAFAS-Instanz – Default: Deutsche Bahn)<br/>  			"train" : {<br/>  				"journeyID" : "1|1426396|4|80|19082023",<br/>  			}<br/> diff --git a/templates/departures.html.ep b/templates/departures.html.ep index 9fd79e6..71dd0ad 100644 --- a/templates/departures.html.ep +++ b/templates/departures.html.ep @@ -1,26 +1,27 @@  <div class="row"> -	<div class="col s12"> -		<h2> -			<i class="material-icons "  aria-hidden="true"><%= param('hafas') ? 'directions' : 'train' %></i> +	<div class="col s8"> +		<strong style="font-size: 120%;">  			<%= $station %> -		</h2> +		</strong>  		% for my $related_station (sort { $a->{name} cmp $b->{name} } @{$related_stations}) {  			+ <%= $related_station->{name} %> <br/>  		% }  	</div> -</div> -% if ($api_link) { -<div class="row"> -	<div class="col s12 center-align"> -	% if (param('hafas')) { -		<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left"  aria-hidden="true">train</i>zum Schienenverkehr</a> -	% } -	% else { -		<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left"  aria-hidden="true">directions</i>zum Nahverkehr</a> -	% } +	<div class="col s4 center-align"> +		% my $self_link = url_for('sstation', station => param('station')); +		% if (param('hafas')) { +			<span class="btn-small disabled"><i class="material-icons left" aria-hidden="true">directions</i> <%= param('hafas') %> (HAFAS)</span> +		% } +		% else { +			% if ($user->{backend_id}) { +				<a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %> (<%= $user->{backend_hafas} ? 'HAFAS' : q{} %>)</a> +			% } +			% else { +				<a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">train</i>DB (IRIS-TTS)</a> +			% } +		% }  	</div>  </div> -% }  % my $have_connections = 0;  % if ($user_status->{checked_in}) { @@ -40,10 +41,10 @@  				</div>  				<div class="card-action">  					% if ($can_check_out) { -						<a class="action-undo" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>"  style="margin-right: 0;"> +						<a class="action-undo" data-hafas="<%= param('hafas') // q{} %>" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>"  style="margin-right: 0;">  							<i class="material-icons left" aria-hidden="true">undo</i> Rückgängig  						</a> -						<a class="action-checkout right" data-station="<%= $eva %>" data-force="1"> +						<a class="action-checkout right" data-hafas="<%= param('hafas') // q{} %>" data-station="<%= $eva %>" data-force="1">  							Hier auschecken  						</a>  					% } @@ -51,7 +52,7 @@  						<a class="action-undo" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>"  style="margin-right: 0;">  							<i class="material-icons left" aria-hidden="true">undo</i> Rückgängig  						</a> -						<a class="action-checkout right" data-station="<%= $eva %>" data-force="1"> +						<a class="action-checkout right" data-hafas="<%= param('hafas') // q{} %>" data-station="<%= $eva %>" data-force="1">  							<i class="material-icons left" aria-hidden="true">gps_off</i>  							Hier auschecken  						</a> @@ -139,7 +140,7 @@  		</p>  		% if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) {  			% if ($hafas) { -				%= include '_departures_hafas', results => $results; +				%= include '_departures_hafas', results => $results, hafas => $hafas;  			% }  			% else {  				%= include '_departures_iris', results => $results; diff --git a/templates/disambiguation.html.ep b/templates/disambiguation.html.ep index 9fc1e4c..af7d1dd 100644 --- a/templates/disambiguation.html.ep +++ b/templates/disambiguation.html.ep @@ -13,7 +13,7 @@  	<div class="col s12">  		<ul class="suggestions">  			% for my $suggestion (@{$suggestions // []}) { -				<li><a href="<%= url_for('station' => $suggestion->{eva}) . (param('hafas') ? '?hafas=DB' : q{}) %>"><%= $suggestion->{name} %></a></li> +				<li><a href="<%= url_for('station' => $suggestion->{eva}) . (param('hafas') ? '?hafas=' . param('hafas') : q{}) %>"><%= $suggestion->{name} %></a></li>  			% }  		</ul>  	</div> diff --git a/templates/exception.html.ep b/templates/exception.html.ep index ec01ad2..9b8697c 100644 --- a/templates/exception.html.ep +++ b/templates/exception.html.ep @@ -20,8 +20,15 @@  			Timestamp:  			%= DateTime->now(time_zone => 'Europe/Berlin')->strftime("%d/%b/%Y:%H:%M:%S %z")  			<br/><br/> -			Message: -			%= ref($exception) ? (split(qr{\n}, $exception->message))[0] : $exception +			% if (ref($exception)) { +				Trace:<br/> +				% for my $line (split(qr{\n}, $exception->message)) { +					<%= $line %><br/> +				% } +			% } +			% else { +				Message: <%= $exception %> +			% }  		</p>  	</div>  </div> diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep index 45bfb21..22478ed 100644 --- a/templates/landingpage.html.ep +++ b/templates/landingpage.html.ep @@ -1,5 +1,6 @@  % if (is_user_authenticated()) {  	% my $status = stash('user_status'); +	% my $user = stash('user');  	% if (stash('error')) {  		<div class="row">  			<div class="col s12"> @@ -51,32 +52,40 @@  				% if ( @{stash('timeline') // [] } ) {  					%= include '_timeline_link', timeline => stash('timeline')  				% } -				<div class="card"> -					<div class="card-content"> -						<span class="card-title">Hallo, <%= current_user->{name} %>!</span> -						<p>Du bist gerade nicht eingecheckt.</p> -						<div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>"> -							<button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button> -						</div> -						%= form_for 'list_departures' => begin +				%= form_for 'list_departures' => begin +					<div class="card"> +						<div class="card-content"> +							<span class="card-title">Hallo, <%= $user->{name} %>!</span> +							<p>Du bist gerade nicht eingecheckt.</p> +							<p> +							<div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>" data-backend=<%= $user->{backend_id} %>"> +								<button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button> +							</div>  							<div class="input-field">  								%= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef  								<label for="station">Manuelle Eingabe</label>  							</div> -							<div class="center-align"> -								<button class="btn waves-effect waves-light btn-flat" type="submit" name="action" value="departures"> -									<i class="material-icons left" aria-hidden="true">send</i> -									Abfahrten -								</button> -							</div> -						%= end +							</p> +						</div> +						<div class="card-action"> +							% if ($user->{backend_id}) { +								<a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %> (<%= $user->{backend_hafas} ? 'HAFAS' : q{} %>)</a> +							% } +							% else { +								<a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true">train</i>DB (IRIS-TTS)</a> +							% } +							<button class="btn right waves-effect waves-light btn-flat" type="submit" name="action" value="departures"> +								<i class="material-icons left" aria-hidden="true">send</i> +								Abfahrten +							</button> +						</div>  					</div> -				</div> +				%= end  			% }  		</div>  	</div>  	<h2 style="margin-left: 0.75rem;">Letzte Fahrten</h2> -	%= include '_history_trains', date_format => '%d.%m.%Y', journeys => [journeys->get(uid => current_user->{id}, limit => 5, with_datetime => 1)]; +	%= include '_history_trains', date_format => '%d.%m.%Y', journeys => [journeys->get(uid => $user->{id}, limit => 5, with_datetime => 1)];  % }  % else {  	<div class="row"> diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep index 01b8018..4edf805 100644 --- a/templates/layouts/default.html.ep +++ b/templates/layouts/default.html.ep @@ -62,7 +62,7 @@  		%= javascript "/static/${av}/js/geolocation${min}.js"  	% }  	% if (stash('with_autocomplete')) { -		%= javascript "/dyn/${av}/autocomplete.js", defer => undef +		%= javascript "/dyn/${av}/autocomplete.js?backend_id=" . (stash('backend_id') // 1), defer => undef  	% }  	% if (stash('with_map')) {  		%= javascript "/static/${av}/leaflet/leaflet.js" diff --git a/templates/select_backend.html.ep b/templates/select_backend.html.ep new file mode 100644 index 0000000..4eb4cc9 --- /dev/null +++ b/templates/select_backend.html.ep @@ -0,0 +1,34 @@ +<div class="row"> +	<div class="col s12"> +		<h2>Backend auswählen</h2> +		<p style="text-align: justify;"> +			Das ausgewählte Backend bestimmt die Datenquelle für Fahrten in travelynx. +			<strong>DB (HAFAS)</strong> ist eine gute Wahl für Nah-, Regional- und Fernverkehr in Deutschland und (teilweise) Nachbarländern. +			<strong>IRIS-TTS</strong> unterstützt ausschließlich Schienenverkehr; im Gegensatz zum HAFAS sind hier detaillierte Verspätungsgründe verfügbar. +			Die restlichen Backends können sich für Fahrten außerhalb Deutschlands sowie in einzelnen innerdeutschen Verkehrsverbünden lohnen. +			Fahrten im Ausland fehlen im DB-HAFAS oft; Fahrten im Inland können mit spezifischen Backends genauere Daten haben – z.B. liefert das AVV-HAFAS im Gegensatz zum DB-HAFAS Kartendaten für dortige Buslinien. +		</p> +	</div> +</div> +%= form_for '/account/select_backend' => (method => 'POST') => begin +	% if (stash('redirect_to')) { +		%= hidden_field 'redirect_to' => stash('redirect_to') +	% } +	% for my $backend (@{ stash('backends') // [] }) { +		<div class="row"> +			<div class="col s12 m4 l4 center-align"> +				<button class="btn waves-effect waves-light <%= $backend->{id} == $user->{backend_id} ? 'disabled' : q{} %>" type="submit" name="backend" value="<%= $backend->{id} %>"> +					<%= $backend->{name} %> (<%= $backend->{type} %>) +				</button> +			</div> +			<div class="col s12 m8 l8"> +				% if ($backend->{longname}) { +					<%= $backend->{longname} %> +				% } +				% if ($backend->{id} == $user->{backend_id}) { +					(aktuell ausgewählt) +				% } +			</div> +		</div> +	% } +%= end diff --git a/templates/traewelling.html.ep b/templates/traewelling.html.ep index c1f2b7d..1e0d65d 100644 --- a/templates/traewelling.html.ep +++ b/templates/traewelling.html.ep @@ -157,26 +157,21 @@  				<div>  					<label>  						%= check_box toot => 1 -						<span>… Checkin auf Mastodon veröffentlichen</span> -					</label> -				</div> -				<div> -					<label> -						%= check_box tweet => 1 -						<span>… Checkin auf Twitter veröffentlichen</span> +						<span>… Checkin im Fediverse veröffentlichen</span>  					</label>  				</div>  				<p>Die Synchronisierung erfolgt spätestens drei Minuten nach der -					Zielwahl. Beachte, dass die Synchronisierung travelynx -					→ Träwelling unabhängig von der eingestellten Sichtbarkeit -					des Checkins erfolgt. travelynx reicht die Sichtbarkeit -					aber an Träwelling weiter. -					Träwelling-Checkins können von travelynx aktuell nicht -					rückgängig gemacht werden. Eine nachträgliche Änderung der -					Zielstation wird nicht übernommen. Mastodon und Twitter beziehen -					sich auf die in den <a +					Zielwahl. Es werden ausschließlich Checkins mittels +					DB (IRIS-TTS) und DB (HAFAS) synchornisiert. Beachte, dass +					die Synchronisierung travelynx → Träwelling unabhängig von +					der eingestellten Sichtbarkeit des Checkins erfolgt. +					travelynx reicht die Sichtbarkeit aber an Träwelling +					weiter.  Träwelling-Checkins können von travelynx aktuell +					nicht rückgängig gemacht werden. Eine nachträgliche +					Änderung der Zielstation wird nicht übernommen. Fediverse +					bezieht sich auf den in den <a  					href="https://traewelling.de/settings">Träwelling-Einstellungen</a> -					verknüpften Accounts.</p> +					verknüpften Account.</p>  			</div>  			<div class="input-field col s12">  				<div> diff --git a/templates/use_history.html.ep b/templates/use_history.html.ep index 9b76e98..f91ca16 100644 --- a/templates/use_history.html.ep +++ b/templates/use_history.html.ep @@ -5,9 +5,13 @@  			Travelynx kann anhand deiner vergangenen Fahrten Verbindungen zum  			Einchecken vorschlagen. Fährst zu z.B regelmäßig von Dortmund Hbf  			nach Essen Hbf, werden dir in Dortmund bevorzugt Fahrten angezeigt, die -			Essen passieren.  Bei Auswahl dieser wird nicht nur in die Fahrt eingecheckt, +			Essen passieren. Bei Auswahl dieser wird nicht nur in die Fahrt eingecheckt,  			sondern auch direkt Essen Hbf als Ziel eingetragen.  		<p/> +		<p> +			Beachte, dass nicht alle von travelynx unterstützten Backends die +			für dieses Feature notwendigen Daten bereitstellen. +		</p>  <!--		<p>  			Falls du das nicht nützlich findest oder nicht möchtest, dass deine  			regelmäßigen (Anschluss-)Züge auf deinem Bildschirm sichtbar sind,  | 
