diff options
Diffstat (limited to 'lib')
| -rwxr-xr-x | lib/Travelynx.pm | 170 | ||||
| -rw-r--r-- | lib/Travelynx/Command/database.pm | 153 | ||||
| -rw-r--r-- | lib/Travelynx/Command/work.pm | 105 | ||||
| -rw-r--r-- | lib/Travelynx/Controller/Account.pm | 19 | ||||
| -rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 103 | ||||
| -rw-r--r-- | lib/Travelynx/Helper/DBRIS.pm | 138 | ||||
| -rw-r--r-- | lib/Travelynx/Model/InTransit.pm | 132 | ||||
| -rwxr-xr-x | lib/Travelynx/Model/Journeys.pm | 11 | ||||
| -rw-r--r-- | lib/Travelynx/Model/Stations.pm | 82 | ||||
| -rw-r--r-- | lib/Travelynx/Model/Users.pm | 9 | 
10 files changed, 874 insertions, 48 deletions
| diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 2b7fdf5..98ba4aa 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -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; @@ -217,6 +218,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) = @_;  			state $hafas = Travelynx::Helper::HAFAS->new( @@ -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);  			} @@ -531,6 +548,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}, @@ -1901,8 +2059,10 @@ sub startup {  				) ? \1 : \0,  				comment => $status->{comment},  				backend => { -					id   => $status->{backend_id}, -					type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS', +					id => $status->{backend_id}, +					type => $status->{ds_dbris} ? 'DBRIS' +					: $status->{is_hafas} ? 'HAFAS' +					: 'IRIS-TTS',  					name => $status->{backend_name},  				},  				fromStation => { diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 6ba80a3..f72a38c 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -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 { diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 5385e9b..55c2005 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -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 { diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 9cd0edb..43b0683 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -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 ); diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 1ca9d4a..a6e56b9 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -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,7 +1237,23 @@ sub redirect_to_station {  	my ($self) = @_;  	my $station = $self->param('station'); -	$self->redirect_to("/s/${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 { diff --git a/lib/Travelynx/Helper/DBRIS.pm b/lib/Travelynx/Helper/DBRIS.pm new file mode 100644 index 0000000..e647cc5 --- /dev/null +++ b/lib/Travelynx/Helper/DBRIS.pm @@ -0,0 +1,138 @@ +package Travelynx::Helper::DBRIS; + +# Copyright (C) 2025 Birte Kristina Friesel +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +use strict; +use warnings; +use 5.020; +use utf8; + +use DateTime; +use Encode qw(decode); +use JSON; +use Mojo::Promise; +use Mojo::UserAgent; +use Travel::Status::DE::DBRIS; + +sub new { +	my ( $class, %opt ) = @_; + +	my $version = $opt{version}; + +	$opt{header} +	  = { 'User-Agent' => +"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx" +	  }; + +	return bless( \%opt, $class ); +} + +sub get_station_id_p { +	my ( $self, $station_name ) = @_; +	my $promise = Mojo::Promise->new; +	Travel::Status::DE::DBRIS->new_p( +		locationSearch => $station_name, +		cache          => $self->{cache}, +		lwp_options    => { +			timeout => 10, +			agent   => $self->{header}{'User-Agent'}, +		}, +		promise    => 'Mojo::Promise', +		user_agent => Mojo::UserAgent->new, +	)->then( +		sub { +			my ($dbris) = @_; +			my $found; +			for my $result ( $dbris->results ) { +				if ( defined $result->eva ) { +					$promise->resolve($result); +					return; +				} +			} +			$promise->reject("Unable to find station '$station_name'"); +			return; +		} +	)->catch( +		sub { +			my ($err) = @_; +			$promise->reject("'$err' while trying to look up '$station_name'"); +			return; +		} +	)->wait; +	return $promise; +} + +sub get_departures_p { +	my ( $self, %opt ) = @_; + +	my $agent = $self->{user_agent}; + +	if ( $opt{station} =~ m{ [@] L = (?<eva> \d+ ) [@] }x ) { +		$opt{station} = { +			eva => $+{eva}, +			id  => $opt{station}, +		}; +	} + +	my $when = ( +		  $opt{timestamp} +		? $opt{timestamp}->clone +		: DateTime->now( time_zone => 'Europe/Berlin' ) +	)->subtract( minutes => $opt{lookbehind} ); +	return Travel::Status::DE::DBRIS->new_p( +		station    => $opt{station}, +		datetime   => $when, +		cache      => $self->{cache}, +		promise    => 'Mojo::Promise', +		user_agent => $agent->request_timeout(10), +	); +} + +sub get_journey_p { +	my ( $self, %opt ) = @_; + +	my $promise = Mojo::Promise->new; +	my $now     = DateTime->now( time_zone => 'Europe/Berlin' ); + +	my $agent = $self->{user_agent}; +	if ( my $proxy = $self->{service_config}{dbris}{proxy} ) { +		$agent = Mojo::UserAgent->new; +		$agent->proxy->http($proxy); +		$agent->proxy->https($proxy); +	} + +	Travel::Status::DE::DBRIS->new_p( +		journey       => $opt{trip_id}, +		with_polyline => $opt{with_polyline}, +		cache         => $self->{realtime_cache}, +		promise       => 'Mojo::Promise', +		user_agent    => $agent->request_timeout(10), +	)->then( +		sub { +			my ($dbris) = @_; +			my $journey = $dbris->result; + +			if ($journey) { +				$self->{log}->debug("get_journey_p($opt{trip_id}): success"); +				$promise->resolve($journey); +				return; +			} +			$self->{log}->debug("get_journey_p($opt{trip_id}): no journey"); +			$promise->reject('no journey'); +			return; +		} +	)->catch( +		sub { +			my ($err) = @_; +			$self->{log}->debug("get_journey_p($opt{trip_id}): error $err"); +			$promise->reject($err); +			return; +		} +	)->wait; + +	return $promise; +} + +1; diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index 43ecb90..2b9832c 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -104,6 +104,8 @@ sub add {  	my $json = JSON->new;  	if ($train) { + +		# IRIS  		$db->insert(  			'in_transit',  			{ @@ -134,7 +136,9 @@ sub add {  			}  		);  	} -	elsif ( $journey and $stop ) { +	elsif ( $journey and $stop and $journey->can('id') ) { + +		# HAFAS  		my @route;  		my $product = $journey->product_at( $stop->loc->eva )  		  // $journey->product; @@ -188,6 +192,56 @@ sub add {  			}  		);  	} +	elsif ( $journey and $stop ) { + +		# DBRIS +		my @route; +		for my $j_stop ( $journey->route ) { +			push( +				@route, +				[ +					$j_stop->name, +					$j_stop->eva, +					{ +						sched_arr => _epoch( $j_stop->sched_arr ), +						sched_dep => _epoch( $j_stop->sched_dep ), +						rt_arr    => _epoch( $j_stop->rt_arr ), +						rt_dep    => _epoch( $j_stop->rt_dep ), +						arr_delay => $j_stop->arr_delay, +						dep_delay => $j_stop->dep_delay, +						load      => undef, +						lat       => $j_stop->lat, +						lon       => $j_stop->lon, +					} +				] +			); +		} +		$db->insert( +			'in_transit', +			{ +				user_id   => $uid, +				cancelled => $stop->{dep_cancelled} +				? 1 +				: 0, +				checkin_station_id => $stop->eva, +				checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ), +				dep_platform => $stop->platform, +				train_type   => $journey->type, +				train_no     => $journey->number, +				train_id     => $data->{trip_id}, +				sched_departure => $stop->sched_dep, +				real_departure  => $stop->rt_dep // $stop->sched_dep, +				route           => $json->encode( \@route ), +				data            => JSON->new->encode( +					{ +						rt => $stop->{rt_dep} ? 1 : 0, +						%{ $data // {} } +					} +				), +				backend_id => $backend_id, +			} +		); +	}  	else {  		die('neither train nor journey specified');  	} @@ -731,6 +785,33 @@ sub update_departure_cancelled {  	return $rows;  } +sub update_departure_dbris { +	my ( $self, %opt ) = @_; +	my $uid     = $opt{uid}; +	my $db      = $opt{db} // $self->{pg}->db; +	my $dep_eva = $opt{dep_eva}; +	my $arr_eva = $opt{arr_eva}; +	my $journey = $opt{journey}; +	my $stop    = $opt{stop}; +	my $json    = JSON->new; + +	# selecting on user_id and train_no avoids a race condition if a user checks +	# into a new train while we are fetching data for their previous journey. In +	# this case, the new train would receive data from the previous journey. +	$db->update( +		'in_transit', +		{ +			real_departure => $stop->{rt_dep}, +		}, +		{ +			user_id             => $uid, +			train_id            => $opt{train_id}, +			checkin_station_id  => $dep_eva, +			checkout_station_id => $arr_eva, +		} +	); +} +  sub update_departure_hafas {  	my ( $self, %opt ) = @_;  	my $uid     = $opt{uid}; @@ -800,6 +881,55 @@ sub update_arrival {  	return $rows;  } +sub update_arrival_dbris { +	my ( $self, %opt ) = @_; +	my $uid     = $opt{uid}; +	my $db      = $opt{db} // $self->{pg}->db; +	my $dep_eva = $opt{dep_eva}; +	my $arr_eva = $opt{arr_eva}; +	my $journey = $opt{journey}; +	my $stop    = $opt{stop}; +	my $json    = JSON->new; + +	my @route; +	for my $j_stop ( $journey->route ) { +		push( +			@route, +			[ +				$j_stop->name, +				$j_stop->eva, +				{ +					sched_arr => _epoch( $j_stop->sched_arr ), +					sched_dep => _epoch( $j_stop->sched_dep ), +					rt_arr    => _epoch( $j_stop->rt_arr ), +					rt_dep    => _epoch( $j_stop->rt_dep ), +					arr_delay => $j_stop->arr_delay, +					dep_delay => $j_stop->dep_delay, +					lat       => $j_stop->lat, +					lon       => $j_stop->lon, +				} +			] +		); +	} + +	# selecting on user_id and train_no avoids a race condition if a user checks +	# into a new train while we are fetching data for their previous journey. In +	# this case, the new train would receive data from the previous journey. +	$db->update( +		'in_transit', +		{ +			real_arrival => $stop->{rt_arr}, +			route        => $json->encode( [@route] ), +		}, +		{ +			user_id             => $uid, +			train_id            => $opt{train_id}, +			checkin_station_id  => $dep_eva, +			checkout_station_id => $arr_eva, +		} +	); +} +  sub update_arrival_hafas {  	my ( $self, %opt ) = @_;  	my $uid     = $opt{uid}; diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index 905c426..f5bc9f1 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -549,7 +549,7 @@ sub get {  	my @select  	  = ( -		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) +		qw(journey_id is_dbris 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, @@ -607,6 +607,7 @@ sub get {  		my $ref = {  			id                   => $entry->{journey_id}, +			is_dbris             => $entry->{is_dbris},  			is_iris              => $entry->{is_iris},  			is_hafas             => $entry->{is_hafas},  			backend_name         => $entry->{backend_name}, @@ -870,8 +871,8 @@ sub get_latest_checkout_stations {  	my $res = $db->select(  		'journeys_str',  		[ -			'arr_name',     'arr_eva', 'train_id', 'backend_id', -			'backend_name', 'is_hafas' +			'arr_name',     'arr_eva',  'train_id', 'backend_id', +			'backend_name', 'is_dbris', 'is_hafas'  		],  		{  			user_id   => $uid, @@ -895,6 +896,7 @@ sub get_latest_checkout_stations {  			{  				name       => $row->{arr_name},  				eva        => $row->{arr_eva}, +				dbris      => $row->{is_dbris} ? $row->{backend_name} : 0,  				hafas      => $row->{is_hafas} ? $row->{backend_name} : 0,  				backend_id => $row->{backend_id},  			} @@ -1883,7 +1885,8 @@ sub get_connection_targets {  	);  	my @destinations  	  = $res->hashes->grep( sub { shift->{count} >= $min_count } ) -	  ->map( sub { shift->{dest} } )->each; +	  ->map( sub { shift->{dest} } ) +	  ->each;  	@destinations = $self->{stations}->get_by_evas(  		backend_id => $opt{backend_id},  		evas       => [@destinations] diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index 76fd452..3d6549f 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -25,11 +25,25 @@ sub get_backend_id {  	if ( $opt{hafas} and $self->{backend_id}{hafas}{ $opt{hafas} } ) {  		return $self->{backend_id}{hafas}{ $opt{hafas} };  	} +	if ( $opt{dbris} and $self->{backend_id}{dbris}{ $opt{dbris} } ) { +		return $self->{backend_id}{dbris}{ $opt{dbris} }; +	}  	my $db         = $opt{db} // $self->{pg}->db;  	my $backend_id = 0; -	if ( $opt{hafas} ) { +	if ( $opt{dbris} ) { +		$backend_id = $db->select( +			'backends', +			['id'], +			{ +				dbris => 1, +				name  => $opt{dbris} +			} +		)->hash->{id}; +		$self->{backend_id}{dbris}{ $opt{dbris} } = $backend_id; +	} +	elsif ( $opt{hafas} ) {  		$backend_id = $db->select(  			'backends',  			['id'], @@ -44,31 +58,25 @@ sub get_backend_id {  	return $backend_id;  } -sub get_hafas_name { +sub get_backend {  	my ( $self, %opt ) = @_; -	if ( exists $self->{hafas_name}{ $opt{backend_id} } ) { -		return $self->{hafas_name}{ $opt{backend_id} }; +	if ( $self->{backend_cache}{ $opt{backend_id} } ) { +		return $self->{backend_cache}{ $opt{backend_id} };  	} -	my $db = $opt{db} // $self->{pg}->db; -	my $hafas_name; +	my $db  = $opt{db} // $self->{pg}->db;  	my $ret = $db->select(  		'backends', -		['name'], +		'*',  		{ -			hafas => 1, -			id    => $opt{backend_id}, +			id => $opt{backend_id},  		}  	)->hash; -	if ($ret) { -		$hafas_name = $ret->{name}; -	} - -	$self->{hafas_name}{ $opt{backend_id} } = $hafas_name; +	$self->{backend_cache}{ $opt{backend_id} } = $ret; -	return $hafas_name; +	return $ret;  }  sub get_backends { @@ -76,7 +84,8 @@ sub get_backends {  	$opt{db} //= $self->{pg}->db; -	my $res = $opt{db}->select( 'backends', [ 'id', 'name', 'iris', 'hafas' ] ); +	my $res = $opt{db} +	  ->select( 'backends', [ 'id', 'name', 'iris', 'hafas', 'dbris' ] );  	my @ret;  	while ( my $row = $res->hash ) { @@ -86,6 +95,7 @@ sub get_backends {  				id    => $row->{id},  				name  => $row->{name},  				iris  => $row->{iris}, +				dbris => $row->{dbris},  				hafas => $row->{hafas},  			}  		); @@ -97,11 +107,49 @@ sub get_backends {  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 ( $opt{dbris} ) { +		if ( +			my $s = $self->get_by_eva( +				$stop->eva, +				db         => $opt{db}, +				backend_id => $opt{backend_id} +			) +		  ) +		{ +			$opt{db}->update( +				'stations', +				{ +					name     => $stop->name, +					lat      => $stop->lat, +					lon      => $stop->lon, +					archived => 0 +				}, +				{ +					eva    => $stop->eva, +					source => $opt{backend_id} +				} +			); +			return; +		} +		$opt{db}->insert( +			'stations', +			{ +				eva      => $stop->eva, +				name     => $stop->name, +				lat      => $stop->lat, +				lon      => $stop->lon, +				source   => $opt{backend_id}, +				archived => 0 +			} +		); +		return; +	} + +	my $loc = $stop->loc;  	if (  		my $s = $self->get_by_eva(  			$loc->eva, diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 7d3777b..e3d6f7a 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -209,7 +209,11 @@ sub set_backend {  	my ( $self, %opt ) = @_;  	$opt{db} //= $self->{pg}->db; -	$opt{db}->update('users', {backend_id => $opt{backend_id}}, {id => $opt{uid}}); +	$opt{db}->update( +		'users', +		{ backend_id => $opt{backend_id} }, +		{ id         => $opt{uid} } +	);  }  sub set_privacy { @@ -414,7 +418,7 @@ sub get {  		  . '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, ' -		  . 'backend_id, backend_name, hafas', +		  . 'backend_id, backend_name, hafas, dbris',  		{ id => $uid }  	)->hash;  	if ($user) { @@ -453,6 +457,7 @@ sub get {  			: undef,  			backend_id    => $user->{backend_id},  			backend_name  => $user->{backend_name}, +			backend_dbris => $user->{dbris},  			backend_hafas => $user->{hafas},  		};  	} | 
