summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rwxr-xr-xlib/Travelynx.pm833
-rw-r--r--lib/Travelynx/Command/database.pm792
-rw-r--r--lib/Travelynx/Command/dumpconfig.pm1
-rw-r--r--lib/Travelynx/Command/dumpstops.pm7
-rw-r--r--lib/Travelynx/Command/influxdb.pm101
-rw-r--r--lib/Travelynx/Command/integritycheck.pm201
-rw-r--r--lib/Travelynx/Command/maintenance.pm16
-rw-r--r--lib/Travelynx/Command/traewelling.pm34
-rw-r--r--lib/Travelynx/Command/work.pm219
-rw-r--r--lib/Travelynx/Controller/Account.pm174
-rwxr-xr-xlib/Travelynx/Controller/Api.pm23
-rw-r--r--lib/Travelynx/Controller/Passengerrights.pm7
-rwxr-xr-xlib/Travelynx/Controller/Profile.pm67
-rwxr-xr-xlib/Travelynx/Controller/Traveling.pm428
-rw-r--r--lib/Travelynx/Helper/DBDB.pm86
-rw-r--r--lib/Travelynx/Helper/HAFAS.pm46
-rw-r--r--lib/Travelynx/Helper/IRIS.pm12
-rw-r--r--lib/Travelynx/Helper/Sendmail.pm2
-rw-r--r--lib/Travelynx/Helper/Traewelling.pm2
-rw-r--r--lib/Travelynx/Model/InTransit.pm152
-rwxr-xr-xlib/Travelynx/Model/Journeys.pm245
-rw-r--r--lib/Travelynx/Model/Stations.pm217
-rw-r--r--lib/Travelynx/Model/Traewelling.pm1
-rw-r--r--lib/Travelynx/Model/Users.pm51
24 files changed, 2574 insertions, 1143 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index 4749d65..f8ace80 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -177,23 +177,14 @@ sub startup {
}
);
- $self->attr(
- coordinates_by_station => sub {
- my $legacy_names = $self->app->renamed_station;
- my $location = $self->stations->get_latlon_by_name;
- while ( my ( $old_name, $new_name ) = each %{$legacy_names} ) {
- $location->{$old_name} = $location->{$new_name};
- }
- return $location;
- }
- );
-
# https://de.wikipedia.org/wiki/Liste_nach_Gemeinden_und_Regionen_benannter_IC/ICE-Fahrzeuge#Namensgebung_ICE-Triebz%C3%BCge_nach_Gemeinden
# via https://github.com/marudor/bahn.expert/blob/main/src/server/coachSequence/TrainNames.ts
$self->attr(
ice_name => sub {
- my $id_to_name = JSON->new->utf8->decode(
- scalar read_file('share/ice_names.json') );
+ state $id_to_name = {
+ Travel::Status::DE::DBWagenreihung::Group::name_to_designation(
+ )
+ };
return $id_to_name;
}
);
@@ -297,13 +288,12 @@ sub startup {
journeys => sub {
my ($self) = @_;
state $journeys = Travelynx::Model::Journeys->new(
- log => $self->app->log,
- pg => $self->pg,
- in_transit => $self->in_transit,
- stats_cache => $self->journey_stats_cache,
- renamed_station => $self->app->renamed_station,
- latlon_by_station => $self->app->coordinates_by_station,
- stations => $self->stations,
+ log => $self->app->log,
+ pg => $self->pg,
+ in_transit => $self->in_transit,
+ stats_cache => $self->journey_stats_cache,
+ renamed_station => $self->app->renamed_station,
+ stations => $self->stations,
);
}
);
@@ -362,11 +352,12 @@ sub startup {
dbdb => sub {
my ($self) = @_;
state $dbdb = Travelynx::Helper::DBDB->new(
- log => $self->app->log,
- cache => $self->app->cache_iris_main,
- root_url => $self->base_url_for('/')->to_abs,
- user_agent => $self->ua,
- version => $self->app->config->{version},
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->base_url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->app->config->{version},
);
}
);
@@ -459,7 +450,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);
}
@@ -493,7 +484,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 ($@) {
@@ -506,6 +499,17 @@ sub startup {
# mustn't be called during a transaction
if ( not $opt{in_transaction} ) {
$self->add_route_timestamps( $uid, $train, 1 );
+ $self->add_wagonorder(
+ uid => $uid,
+ train_id => $train->train_id,
+ is_departure => 1,
+ eva => $eva,
+ datetime => $train->sched_departure,
+ train_type => $train->type,
+ train_no => $train->train_no
+ );
+ $self->add_stationinfo( $uid, 1, $train->train_id,
+ $eva );
$self->run_hook( $uid, 'checkin' );
}
@@ -537,6 +541,7 @@ sub startup {
my $promise = Mojo::Promise->new;
$self->hafas->get_journey_p(
+ service => $opt{hafas},
trip_id => $train_id,
with_polyline => 1
)->then(
@@ -553,21 +558,27 @@ sub startup {
}
if ( not $found ) {
$promise->reject(
- "Did not find journey $train_id at $station");
+"Did not find stop '$station' within journey '$train_id'"
+ );
return;
}
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,
+ uid => $uid,
+ db => $db,
+ journey => $journey,
+ stop => $found,
+ data => { trip_id => $journey->id },
+ backend_id => $self->stations->get_backend_id(
+ hafas => $opt{hafas}
+ ),
);
};
if ($@) {
@@ -576,11 +587,6 @@ sub startup {
$promise->reject( 'INSERT failed: ' . $@ );
return;
}
- $self->in_transit->update_data(
- uid => $uid,
- db => $db,
- data => { trip_id => $journey->id }
- );
my $polyline;
if ( $journey->polyline ) {
@@ -631,6 +637,19 @@ sub startup {
# mustn't be called during a transaction
if ( not $opt{in_transaction} ) {
$self->run_hook( $uid, 'checkin' );
+ if ( $opt{hafas} eq 'DB' and $journey->class <= 16 ) {
+ $self->add_wagonorder(
+ uid => $uid,
+ train_id => $journey->id,
+ is_departure => 1,
+ eva => $found->loc->eva,
+ datetime => $found->sched_dep,
+ train_type => $journey->type,
+ train_no => $journey->number
+ );
+ $self->add_stationinfo( $uid, 1, $journey->id,
+ $found->loc->eva );
+ }
}
$promise->resolve($journey);
@@ -749,6 +768,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;
@@ -770,7 +790,7 @@ sub startup {
return $promise->resolve( 0, 'race condition' );
}
- if ( $train_id =~ m{[|]} ) {
+ if ( $user->{is_hafas} ) {
return $self->_checkout_hafas_p(%opt);
}
@@ -893,7 +913,6 @@ sub startup {
uid => $uid,
db => $db,
train => $train,
- route => [ $self->iris->route_diff($train) ]
);
$has_arrived
@@ -992,6 +1011,17 @@ sub startup {
if ( not $opt{in_transaction} ) {
$self->run_hook( $uid, 'update' );
$self->add_route_timestamps( $uid, $train, 0, 1 );
+ $self->add_wagonorder(
+ uid => $uid,
+ train_id => $train->train_id,
+ is_arrival => 1,
+ eva => $new_checkout_station_id,
+ datetime => $train->sched_departure,
+ train_type => $train->type,
+ train_no => $train->train_no
+ );
+ $self->add_stationinfo( $uid, 0, $train->train_id,
+ $dep_eva, $new_checkout_station_id );
}
$promise->resolve( 1, undef );
return;
@@ -1065,7 +1095,7 @@ sub startup {
last;
}
}
- if ( not $found ) {
+ if ( not $found and not $force ) {
return $promise->resolve( 1, 'station not found in route' );
}
@@ -1218,6 +1248,106 @@ sub startup {
}
);
+ $self->helper(
+ 'add_wagonorder' => sub {
+ my ( $self, %opt ) = @_;
+
+ my $uid = $opt{uid};
+ my $train_id = $opt{train_id};
+ my $train_type = $opt{train_type};
+ my $train_no = $opt{train_no};
+ my $eva = $opt{eva};
+ my $datetime = $opt{datetime};
+
+ $uid //= $self->current_user->{id};
+
+ my $db = $self->pg->db;
+
+ if ( $datetime and $train_no ) {
+ $self->dbdb->has_wagonorder_p(%opt)->then(
+ sub {
+ return $self->dbdb->get_wagonorder_p(%opt);
+ }
+ )->then(
+ sub {
+ my ($wagonorder) = @_;
+
+ my $data = {};
+ my $user_data = {};
+
+ if ( $opt{is_departure}
+ and not exists $wagonorder->{error} )
+ {
+ $data->{wagonorder_dep} = $wagonorder;
+ $user_data->{wagongroups} = [];
+ for my $group ( @{ $wagonorder->{groups} // [] } ) {
+ my @wagons;
+ for my $wagon ( @{ $group->{vehicles} // [] } )
+ {
+ push(
+ @wagons,
+ {
+ id => $wagon->{vehicleID},
+ number => $wagon
+ ->{wagonIdentificationNumber},
+ type =>
+ $wagon->{type}{constructionType},
+ }
+ );
+ }
+ push(
+ @{ $user_data->{wagongroups} },
+ {
+ name => $group->{name},
+ to => $group->{transport}{destination}
+ {name},
+ type => $group->{transport}{category},
+ no => $group->{transport}{number},
+ wagons => [@wagons],
+ }
+ );
+ if ( $group->{name}
+ and $group->{name} eq 'ICE0304' )
+ {
+ $data->{wagonorder_pride} = 1;
+ }
+ }
+ $self->in_transit->update_data(
+ uid => $uid,
+ db => $db,
+ data => $data,
+ train_id => $train_id,
+ );
+ $self->in_transit->update_user_data(
+ uid => $uid,
+ db => $db,
+ user_data => $user_data,
+ train_id => $train_id,
+ );
+ }
+ elsif ( $opt{is_arrival}
+ and not exists $wagonorder->{error} )
+ {
+ $data->{wagonorder_arr} = $wagonorder;
+ $self->in_transit->update_data(
+ uid => $uid,
+ db => $db,
+ data => $data,
+ train_id => $train_id,
+ );
+ }
+ return;
+ }
+ )->catch(
+ sub {
+ # no wagonorder? no problem.
+ return;
+ }
+ )->wait;
+ }
+ }
+ );
+
# This helper is only ever called from an IRIS context.
# HAFAS already has all relevant information.
$self->helper(
@@ -1241,19 +1371,33 @@ sub startup {
return;
}
- my $route = $in_transit->{route};
+ my $route = $in_transit->{route};
+ my $train_id = $train->train_id;
- $self->hafas->get_tripid_p( train => $train )->then(
+ my $tripid_promise;
+
+ if ( $in_transit->{data}{trip_id} ) {
+ $tripid_promise
+ = Mojo::Promise->resolve( $in_transit->{data}{trip_id} );
+ }
+ else {
+ $tripid_promise = $self->hafas->get_tripid_p( train => $train );
+ }
+
+ $tripid_promise->then(
sub {
my ($trip_id) = @_;
- $self->in_transit->update_data(
- uid => $uid,
- db => $db,
- data => { trip_id => $trip_id }
- );
+ if ( not $in_transit->{extra_data}{trip_id} ) {
+ $self->in_transit->update_data(
+ uid => $uid,
+ db => $db,
+ data => { trip_id => $trip_id },
+ train_id => $train_id,
+ );
+ }
- return $self->hafas->get_route_timestamps_p(
+ return $self->hafas->get_route_p(
train => $train,
trip_id => $trip_id,
with_polyline => (
@@ -1264,42 +1408,63 @@ sub startup {
}
)->then(
sub {
- my ( $route_data, $journey, $polyline ) = @_;
+ my ( $new_route, $journey, $polyline ) = @_;
+ my $db_route;
- for my $station ( @{$route} ) {
- if ( $station->[0]
- =~ m{^Betriebsstelle nicht bekannt (\d+)$} )
- {
- my $eva = $1;
- if ( $route_data->{$eva} ) {
- $station->[0] = $route_data->{$eva}{name};
- $station->[1] = $route_data->{$eva}{eva};
- }
- }
- if ( my $sd = $route_data->{ $station->[0] } ) {
- $station->[1] = $sd->{eva};
- if ( $station->[2]{isAdditional} ) {
- $sd->{isAdditional} = 1;
- }
- if ( $station->[2]{isCancelled} ) {
- $sd->{isCancelled} = 1;
- }
+ for my $stop ( $journey->route ) {
+ $self->stations->add_or_update(
+ stop => $stop,
+ db => $db,
+ iris => 1,
+ );
+ }
- # keep rt_dep / rt_arr if they are no longer present
- my %old;
- for my $k (qw(rt_arr rt_dep arr_delay dep_delay)) {
- $old{$k} = $station->[2]{$k};
- }
- $station->[2] = $sd;
- if ( not $station->[2]{rt_arr} ) {
- $station->[2]{rt_arr} = $old{rt_arr};
- $station->[2]{arr_delay} = $old{arr_delay};
+ for my $i ( 0 .. $#{$new_route} ) {
+ my $old_name = $route->[$i][0];
+ my $old_eva = $route->[$i][1];
+ my $old_entry = $route->[$i][2];
+ my $new_name = $new_route->[$i]->{name};
+ my $new_eva = $new_route->[$i]->{eva};
+ my $new_entry = $new_route->[$i];
+
+ if ( defined $old_name and $old_name eq $new_name ) {
+ if ( $old_entry->{rt_arr}
+ and not $new_entry->{rt_arr} )
+ {
+ $new_entry->{rt_arr} = $old_entry->{rt_arr};
+ $new_entry->{arr_delay}
+ = $old_entry->{arr_delay};
}
- if ( not $station->[2]{rt_dep} ) {
- $station->[2]{rt_dep} = $old{rt_dep};
- $station->[2]{dep_delay} = $old{dep_delay};
+ if ( $old_entry->{rt_dep}
+ and not $new_entry->{rt_dep} )
+ {
+ $new_entry->{rt_dep} = $old_entry->{rt_dep};
+ $new_entry->{dep_delay}
+ = $old_entry->{dep_delay};
}
}
+
+ push(
+ @{$db_route},
+ [
+ $new_name,
+ $new_eva,
+ {
+ sched_arr => $new_entry->{sched_arr},
+ rt_arr => $new_entry->{rt_arr},
+ arr_delay => $new_entry->{arr_delay},
+ sched_dep => $new_entry->{sched_dep},
+ rt_dep => $new_entry->{rt_dep},
+ dep_delay => $new_entry->{dep_delay},
+ tz_offset => $new_entry->{tz_offset},
+ isAdditional => $new_entry->{isAdditional},
+ isCancelled => $new_entry->{isCancelled},
+ load => $new_entry->{load},
+ lat => $new_entry->{lat},
+ lon => $new_entry->{lon},
+ }
+ ]
+ );
}
my @messages;
@@ -1318,7 +1483,7 @@ sub startup {
$self->in_transit->set_route_data(
uid => $uid,
db => $db,
- route => $route,
+ route => $db_route,
delay_messages => [
map { [ $_->[0]->epoch, $_->[1] ] }
$train->delay_messages
@@ -1328,6 +1493,7 @@ sub startup {
$train->qos_messages
],
him_messages => \@messages,
+ train_id => $train_id,
);
if ($polyline) {
@@ -1336,6 +1502,7 @@ sub startup {
db => $db,
polyline => $polyline,
old_id => $in_transit->{polyline_id},
+ train_id => $train_id,
);
}
@@ -1348,107 +1515,28 @@ sub startup {
return;
}
)->wait;
+ }
+ );
- if ( $train->sched_departure ) {
- $self->dbdb->has_wagonorder_p( $train->sched_departure,
- $train->train_no )->then(
- sub {
- my ($api) = @_;
- return $self->dbdb->get_wagonorder_p( $api,
- $train->sched_departure, $train->train_no );
- }
- )->then(
- sub {
- my ($wagonorder) = @_;
-
- my $data = {};
- my $user_data = {};
+ $self->helper(
+ 'add_stationinfo' => sub {
+ my ( $self, $uid, $is_departure, $train_id, $dep_eva, $arr_eva )
+ = @_;
- if ( $is_departure and not exists $wagonorder->{error} )
- {
- $data->{wagonorder_dep} = $wagonorder;
- $user_data->{wagongroups} = [];
- for my $group (
- @{
- $wagonorder->{data}{istformation}
- {allFahrzeuggruppe} // []
- }
- )
- {
- my @wagons;
- for
- my $wagon ( @{ $group->{allFahrzeug} // [] } )
- {
- push(
- @wagons,
- {
- id => $wagon->{fahrzeugnummer},
- number =>
- $wagon->{wagenordnungsnummer},
- type => $wagon->{fahrzeugtyp},
- }
- );
- }
- push(
- @{ $user_data->{wagongroups} },
- {
- name =>
- $group->{fahrzeuggruppebezeichnung},
- from =>
- $group->{startbetriebsstellename},
- to => $group->{zielbetriebsstellename},
- no => $group->{verkehrlichezugnummer},
- wagons => [@wagons],
- }
- );
- if ( $group->{fahrzeuggruppebezeichnung}
- and $group->{fahrzeuggruppebezeichnung} eq
- 'ICE0304' )
- {
- $data->{wagonorder_pride} = 1;
- }
- }
- $self->in_transit->update_data(
- uid => $uid,
- db => $db,
- data => $data
- );
- $self->in_transit->update_user_data(
- uid => $uid,
- db => $db,
- user_data => $user_data
- );
- }
- elsif ( not $is_departure
- and not exists $wagonorder->{error} )
- {
- $data->{wagonorder_arr} = $wagonorder;
- $self->in_transit->update_data(
- uid => $uid,
- db => $db,
- data => $data
- );
- }
- return;
- }
- )->catch(
- sub {
- # no wagonorder? no problem.
- return;
- }
- )->wait;
- }
+ $uid //= $self->current_user->{id};
+ my $db = $self->pg->db;
if ($is_departure) {
- $self->dbdb->get_stationinfo_p( $in_transit->{dep_eva} )->then(
+ $self->dbdb->get_stationinfo_p($dep_eva)->then(
sub {
my ($station_info) = @_;
my $data = { stationinfo_dep => $station_info };
$self->in_transit->update_data(
- uid => $uid,
- db => $db,
- data => $data
+ uid => $uid,
+ db => $db,
+ data => $data,
+ train_id => $train_id,
);
return;
}
@@ -1460,16 +1548,17 @@ sub startup {
)->wait;
}
- if ( $in_transit->{arr_eva} and not $is_departure ) {
- $self->dbdb->get_stationinfo_p( $in_transit->{arr_eva} )->then(
+ if ( $arr_eva and not $is_departure ) {
+ $self->dbdb->get_stationinfo_p($arr_eva)->then(
sub {
my ($station_info) = @_;
my $data = { stationinfo_arr => $station_info };
$self->in_transit->update_data(
- uid => $uid,
- db => $db,
- data => $data
+ uid => $uid,
+ db => $db,
+ data => $data,
+ train_id => $train_id,
);
return;
}
@@ -1493,6 +1582,7 @@ sub startup {
$ret =~ s{[{]tt[}]}{$opt{tt}}g;
$ret =~ s{[{]tn[}]}{$opt{tn}}g;
$ret =~ s{[{]id[}]}{$opt{id}}g;
+ $ret =~ s{[{]hafas[}]}{$opt{hafas}}g;
return $ret;
}
);
@@ -1527,10 +1617,10 @@ sub startup {
from_json => $wagonorder );
};
if ( $wr
- and $wr->sections
+ and $wr->sectors
and defined $wr->direction )
{
- my $section_0 = ( $wr->sections )[0];
+ my $section_0 = ( $wr->sectors )[0];
my $direction = $wr->direction;
if ( $section_0->name eq 'A'
and $direction == 0 )
@@ -1673,7 +1763,7 @@ sub startup {
from_json => $in_transit->{data}{wagonorder_dep} );
};
if ( $wr
- and $wr->wagons
+ and $wr->carriages
and defined $wr->direction )
{
$ret->{wagonorder} = $wr;
@@ -1691,7 +1781,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},
)
)
{
@@ -1700,7 +1791,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},
)
)
{
@@ -1715,14 +1807,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};
@@ -1731,6 +1829,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,
@@ -1788,13 +1890,19 @@ 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},
uic => $status->{dep_eva},
longitude => $status->{dep_lon},
latitude => $status->{dep_lat},
+ platform => $status->{dep_platform},
scheduledTime => $status->{sched_departure}
? $status->{sched_departure}->epoch
: undef,
@@ -1808,6 +1916,7 @@ sub startup {
uic => $status->{arr_eva},
longitude => $status->{arr_lon},
latitude => $status->{arr_lat},
+ platform => $status->{arr_platform},
scheduledTime => $status->{sched_arrival}
? $status->{sched_arrival}->epoch
: undef,
@@ -1903,224 +2012,76 @@ sub startup {
return $promise->resolve;
}
- if ( $traewelling->{category}
- !~ m{^ (?: national .* | regional .* | suburban ) $ }x )
- {
-
- my $db = $self->pg->db;
- my $tx = $db->begin;
-
- $self->checkin_p(
- station => $traewelling->{dep_eva},
- train_id => $traewelling->{trip_id},
- uid => $uid,
- in_transaction => 1,
- db => $db
- )->then(
- sub {
- $self->log->debug("... handled origin");
- return $self->checkout_p(
- station => $traewelling->{arr_eva},
- train_id => $traewelling->{trip_id},
- uid => $uid,
- in_transaction => 1,
- db => $db
- );
- }
- )->then(
- sub {
- my ( undef, $err ) = @_;
- if ($err) {
- $self->log->debug("... error: $err");
- return Mojo::Promise->reject($err);
- }
- $self->log->debug("... handled destination");
- if ( $traewelling->{message} ) {
- $self->in_transit->update_user_data(
- uid => $uid,
- db => $db,
- user_data =>
- { comment => $traewelling->{message} }
- );
- }
- $self->traewelling->log(
- uid => $uid,
- db => $db,
- message =>
-"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},
- db => $db
- );
-
- $tx->commit;
- $promise->resolve;
- return;
- }
- )->catch(
- sub {
- my ($err) = @_;
- $self->log->debug("... error: $err");
- $self->traewelling->log(
- uid => $uid,
- message =>
-"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: $err",
- status_id => $traewelling->{status_id},
- is_error => 1
- );
- $promise->resolve;
- return;
- }
- )->wait;
- return $promise;
- }
-
- $self->iris->get_departures_p(
- station => $traewelling->{dep_eva},
- lookbehind => 60,
- lookahead => 40
+ my $db = $self->pg->db;
+ my $tx = $db->begin;
+
+ $self->_checkin_hafas_p(
+ hafas => 'DB',
+ station => $traewelling->{dep_eva},
+ train_id => $traewelling->{trip_id},
+ uid => $uid,
+ in_transaction => 1,
+ db => $db
)->then(
sub {
- my ($dep) = @_;
- my ( $train_ref, $train_id );
-
- if ( $dep->{errstr} ) {
- $self->traewelling->log(
- uid => $uid,
- message =>
-"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: $dep->{errstr}",
- status_id => $traewelling->{status_id},
- is_error => 1,
- );
- $promise->resolve;
- return;
- }
-
- for my $train ( @{ $dep->{results} } ) {
- if ( $train->line ne $traewelling->{line} ) {
- next;
- }
- if ( not $train->sched_departure
- or $train->sched_departure->epoch
- != $traewelling->{dep_dt}->epoch )
- {
- next;
- }
- if (
- not
- List::Util::first { $_ eq $traewelling->{arr_name} }
- $train->route_post
- )
- {
- next;
- }
- $train_id = $train->train_id;
- $train_ref = $train;
- last;
- }
-
- if ( not $train_id ) {
- $self->log->debug(
- "... train $traewelling->{line} not found");
- $self->traewelling->log(
- uid => $uid,
- message =>
-"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: Zug nicht gefunden",
- status_id => $traewelling->{status_id},
- is_error => 1
- );
- return $promise->resolve;
- }
-
- $self->log->debug("... found train: $train_id");
-
- my $db = $self->pg->db;
- my $tx = $db->begin;
-
- $self->checkin_p(
- station => $traewelling->{dep_eva},
- train_id => $train_id,
+ $self->log->debug("... handled origin");
+ return $self->_checkout_hafas_p(
+ hafas => 'DB',
+ station => $traewelling->{arr_eva},
+ train_id => $traewelling->{trip_id},
uid => $uid,
in_transaction => 1,
db => $db
- )->then(
- sub {
- $self->log->debug("... handled origin");
- return $self->checkout_p(
- station => $traewelling->{arr_eva},
- train_id => 0,
- uid => $uid,
- in_transaction => 1,
- db => $db
- );
- }
- )->then(
- sub {
- my ( undef, $err ) = @_;
- if ($err) {
- $self->log->debug("... error: $err");
- return Mojo::Promise->reject($err);
- }
- $self->log->debug("... handled destination");
- if ( $traewelling->{message} ) {
- $self->in_transit->update_user_data(
- uid => $uid,
- db => $db,
- user_data =>
- { comment => $traewelling->{message} }
- );
- }
- $self->traewelling->log(
- uid => $uid,
- db => $db,
- message =>
+ );
+ }
+ )->then(
+ sub {
+ my ( undef, $err ) = @_;
+ if ($err) {
+ $self->log->debug("... error: $err");
+ return Mojo::Promise->reject($err);
+ }
+ $self->log->debug("... handled destination");
+ if ( $traewelling->{message} ) {
+ $self->in_transit->update_user_data(
+ uid => $uid,
+ db => $db,
+ user_data => { comment => $traewelling->{message} }
+ );
+ }
+ $self->traewelling->log(
+ uid => $uid,
+ db => $db,
+ message =>
"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},
- db => $db
- );
+ status_id => $traewelling->{status_id},
+ );
- $tx->commit;
- $promise->resolve;
- return;
- }
- )->catch(
- sub {
- my ($err) = @_;
- $self->log->debug("... error: $err");
- $self->traewelling->log(
- uid => $uid,
- message =>
-"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: $err",
- status_id => $traewelling->{status_id},
- is_error => 1
- );
- $promise->resolve;
- return;
- }
- )->wait;
+ $self->traewelling->set_latest_pull_status_id(
+ uid => $uid,
+ status_id => $traewelling->{status_id},
+ db => $db
+ );
+
+ $tx->commit;
+ $promise->resolve;
+ return;
}
)->catch(
sub {
- my ( $err, $dep ) = @_;
+ my ($err) = @_;
+ $self->log->debug("... error: $err");
$self->traewelling->log(
uid => $uid,
message =>
-"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: $dep->{errstr}",
+"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: $err",
status_id => $traewelling->{status_id},
- is_error => 1,
+ is_error => 1
);
$promise->resolve;
return;
}
)->wait;
-
return $promise;
}
);
@@ -2133,8 +2094,6 @@ sub startup {
my $route_type = $opt{route_type} // 'polybee';
my $include_manual = $opt{include_manual} ? 1 : 0;
- my $location = $self->app->coordinates_by_station;
-
my $with_polyline = $route_type eq 'beeline' ? 0 : 1;
if ( not @journeys ) {
@@ -2150,12 +2109,19 @@ sub startup {
my $first_departure = $journeys[-1]->{rt_departure};
my $last_departure = $journeys[0]->{rt_departure};
- my @stations = List::Util::uniq map { $_->{to_name} } @journeys;
- push( @stations,
- List::Util::uniq map { $_->{from_name} } @journeys );
- @stations = List::Util::uniq @stations;
- my @station_coordinates = map { [ $location->{$_}, $_ ] }
- grep { exists $location->{$_} } @stations;
+ my @stations = uniq_by { $_->{name} } map {
+ {
+ name => $_->{to_name},
+ latlon => $_->{to_latlon}
+ },
+ {
+ name => $_->{from_name},
+ latlon => $_->{from_latlon}
+ }
+ } @journeys;
+
+ my @station_coordinates
+ = map { [ $_->{latlon}, $_->{name} ] } @stations;
my @station_pairs;
my @polylines;
@@ -2183,6 +2149,31 @@ sub startup {
my $to_index
= first_index { $_->[2] and $_->[2] == $to_eva } @polyline;
+ # Work around inconsistencies caused by a multiple EVA IDs mapping to the same station name
+ if ( $from_index == -1 ) {
+ for my $entry ( @{ $journey->{route} // [] } ) {
+ if ( $entry->[0] eq $journey->{from_name} ) {
+ $from_eva = $entry->[1];
+ $from_index
+ = first_index { $_->[2] and $_->[2] == $from_eva }
+ @polyline;
+ last;
+ }
+ }
+ }
+
+ if ( $to_index == -1 ) {
+ for my $entry ( @{ $journey->{route} // [] } ) {
+ if ( $entry->[0] eq $journey->{to_name} ) {
+ $to_eva = $entry->[1];
+ $to_index
+ = first_index { $_->[2] and $_->[2] == $to_eva }
+ @polyline;
+ last;
+ }
+ }
+ }
+
if ( $from_index == -1
or $to_index == -1 )
{
@@ -2219,23 +2210,32 @@ sub startup {
for my $journey (@beeline_journeys) {
- my @route = map { $_->[0] } @{ $journey->{route} };
+ my @route = @{ $journey->{route} };
- my $from_index
- = first_index { $_ eq $journey->{from_name} } @route;
- my $to_index = first_index { $_ eq $journey->{to_name} } @route;
+ my $from_index = first_index {
+ ( $_->[1] and $_->[1] == $journey->{from_eva} )
+ or $_->[0] eq $journey->{from_name}
+ }
+ @route;
+ my $to_index = first_index {
+ ( $_->[1] and $_->[1] == $journey->{to_eva} )
+ or $_->[0] eq $journey->{to_name}
+ }
+ @route;
if ( $from_index == -1 ) {
my $rename = $self->app->renamed_station;
$from_index = first_index {
- ( $rename->{$_} // $_ ) eq $journey->{from_name}
+ ( $rename->{ $_->[0] } // $_->[0] ) eq
+ $journey->{from_name}
}
@route;
}
if ( $to_index == -1 ) {
my $rename = $self->app->renamed_station;
$to_index = first_index {
- ( $rename->{$_} // $_ ) eq $journey->{to_name}
+ ( $rename->{ $_->[0] } // $_->[0] ) eq
+ $journey->{to_name}
}
@route;
}
@@ -2269,7 +2269,7 @@ sub startup {
@route = @route[ $from_index .. $to_index ];
- my $key = join( '|', @route );
+ my $key = join( '|', map { $_->[0] } @route );
if ( $seen{$key} ) {
next;
@@ -2278,7 +2278,7 @@ sub startup {
$seen{$key} = 1;
# direction does not matter at the moment
- $seen{ join( '|', reverse @route ) } = 1;
+ $seen{ join( '|', reverse map { $_->[0] } @route ) } = 1;
my $prev_station = shift @route;
for my $station (@route) {
@@ -2287,14 +2287,17 @@ sub startup {
}
}
- @station_pairs = uniq_by { $_->[0] . '|' . $_->[1] } @station_pairs;
- @station_pairs = grep {
- exists $location->{ $_->[0] }
- and exists $location->{ $_->[1] }
- } @station_pairs;
@station_pairs
- = map { [ $location->{ $_->[0] }, $location->{ $_->[1] } ] }
+ = uniq_by { $_->[0][0] . '|' . $_->[1][0] } @station_pairs;
+ @station_pairs
+ = grep { defined $_->[0][2]{lat} and defined $_->[1][2]{lat} }
@station_pairs;
+ @station_pairs = map {
+ [
+ [ $_->[0][2]{lat}, $_->[0][2]{lon} ],
+ [ $_->[1][2]{lat}, $_->[1][2]{lon} ]
+ ]
+ } @station_pairs;
my $ret = {
skipped_journeys => \@skipped_journeys,
@@ -2364,7 +2367,8 @@ sub startup {
->to( 'profile#user_status', format => undef );
$r->get('/ajax/status/#name')->to('profile#status_card');
$r->get('/ajax/status/:name/:ts')->to('profile#status_card');
- $r->get('/p/:name')->to('profile#profile');
+ $r->get( '/p/:name' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'profile#profile', format => undef );
$r->get( '/p/:name/j/:id' => 'public_journey' )
->to('profile#journey_details');
$r->get('/.well-known/webfinger')->to('account#webfinger');
@@ -2410,13 +2414,14 @@ sub startup {
$authed_r->get('/account/hooks')->to('account#webhook');
$authed_r->get('/account/traewelling')->to('traewelling#settings');
$authed_r->get('/account/insight')->to('account#insight');
- $authed_r->get('/account/services')->to('account#services');
$authed_r->get('/ajax/status_card.html')->to('traveling#status_card');
- $authed_r->get('/cancelled')->to('traveling#cancelled');
+ $authed_r->get( '/cancelled' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'traveling#cancelled', format => undef );
$authed_r->get('/fgr')->to('passengerrights#list_candidates');
$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');
@@ -2438,7 +2443,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/services')->to('account#services');
+ $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 d13b2a7..a7d13a8 100644
--- a/lib/Travelynx/Command/database.pm
+++ b/lib/Travelynx/Command/database.pm
@@ -7,7 +7,9 @@ use Mojo::Base 'Mojolicious::Command';
use DateTime;
use File::Slurp qw(read_file);
+use List::Util qw();
use JSON;
+use Travel::Status::DE::HAFAS;
use Travel::Status::DE::IRIS::Stations;
has description => 'Initialize or upgrade database layout';
@@ -1918,7 +1920,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(
@@ -1946,6 +1948,747 @@ my @migrations = (
}
);
},
+
+ # v51 -> v52
+ # Explicitly encode backend type; preparation for multiple HAFAS backends
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ create table backends (
+ id smallserial not null primary key,
+ iris bool not null,
+ hafas bool not null,
+ efa bool not null,
+ ris bool not null,
+ name varchar(32) not null,
+ unique (iris, hafas, efa, ris, name)
+ );
+ insert into backends (id, iris, hafas, efa, ris, name) values (0, true, false, false, false, '');
+ insert into backends (id, iris, hafas, efa, ris, name) values (1, false, true, false, false, 'DB');
+ alter sequence backends_id_seq restart with 2;
+ alter table in_transit add column backend_id smallint references backends (id);
+ alter table journeys add column backend_id smallint references backends (id);
+ update in_transit set backend_id = 0 where train_id not like '%|%';
+ update journeys set backend_id = 0 where train_id not like '%|%';
+ update in_transit set backend_id = 1 where train_id like '%|%';
+ update journeys set backend_id = 1 where train_id like '%|%';
+ update journeys set backend_id = 1 where train_id = 'manual';
+ alter table in_transit alter column backend_id set not null;
+ alter table journeys alter column backend_id set not null;
+
+ drop view in_transit_str;
+ drop view journeys_str;
+ 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,
+ 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
+ left join stations as arr_station on checkout_station_id = arr_station.eva
+ left join backends as backend on 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,
+ 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
+ left join stations as arr_station on checkout_station_id = arr_station.eva
+ left join backends as backend on backend_id = backend.id
+ ;
+ update schema_version set version = 52;
+ }
+ );
+ },
+
+ # v52 -> v53
+ # Extend train_id to be compatible with more recent HAFAS versions
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ drop view in_transit_str;
+ drop view journeys_str;
+ drop view follows_in_transit;
+ alter table in_transit alter column train_id type varchar(384);
+ alter table journeys alter column train_id type varchar(384);
+ 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,
+ 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
+ left join stations as arr_station on checkout_station_id = arr_station.eva
+ left join backends as backend on 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,
+ 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
+ left join stations as arr_station on checkout_station_id = arr_station.eva
+ left join backends as backend on 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,
+ 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
+ left join stations as arr_station on checkout_station_id = arr_station.eva
+ order by checkin_time desc
+ ;
+ update schema_version set version = 53;
+ }
+ );
+ },
+
+ # v53 -> v54
+ # Retrofit lat/lon data onto routes logged before v2.7.8; ensure
+ # consistent name and eva entries as well.
+ sub {
+ my ($db) = @_;
+
+ say
+'Adding lat/lon to routes of journeys logged before v2.7.8 and improving consistency of name/eva data in very old route entries.';
+ say 'This may take a while ...';
+
+ my %legacy_to_new;
+ if ( -r 'share/old_station_names.json' ) {
+ %legacy_to_new = %{ JSON->new->utf8->decode(
+ scalar read_file('share/old_station_names.json')
+ )
+ };
+ }
+
+ my %latlon_by_eva;
+ my %latlon_by_name;
+ my $res = $db->select( 'stations', [ 'name', 'eva', 'lat', 'lon' ] );
+ while ( my $row = $res->hash ) {
+ $latlon_by_eva{ $row->{eva} } = $row;
+ $latlon_by_name{ $row->{name} } = $row;
+ }
+
+ my $total
+ = $db->select( 'journeys', 'count(*) as count' )->hash->{count};
+ my $count = 0;
+ my $total_no_eva = 0;
+ my $total_no_latlon = 0;
+
+ my $json = JSON->new;
+
+ $res = $db->select( 'journeys_str', [ 'route', 'journey_id' ] );
+ while ( my $row = $res->expand->hash ) {
+ my $no_eva = 0;
+ my $no_latlon = 0;
+ my $changed = 0;
+ my @route = @{ $row->{route} };
+ for my $stop (@route) {
+ my $name = $stop->[0];
+ my $eva = $stop->[1];
+
+ if ( not $eva and $stop->[2]{eva} ) {
+ $eva = $stop->[1] = 0 + $stop->[2]{eva};
+ }
+
+ if ( $stop->[2]{eva} and $eva and $eva == $stop->[2]{eva} ) {
+ delete $stop->[2]{eva};
+ }
+
+ if ( $stop->[2]{name} and $name eq $stop->[2]{name} ) {
+ delete $stop->[2]{name};
+ }
+
+ if ( not $eva ) {
+ if ( $latlon_by_name{$name} ) {
+ $eva = $stop->[1] = $latlon_by_name{$name}{eva};
+ $changed = 1;
+ }
+ elsif ( $legacy_to_new{$name}
+ and $latlon_by_name{ $legacy_to_new{$name} } )
+ {
+ $eva = $stop->[1]
+ = $latlon_by_name{ $legacy_to_new{$name} }{eva};
+ $stop->[2]{lat}
+ = $latlon_by_name{ $legacy_to_new{$name} }{lat};
+ $stop->[2]{lon}
+ = $latlon_by_name{ $legacy_to_new{$name} }{lon};
+ $changed = 1;
+ }
+ else {
+ $no_eva = 1;
+ }
+ }
+
+ if ( $stop->[2]{lat} and $stop->[2]{lon} ) {
+ next;
+ }
+
+ if ( $eva and $latlon_by_eva{$eva} ) {
+ $stop->[2]{lat} = $latlon_by_eva{$eva}{lat};
+ $stop->[2]{lon} = $latlon_by_eva{$eva}{lon};
+ $changed = 1;
+ }
+ elsif ( $latlon_by_name{$name} ) {
+ $stop->[2]{lat} = $latlon_by_name{$name}{lat};
+ $stop->[2]{lon} = $latlon_by_name{$name}{lon};
+ $changed = 1;
+ }
+ elsif ( $legacy_to_new{$name}
+ and $latlon_by_name{ $legacy_to_new{$name} } )
+ {
+ $stop->[2]{lat}
+ = $latlon_by_name{ $legacy_to_new{$name} }{lat};
+ $stop->[2]{lon}
+ = $latlon_by_name{ $legacy_to_new{$name} }{lon};
+ $changed = 1;
+ }
+ else {
+ $no_latlon = 1;
+ }
+ }
+ if ($no_eva) {
+ $total_no_eva += 1;
+ }
+ if ($no_latlon) {
+ $total_no_latlon += 1;
+ }
+ if ($changed) {
+ $db->update(
+ 'journeys',
+ {
+ route => $json->encode( \@route ),
+ },
+ { id => $row->{journey_id} }
+ );
+ }
+ if ( $count++ % 10000 == 0 ) {
+ printf( " %2.0f%% complete\n", $count * 100 / $total );
+ }
+ }
+ say ' done';
+ if ($total_no_eva) {
+ printf( " (%d of %d routes still lack some EVA IDs)\n",
+ $total_no_eva, $total );
+ }
+ if ($total_no_latlon) {
+ printf( " (%d of %d routes still lack some lat/lon data)\n",
+ $total_no_latlon, $total );
+ }
+
+ $db->query(
+ qq{
+ update schema_version set version = 54;
+ }
+ );
+ },
+
+ # 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 );
+ }
+ }
+
+ # Occasionally, IRIS checkins refer to stations that are not part of
+ # the Travel::Status::DE::IRIS database. Add those as HAFAS stops to
+ # satisfy the upcoming foreign key constraints.
+
+ my %iris_has_eva;
+ $res = $db->query(qq{select eva from stations where source = 0;});
+ while ( my $row = $res->hash ) {
+ $iris_has_eva{ $row->{eva} } = 1;
+ }
+
+ my %hafas_by_eva;
+ $res = $db->query(qq{select * from stations where source = 1;});
+ while ( my $row = $res->hash ) {
+ $hafas_by_eva{ $row->{eva} } = $row;
+ }
+
+ my @iris_ref_stations;
+ $res
+ = $db->query(
+qq{select distinct checkin_station_id from journeys where backend_id = 0;}
+ );
+ while ( my $row = $res->hash ) {
+ push( @iris_ref_stations, $row->{checkin_station_id} );
+ }
+ $res
+ = $db->query(
+qq{select distinct checkout_station_id from journeys where backend_id = 0;}
+ );
+ while ( my $row = $res->hash ) {
+ push( @iris_ref_stations, $row->{checkout_station_id} );
+ }
+ $res
+ = $db->query(
+qq{select distinct checkin_station_id from in_transit where backend_id = 0;}
+ );
+ while ( my $row = $res->hash ) {
+ push( @iris_ref_stations, $row->{checkin_station_id} );
+ }
+ $res
+ = $db->query(
+qq{select distinct checkout_station_id from in_transit where backend_id = 0;}
+ );
+ while ( my $row = $res->hash ) {
+ if ( $row->{checkout_station_id} ) {
+ push( @iris_ref_stations, $row->{checkout_station_id} );
+ }
+ }
+
+ @iris_ref_stations = List::Util::uniq @iris_ref_stations;
+
+ for my $station (@iris_ref_stations) {
+ if ( not $iris_has_eva{$station} ) {
+ $hafas_by_eva{$station}{source} = 0;
+ $hafas_by_eva{$station}{archived} = 1;
+ $db->insert( 'stations', $hafas_by_eva{$station} );
+ }
+ }
+
+ $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';
+ },
+
+ # v55 -> v56
+ # include backend data in dumpstops command
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ create view stations_str as
+ select stations.name as name,
+ eva, lat, lon,
+ backends.name as backend,
+ iris as is_iris,
+ hafas as is_hafas,
+ efa as is_efa,
+ ris as is_ris
+ from stations
+ left join backends
+ on source = backends.id;
+ update schema_version set version = 56;
+ }
+ );
+ },
+
+ # v56 -> v57
+ # Berlin Hbf used to be divided between "Berlin Hbf" (8011160) and "Berlin
+ # Hbf (tief)" (8098160). Since 2024, both are called "Berlin Hbf".
+ # As there are some places in the IRIS backend where station names are
+ # mapped to EVA IDs, this is not good. As of 2.8.21, travelynx deals with
+ # this IRIS edge case (and probably similar edge cases in Karlsruhe).
+ # Rebuild stats to ensure no bogus data is in there.
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ truncate journey_stats;
+ update schema_version set version = 57;
+ }
+ );
+ },
+
+ # v57 -> v58
+ # Add backend data to follows_in_transit
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ drop view follows_in_transit;
+ 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.ris as is_ris,
+ 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 = 58;
+ }
+ );
+ },
);
sub sync_stations {
@@ -1977,7 +2720,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 ) {
@@ -2136,6 +2879,36 @@ sub sync_stations {
}
}
+sub sync_backends {
+ my ($db) = @_;
+ for my $service ( Travel::Status::DE::HAFAS::get_services() ) {
+ my $present = $db->select(
+ 'backends',
+ 'count(*) as count',
+ {
+ hafas => 1,
+ name => $service->{shortname}
+ }
+ )->hash->{count};
+ if ( not $present ) {
+ $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;
@@ -2202,9 +2975,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 {
@@ -2223,6 +2996,17 @@ 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/dumpstops.pm b/lib/Travelynx/Command/dumpstops.pm
index e6740ec..4d20bbd 100644
--- a/lib/Travelynx/Command/dumpstops.pm
+++ b/lib/Travelynx/Command/dumpstops.pm
@@ -8,7 +8,7 @@ use Mojo::Base 'Mojolicious::Command';
use List::Util qw();
use Text::CSV;
-has description => 'Export HAFAS/IRIS stops to CSV';
+has description => 'Export known stops to CSV';
has usage => sub { shift->extract_usage };
@@ -24,12 +24,13 @@ sub run {
or die("open($filename): $!\n");
my $csv = Text::CSV->new( { eol => "\r\n" } );
- $csv->combine(qw(name eva lat lon source archived));
+ $csv->combine(qw(name eva lat lon backend is_iris is_hafas));
print $fh $csv->string;
my $iter = $self->app->stations->get_db_iterator;
while ( my $row = $iter->hash ) {
- $csv->combine( @{$row}{qw{name eva lat lon source archived}} );
+ $csv->combine(
+ @{$row}{qw{name eva lat lon backend is_iris is_hafas}} );
print $fh $csv->string;
}
close($fh);
diff --git a/lib/Travelynx/Command/influxdb.pm b/lib/Travelynx/Command/influxdb.pm
index f3fc3de..4b779a2 100644
--- a/lib/Travelynx/Command/influxdb.pm
+++ b/lib/Travelynx/Command/influxdb.pm
@@ -29,7 +29,7 @@ sub run {
my $active = $now->clone->subtract( months => 1 );
my @stats;
- my @stations;
+ my @backend_stats;
my @traewelling;
push(
@@ -85,50 +85,31 @@ sub run {
)
);
- push(
- @stations,
- query_to_influx(
- 'iris',
- $db->select(
- 'stations',
- 'count(*) as count',
- {
- source => 0,
- archived => 0
- }
- )->hash->{count}
- )
- );
- push(
- @stations,
- query_to_influx(
- 'hafas',
- $db->select(
- 'stations',
- 'count(*) as count',
- {
- source => 1,
- archived => 0
- }
- )->hash->{count}
- )
- );
- push(
- @stations,
- query_to_influx(
- 'archived',
- $db->select( 'stations', 'count(*) as count', { archived => 1 } )
- ->hash->{count}
- )
- );
- push(
- @stations,
- query_to_influx(
- 'meta',
- $db->select( 'related_stations', 'count(*) as count' )
- ->hash->{count}
- )
- );
+ my @backends = $self->app->stations->get_backends;
+
+ for my $backend (@backends) {
+ push(
+ @backend_stats,
+ [
+ $backend->{iris} ? 'IRIS' : $backend->{name},
+ $db->select(
+ 'stations',
+ 'count(*) as count',
+ {
+ source => $backend->{id},
+ archived => 0
+ }
+ )->hash->{count},
+ $db->select(
+ 'related_stations',
+ 'count(*) as count',
+ {
+ backend_id => $backend->{id},
+ }
+ )->hash->{count}
+ ]
+ );
+ }
push(
@traewelling,
@@ -167,10 +148,18 @@ sub run {
. $self->app->config->{influxdb}->{url}
. ' stats '
. join( ',', @stats ) );
- $self->app->log->debug( 'POST '
- . $self->app->config->{influxdb}->{url}
- . ' stations '
- . join( ',', @stations ) );
+ for my $backend_entry (@backend_stats) {
+ $self->app->log->debug(
+ 'POST '
+ . $self->app->config->{influxdb}->{url}
+ . ' stations,backend='
+ . $backend_entry->[0]
+ . sprintf(
+ ' count=%d,meta=%d',
+ $backend_entry->[1], $backend_entry->[2]
+ )
+ );
+ }
$self->app->log->debug( 'POST '
. $self->app->config->{influxdb}->{url}
. ' traewelling '
@@ -181,10 +170,16 @@ sub run {
$self->app->config->{influxdb}->{url},
'stats ' . join( ',', @stats )
)->wait;
- $self->app->ua->post_p(
- $self->app->config->{influxdb}->{url},
- 'stations ' . join( ',', @stations )
- )->wait;
+ my $buf = q{};
+ for my $backend_entry (@backend_stats) {
+ $buf
+ .= "\nstations,backend="
+ . $backend_entry->[0]
+ . sprintf( ' count=%d,meta=%d',
+ $backend_entry->[1], $backend_entry->[2] );
+ }
+ $self->app->ua->post_p( $self->app->config->{influxdb}->{url}, $buf )
+ ->wait;
$self->app->ua->post_p(
$self->app->config->{influxdb}->{url},
'traewelling ' . join( ',', @traewelling )
diff --git a/lib/Travelynx/Command/integritycheck.pm b/lib/Travelynx/Command/integritycheck.pm
index 4894c3d..be5fe71 100644
--- a/lib/Travelynx/Command/integritycheck.pm
+++ b/lib/Travelynx/Command/integritycheck.pm
@@ -9,58 +9,60 @@ use List::Util qw();
use Travel::Status::DE::IRIS::Stations;
sub run {
- my ($self) = @_;
- my $found = 0;
- my $db = $self->app->pg->db;
-
- my $res1 = $db->query(
- qq{
- select checkin_station_id
- from journeys
- left join stations on journeys.checkin_station_id = stations.eva
- where stations.eva is null;
- }
- );
-
- my $res2 = $db->query(
- qq{
- select checkout_station_id
- from journeys
- left join stations on journeys.checkout_station_id = stations.eva
- where stations.eva is null;
- }
- );
-
- my %notified;
- while ( my $row = $res1->hash ) {
- my $eva = $row->{checkin_station_id};
- if ( not $found ) {
- $found = 1;
- say
+ my ( $self, $mode ) = @_;
+ my $found = 0;
+ my $db = $self->app->pg->db;
+
+ if ( $mode eq 'all' or $mode eq 'unknown-evas' ) {
+
+ my %notified;
+ my $res1 = $db->query(
+ qq{
+ select checkin_station_id
+ from journeys
+ left join stations on journeys.checkin_station_id = stations.eva
+ where stations.eva is null;
+ }
+ );
+ my $res2 = $db->query(
+ qq{
+ select checkout_station_id
+ from journeys
+ left join stations on journeys.checkout_station_id = stations.eva
+ where stations.eva is null;
+ }
+ );
+
+ while ( my $row = $res1->hash ) {
+ my $eva = $row->{checkin_station_id};
+ if ( not $found ) {
+ $found = 1;
+ say
'Journeys in the travelynx database contain the following unknown EVA IDs.';
- say '------------8<----------';
- say 'Travel::Status::DE::IRIS v'
- . $Travel::Status::DE::IRIS::Stations::VERSION;
- }
- if ( not $notified{$eva} ) {
- say $eva;
- $notified{$eva} = 1;
+ say '------------8<----------';
+ say 'Travel::Status::DE::IRIS v'
+ . $Travel::Status::DE::IRIS::Stations::VERSION;
+ }
+ if ( not $notified{$eva} ) {
+ say $eva;
+ $notified{$eva} = 1;
+ }
}
- }
- while ( my $row = $res2->hash ) {
- my $eva = $row->{checkout_station_id};
- if ( not $found ) {
- $found = 1;
- say
+ while ( my $row = $res2->hash ) {
+ my $eva = $row->{checkout_station_id};
+ if ( not $found ) {
+ $found = 1;
+ say
'Journeys in the travelynx database contain the following unknown EVA IDs.';
- say '------------8<----------';
- say 'Travel::Status::DE::IRIS v'
- . $Travel::Status::DE::IRIS::Stations::VERSION;
- }
- if ( not $notified{$eva} ) {
- say $eva;
- $notified{$eva} = 1;
+ say '------------8<----------';
+ say 'Travel::Status::DE::IRIS v'
+ . $Travel::Status::DE::IRIS::Stations::VERSION;
+ }
+ if ( not $notified{$eva} ) {
+ say $eva;
+ $notified{$eva} = 1;
+ }
}
}
@@ -70,42 +72,101 @@ sub run {
$found = 0;
}
- my $rename = $self->app->renamed_station;
+ if ( $mode eq 'all' or $mode eq 'unknown-route-entries' ) {
- my $res = $db->select( 'journeys', [ 'route', 'edited' ] )->expand;
- while ( my $j = $res->hash ) {
- if ( $j->{edited} & 0x0010 ) {
- next;
- }
- my @stops = @{ $j->{route} // [] };
- for my $stop (@stops) {
- my $stop_name = $stop->[0];
- if ( $rename->{ $stop->[0] } ) {
- $stop->[0] = $rename->{ $stop->[0] };
+ my %notified;
+ my $rename = $self->app->renamed_station;
+ my $res = $db->select( 'journeys', [ 'route', 'edited' ] )->expand;
+
+ while ( my $j = $res->hash ) {
+ if ( $j->{edited} & 0x0010 ) {
+ next;
+ }
+ my @stops = @{ $j->{route} // [] };
+ for my $stop (@stops) {
+ my $stop_name = $stop->[0];
+ if ( $rename->{ $stop->[0] } ) {
+ $stop->[0] = $rename->{ $stop->[0] };
+ }
+ }
+ my @unknown
+ = $self->app->stations->grep_unknown( map { $_->[0] } @stops );
+ for my $stop_name (@unknown) {
+ if ( not $notified{$stop_name} ) {
+ if ( not $found ) {
+ say
+'Journeys in the travelynx database contain the following unknown route entries.';
+ say
+ 'Note that this check ignores manual route entries.';
+ say
+'All reports refer to routes obtained via HAFAS/IRIS.';
+ say '------------8<----------';
+ say 'Travel::Status::DE::IRIS v'
+ . $Travel::Status::DE::IRIS::Stations::VERSION;
+ $found = 1;
+ }
+ say $stop_name;
+ $notified{$stop_name} = 1;
+ }
}
}
- my @unknown
- = $self->app->stations->grep_unknown( map { $_->[0] } @stops );
- for my $stop_name (@unknown) {
- if ( not $notified{$stop_name} ) {
+ }
+
+ if ($found) {
+ say '------------8<----------';
+ say '';
+ $found = 0;
+ }
+
+ if ( $mode eq 'all' or $mode eq 'checkout-eva-vs-route-eva' ) {
+
+ my $res = $db->select(
+ 'journeys_str',
+ [ 'journey_id', 'sched_arr_ts', 'route', 'arr_name', 'arr_eva' ],
+ { backend_id => 0 }
+ )->expand;
+
+ journey: while ( my $j = $res->hash ) {
+ my $found_in_route;
+ my $found_arr;
+ for my $stop ( @{ $j->{route} // [] } ) {
+ if ( not $stop->[1] ) {
+ next journey;
+ }
+ if ( $stop->[1] == $j->{arr_eva} ) {
+ $found_in_route = 1;
+ last;
+ }
+ if ( $stop->[2]{sched_arr}
+ and $j->{sched_arr_ts}
+ and $stop->[2]{sched_arr} == int( $j->{sched_arr_ts} ) )
+ {
+ $found_arr = $stop;
+ }
+ }
+ if ( $found_arr and not $found_in_route ) {
if ( not $found ) {
+ say q{};
say
-'Journeys in the travelynx database contain the following unknown route entries.';
- say 'Note that this check ignores manual route entries.';
- say 'All reports refer to routes obtained via HAFAS/IRIS.';
+'The following journeys have route entries which do not agree with checkout EVA ID.';
+ say
+'checkout station ID (left) vs route entry with matching checkout time (right)';
say '------------8<----------';
- say 'Travel::Status::DE::IRIS v'
- . $Travel::Status::DE::IRIS::Stations::VERSION;
$found = 1;
}
- say $stop_name;
- $notified{$stop_name} = 1;
+ printf(
+ "%7d %d (%s) vs %d (%s)\n",
+ $j->{journey_id}, $j->{arr_eva}, $j->{arr_name},
+ $found_arr->[1], $found_arr->[0]
+ );
}
}
}
+
if ($found) {
say '------------8<----------';
say '';
+ $found = 0;
}
}
diff --git a/lib/Travelynx/Command/maintenance.pm b/lib/Travelynx/Command/maintenance.pm
index c9c7ed6..7baf762 100644
--- a/lib/Travelynx/Command/maintenance.pm
+++ b/lib/Travelynx/Command/maintenance.pm
@@ -153,22 +153,6 @@ sub run {
}
$tx->commit;
-
- # Computing stats may take a while, but we've got all time in the
- # world here. This means users won't have to wait when loading their
- # own journey log.
- say 'Generating missing stats ...';
- for
- my $user ( $db->select( 'users', ['id'], { status => 1 } )->hashes->each )
- {
- $tx = $db->begin;
- $self->app->journeys->generate_missing_stats( uid => $user->{id} );
- $self->app->journeys->get_stats(
- uid => $user->{id},
- year => $now->year
- );
- $tx->commit;
- }
}
1;
diff --git a/lib/Travelynx/Command/traewelling.pm b/lib/Travelynx/Command/traewelling.pm
index 4c47e84..ed40371 100644
--- a/lib/Travelynx/Command/traewelling.pm
+++ b/lib/Travelynx/Command/traewelling.pm
@@ -20,6 +20,12 @@ sub pull_sync {
my $request_count = 0;
for my $account_data ( $self->app->traewelling->get_pull_accounts ) {
+ if ( -e 'maintenance' ) {
+ $self->app->log->debug(
+ 'treawelling: "maintenance" file found, aborting');
+ return;
+ }
+
my $in_transit = $self->app->in_transit->get(
uid => $account_data->{user_id},
);
@@ -30,6 +36,13 @@ sub pull_sync {
next;
}
+ if ( not defined $account_data->{data}{user_name} ) {
+ $self->app->log->debug(
+"travelynx user $account_data->{user_id} has a Traewellig connection, but no username"
+ );
+ next;
+ }
+
# $account_data->{user_id} is the travelynx uid
# $account_data->{user_name} is the Träwelling username
$request_count += 1;
@@ -39,7 +52,7 @@ sub pull_sync {
# In 'work', the event loop is not running,
# so there's no need to multiply by $request_count at the moment
- Mojo::Promise->timer(1)->then(
+ Mojo::Promise->timer(1.5)->then(
sub {
return $self->app->traewelling_api->get_status_p(
username => $account_data->{data}{user_name},
@@ -77,6 +90,13 @@ sub push_sync {
my %push_result;
for my $candidate ( $self->app->traewelling->get_pushable_accounts ) {
+
+ if ( -e 'maintenance' ) {
+ $self->app->log->debug(
+ 'treawelling: "maintenance" file found, aborting');
+ return;
+ }
+
$self->app->log->debug(
"Pushing to Traewelling for UID $candidate->{uid}");
my $trip_id = $candidate->{journey_data}{trip_id};
@@ -121,6 +141,12 @@ sub run {
my $push_result;
my $pull_result;
+ if ( -e 'maintenance' ) {
+ $self->app->log->debug(
+ 'treawelling: "maintenance" file found, aborting');
+ return;
+ }
+
if ( not $direction or $direction eq 'push' ) {
$push_result = $self->push_sync;
}
@@ -133,6 +159,12 @@ sub run {
my $trwl_pull_finished_at = DateTime->now( time_zone => 'Europe/Berlin' );
+ if ( -e 'maintenance' ) {
+ $self->app->log->debug(
+ 'treawelling: "maintenance" file found, aborting');
+ return;
+ }
+
my $trwl_push_duration = $trwl_push_finished_at->epoch - $started_at->epoch;
my $trwl_pull_duration
= $trwl_pull_finished_at->epoch - $trwl_push_finished_at->epoch;
diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm
index 10b1b69..98f478a 100644
--- a/lib/Travelynx/Command/work.pm
+++ b/lib/Travelynx/Command/work.pm
@@ -21,6 +21,11 @@ sub run {
my $checkin_deadline = $now->clone->subtract( hours => 48 );
my $json = JSON->new;
+ if ( -e 'maintenance' ) {
+ $self->app->log->debug('work: "maintenance" file found, aborting');
+ return;
+ }
+
my $num_incomplete = $self->app->in_transit->delete_incomplete_checkins(
earlier_than => $checkin_deadline );
@@ -32,83 +37,141 @@ sub run {
for my $entry ( $self->app->in_transit->get_all_active ) {
+ if ( -e 'maintenance' ) {
+ $self->app->log->debug('work: "maintenance" file found, aborting');
+ return;
+ }
+
my $uid = $entry->{user_id};
my $dep = $entry->{dep_eva};
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(
- sub {
- my ($journey) = @_;
+ eval {
- my $found_dep;
- my $found_arr;
- for my $stop ( $journey->route ) {
- if ( $stop->loc->eva == $dep ) {
- $found_dep = $stop;
+ $self->app->hafas->get_journey_p(
+ trip_id => $train_id,
+ service => $entry->{backend_name}
+ )->then(
+ sub {
+ my ($journey) = @_;
+
+ my $found_dep;
+ my $found_arr;
+ for my $stop ( $journey->route ) {
+ if ( $stop->loc->eva == $dep ) {
+ $found_dep = $stop;
+ }
+ if ( $arr and $stop->loc->eva == $arr ) {
+ $found_arr = $stop;
+ last;
+ }
}
- if ( $arr and $stop->loc->eva == $arr ) {
- $found_arr = $stop;
- last;
+ if ( not $found_dep ) {
+ $self->app->log->debug(
+ "Did not find $dep within journey $train_id");
+ return;
}
- }
- 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_hafas(
- uid => $uid,
- journey => $journey,
- stop => $found_dep,
- dep_eva => $dep,
- arr_eva => $arr
- );
- }
+ if ( $found_dep->rt_dep ) {
+ $self->app->in_transit->update_departure_hafas(
+ uid => $uid,
+ journey => $journey,
+ stop => $found_dep,
+ dep_eva => $dep,
+ arr_eva => $arr
+ );
+ if ( $entry->{backend_id} <= 1
+ and $journey->class <= 16
+ and $found_dep->rt_dep->epoch > $now->epoch )
+ {
+ $self->app->add_wagonorder(
+ uid => $uid,
+ train_id => $journey->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,
+ $journey->id, $found_dep->loc->eva );
+ }
+ }
- if ( $found_arr and $found_arr->{rt_arr} ) {
- $self->app->in_transit->update_arrival_hafas(
- uid => $uid,
- journey => $journey,
- stop => $found_arr,
- dep_eva => $dep,
- arr_eva => $arr
- );
- }
- }
- )->catch(
- sub {
- my ($err) = @_;
- if ( $err =~ m{svcResL\[0\][.]err is (?:FAIL|PARAMETER)$} )
- {
- # HAFAS do be weird. These are not actionable.
- $self->app->log->debug("work($uid)/journey: $err");
+ if ( $found_arr and $found_arr->rt_arr ) {
+ $self->app->in_transit->update_arrival_hafas(
+ uid => $uid,
+ journey => $journey,
+ stop => $found_arr,
+ dep_eva => $dep,
+ arr_eva => $arr
+ );
+ if ( $entry->{backend_id} <= 1
+ and $journey->class <= 16
+ and $found_arr->rt_arr->epoch - $now->epoch
+ < 600 )
+ {
+ $self->app->add_wagonorder(
+ uid => $uid,
+ train_id => $journey->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,
+ $journey->id, $found_dep->loc->eva,
+ $found_arr->loc->eva );
+ }
+ }
}
- else {
- $self->app->log->error("work($uid)/journey: $err");
+ )->catch(
+ sub {
+ my ($err) = @_;
+ if ( $err
+ =~ m{svcResL\[0\][.]err is (?:FAIL|PARAMETER)$} )
+ {
+ # HAFAS do be weird. These are not actionable.
+ $self->app->log->debug(
+"work($uid) @ HAFAS $entry->{backend_name}: journey: $err"
+ );
+ }
+ else {
+ $self->app->log->error(
+"work($uid) @ HAFAS $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 ( $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) @ HAFAS $entry->{backend_name}: $@");
}
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
@@ -171,12 +234,23 @@ sub run {
}
else {
$self->app->add_route_timestamps( $uid, $train, 1 );
+ $self->app->add_wagonorder(
+ uid => $uid,
+ train_id => $train->train_id,
+ is_departure => 1,
+ eva => $dep,
+ datetime => $train->sched_departure,
+ train_type => $train->type,
+ train_no => $train->train_no
+ );
+ $self->app->add_stationinfo( $uid, 1, $train->train_id,
+ $dep, $arr );
}
}
};
if ($@) {
$errors += 1;
- $self->app->log->error("work($uid)/departure: $@");
+ $self->app->log->error("work($uid) @ IRIS: departure: $@");
}
eval {
@@ -243,6 +317,17 @@ sub run {
and $now->epoch > $entry->{real_arr_ts}
) ? 1 : 0
);
+ $self->app->add_wagonorder(
+ uid => $uid,
+ train_id => $train->train_id,
+ is_arrival => 1,
+ eva => $arr,
+ datetime => $train->sched_departure,
+ train_type => $train->type,
+ train_no => $train->train_no
+ );
+ $self->app->add_stationinfo( $uid, 0, $train->train_id,
+ $dep, $arr );
}
}
elsif ( $entry->{real_arr_ts} ) {
@@ -255,14 +340,15 @@ sub run {
)->catch(
sub {
my ($error) = @_;
- $self->app->log->error("work($uid)/arrival: $error");
+ $self->app->log->error(
+ "work($uid) @ IRIS: arrival: $error");
$errors += 1;
}
)->wait;
}
};
if ($@) {
- $self->app->log->error("work($uid)/arrival: $@");
+ $self->app->log->error("work($uid) @ IRIS: arrival: $@");
$errors += 1;
}
@@ -290,6 +376,15 @@ sub run {
if ( not $self->app->config->{traewelling}->{separate_worker} ) {
$self->app->start('traewelling');
}
+
+ # add_wagonorder and add_stationinfo assume a permanently running IOLoop
+ # and do not allow Mojolicious commands to wait until they have completed.
+ # Hence, some add_wagonorder and add_stationinfo calls made here may not
+ # complete before the work command exits, and thus have no effect.
+ #
+ # This is not ideal and will need fixing at some point. Until then, here
+ # is the pragmatic solution for 99% of the associated issues.
+ Mojo::Promise->timer(5)->wait;
}
1;
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm
index f1dc43e..453664c 100644
--- a/lib/Travelynx/Controller/Account.pm
+++ b/lib/Travelynx/Controller/Account.pm
@@ -6,6 +6,7 @@ package Travelynx::Controller::Account;
use Mojo::Base 'Mojolicious::Controller';
use JSON;
+use Math::Polygon;
use Mojo::Util qw(xml_escape);
use Text::Markdown;
use UUID::Tiny qw(:std);
@@ -831,29 +832,6 @@ sub insight {
}
-sub services {
- my ($self) = @_;
- my $user = $self->current_user;
-
- if ( $self->param('action') and $self->param('action') eq 'save' ) {
- my $sb = $self->param('stationboard');
- my $value = 0;
- if ( $sb =~ m{ ^ \d+ $ }x and $sb >= 0 and $sb <= 4 ) {
- $value = int($sb);
- }
- $self->users->use_external_services(
- uid => $user->{id},
- set => $value
- );
- $self->flash( success => 'external' );
- $self->redirect_to('account');
- }
-
- $self->param( stationboard =>
- $self->users->use_external_services( uid => $user->{id} ) );
- $self->render('use_external_links');
-}
-
sub webhook {
my ($self) = @_;
@@ -1022,6 +1000,156 @@ sub password_form {
$self->render('change_password');
}
+sub lonlat_in_polygon {
+ my ( $self, $polygon, $lonlat ) = @_;
+
+ my $circle = shift( @{$polygon} );
+ my @holes = @{$polygon};
+
+ my $circle_poly = Math::Polygon->new( @{$circle} );
+ if ( $circle_poly->contains($lonlat) ) {
+ for my $hole (@holes) {
+ my $hole_poly = Math::Polygon->new( @{$hole} );
+ if ( $hole_poly->contains($lonlat) ) {
+ return;
+ }
+ }
+ return 1;
+ }
+ return;
+}
+
+sub backend_form {
+ my ($self) = @_;
+ my $user = $self->current_user;
+
+ my @backends = $self->stations->get_backends;
+ my @suggested_backends;
+
+ my %place_map = (
+ AT => 'Österreich',
+ CH => 'Schweiz',
+ 'CH-BE' => 'Kanton Bern',
+ 'CH-GE' => 'Kanton Genf',
+ 'CH-LU' => 'Kanton Luzern',
+ 'CH-ZH' => 'Kanton Zürich',
+ DE => 'Deutschland',
+ 'DE-BB' => 'Brandenburg',
+ 'DE-BW' => 'Baden-Württemberg',
+ 'DE-BE' => 'Berlin',
+ 'DE-BY' => 'Bayern',
+ 'DE-HB' => 'Bremen',
+ 'DE-HE' => 'Hessen',
+ 'DE-MV' => 'Mecklenburg-Vorpommern',
+ 'DE-NI' => 'Niedersachsen',
+ 'DE-NW' => 'Nordrhein-Westfalen',
+ 'DE-RP' => 'Rheinland-Pfalz',
+ 'DE-SH' => 'Schleswig-Holstein',
+ 'DE-ST' => 'Sachsen-Anhalt',
+ 'DE-TH' => 'Thüringen',
+ DK => 'Dänemark',
+ 'GB-NIR' => 'Nordirland',
+ LI => 'Liechtenstein',
+ LU => 'Luxembourg',
+ IE => 'Irland',
+ 'US-CA' => 'California',
+ 'US-TX' => 'Texas',
+ );
+
+ my ( $user_lat, $user_lon )
+ = $self->journeys->get_latest_checkout_latlon( uid => $user->{id} );
+
+ for my $backend (@backends) {
+ my $type = 'UNKNOWN';
+ if ( $backend->{iris} ) {
+ $type = 'IRIS-TTS';
+ $backend->{name} = 'IRIS';
+ $backend->{longname} = 'Deutsche Bahn (IRIS-TTS)';
+ $backend->{homepage} = 'https://www.bahn.de';
+ }
+ elsif ( $backend->{hafas} ) {
+ if ( my $s = $self->hafas->get_service( $backend->{name} ) ) {
+ $type = 'HAFAS';
+ $backend->{longname} = $s->{name};
+ $backend->{homepage} = $s->{homepage};
+ $backend->{regions} = [ map { $place_map{$_} // $_ }
+ @{ $s->{coverage}{regions} // [] } ];
+ $backend->{has_area} = $s->{coverage}{area} ? 1 : 0;
+
+ if (
+ $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'Polygon'
+ and $self->lonlat_in_polygon(
+ $s->{coverage}{area}{coordinates},
+ [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ }
+ elsif ( $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'MultiPolygon' )
+ {
+ for my $s_poly (
+ @{ $s->{coverage}{area}{coordinates} // [] } )
+ {
+ if (
+ $self->lonlat_in_polygon(
+ $s_poly, [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ last;
+ }
+ }
+ }
+ }
+ else {
+ $type = undef;
+ }
+ }
+ $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;
+
+ unshift( @backends, $iris );
+
+ $self->render(
+ 'select_backend',
+ suggestions => \@suggested_backends,
+ 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/Passengerrights.pm b/lib/Travelynx/Controller/Passengerrights.pm
index d80f1ae..5759d2e 100644
--- a/lib/Travelynx/Controller/Passengerrights.pm
+++ b/lib/Travelynx/Controller/Passengerrights.pm
@@ -121,6 +121,8 @@ sub list_candidates {
}
}
+ my @abo_journeys
+ = grep { $_->{delay} >= 20 and $_->{delay} < 60 } @journeys;
@journeys = grep { $_->{delay} >= 60 or $_->{connection_missed} } @journeys;
my @cancelled = $self->journeys->get(
@@ -154,8 +156,9 @@ sub list_candidates {
$self->respond_to(
json => { json => [@journeys] },
any => {
- template => 'passengerrights',
- journeys => [@journeys]
+ template => 'passengerrights',
+ journeys => [@journeys],
+ abo_journeys => [@abo_journeys]
}
);
}
diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm
index fc2d38c..a5f394f 100755
--- a/lib/Travelynx/Controller/Profile.pm
+++ b/lib/Travelynx/Controller/Profile.pm
@@ -152,34 +152,45 @@ sub profile {
@journeys = $self->journeys->get(%opt);
}
- $self->render(
- 'profile',
- title => "travelynx: $name",
- name => $name,
- uid => $user->{id},
- privacy => $user,
- bio => $profile->{bio}{html},
- metadata => $profile->{metadata},
- is_self => $is_self,
- following => ( $relation and $relation eq 'follows' ) ? 1 : 0,
- follow_requested => ( $relation and $relation eq 'requests_follow' )
- ? 1
- : 0,
- can_follow => ( $my_user and $user->{accept_follows} and not $relation )
- ? 1
- : 0,
- can_request_follow =>
- ( $my_user and $user->{accept_follow_requests} and not $relation )
- ? 1
- : 0,
- follows_me => ( $inverse_relation and $inverse_relation eq 'follows' )
- ? 1
- : 0,
- follow_reqs_me =>
- ( $inverse_relation and $inverse_relation eq 'requests_follow' ) ? 1
- : 0,
- journey => $status,
- journeys => [@journeys],
+ $self->respond_to(
+ json => {
+ json => {
+ name => $name,
+ uid => $user->{id},
+ bio => $profile->{bio}{html},
+ metadata => $profile->{metadata},
+ }
+ },
+ any => {
+ template => 'profile',
+ title => "travelynx: $name",
+ name => $name,
+ uid => $user->{id},
+ privacy => $user,
+ bio => $profile->{bio}{html},
+ metadata => $profile->{metadata},
+ is_self => $is_self,
+ following => ( $relation and $relation eq 'follows' ) ? 1 : 0,
+ follow_requested => ( $relation and $relation eq 'requests_follow' )
+ ? 1
+ : 0,
+ can_follow =>
+ ( $my_user and $user->{accept_follows} and not $relation ) ? 1
+ : 0,
+ can_request_follow => (
+ $my_user and $user->{accept_follow_requests} and not $relation
+ ) ? 1
+ : 0,
+ follows_me =>
+ ( $inverse_relation and $inverse_relation eq 'follows' ) ? 1
+ : 0,
+ follow_reqs_me => (
+ $inverse_relation and $inverse_relation eq 'requests_follow'
+ ) ? 1
+ : 0,
+ journey => $status,
+ journeys => [@journeys],
+ }
);
}
diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm
index 89385e1..3151d42 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,70 @@ 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 ) {
- $self->render( json => { error => 'Invalid lon/lat received' } );
+ $self->render(
+ json => { error => "Invalid lon/lat (${lon}/${lat}) received" } );
+ return;
+ }
+
+ if ( $backend_id !~ m{ ^ \d+ $ }x ) {
+ $self->render(
+ json => { error => "Invalid backend (${backend_id}) received" } );
+ return;
+ }
+
+ 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;
}
- $self->render_later;
my @iris = map {
{
@@ -588,48 +588,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 => 1
- }
- } $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 +648,7 @@ sub travel_action {
$promise->then(
sub {
return $self->checkin_p(
+ hafas => $params->{hafas},
station => $params->{station},
train_id => $params->{train}
);
@@ -713,8 +678,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=1';
+ if ( $status->{is_hafas} ) {
+ $station_link .= '?hafas=' . $status->{backend_name};
}
$self->render(
json => {
@@ -749,8 +714,8 @@ sub travel_action {
sub {
my ( $still_checked_in, $error ) = @_;
my $station_link = '/s/' . $params->{station};
- if ( $status->{train_id} =~ m{[|]} ) {
- $station_link .= '?hafas=1';
+ if ( $status->{is_hafas} ) {
+ $station_link .= '?hafas=' . $status->{backend_name};
}
if ($error) {
@@ -800,8 +765,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=1';
+ if ( $status->{is_hafas} ) {
+ $redir
+ = '/s/'
+ . $status->{dep_eva}
+ . '?hafas='
+ . $status->{backend_name};
}
else {
$redir = '/s/' . $status->{dep_ds100};
@@ -818,6 +787,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 +890,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 +899,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 +915,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 +938,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 +965,6 @@ sub station {
}
else {
- $api_link = '/s/' . $status->{station_eva} . '?hafas=1';
-
# You can't check into a train which terminates here
@results = grep { $_->departure } @{ $status->{results} };
@@ -1029,10 +993,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 +1008,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 +1026,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 +1045,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 +1063,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 +1087,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=1' );
+ $self->redirect_to( '/s/'
+ . $suggestions[0]->eva
+ . '?hafas='
+ . $hafas_service );
}
else {
$self->render(
@@ -1169,17 +1143,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=1");
- }
- else {
- $self->redirect_to("/s/${station}");
- }
- }
- else {
- $self->redirect_to("/s/${station}?hafas=1");
- }
+ $self->redirect_to("/s/${station}");
}
sub cancelled {
@@ -1323,8 +1287,6 @@ sub commute {
sub map_history {
my ($self) = @_;
- my $location = $self->app->coordinates_by_station;
-
if ( not $self->param('route_type') ) {
$self->param( route_type => 'polybee' );
}
@@ -1510,7 +1472,7 @@ sub year_in_review {
if ( not @journeys ) {
$self->render(
'not_found',
- message => 'Keine Zugfahrten im angefragten Jahr gefunden.',
+ message => 'Keine Fahrten im angefragten Jahr gefunden.',
status => 404
);
return;
@@ -1583,7 +1545,7 @@ sub yearly_history {
$self->render(
'not_found',
status => 404,
- message => 'Keine Zugfahrten im angefragten Jahr gefunden.'
+ message => 'Keine Fahrten im angefragten Jahr gefunden.'
);
return;
}
@@ -1660,7 +1622,7 @@ sub monthly_history {
if ( not @journeys ) {
$self->render(
'not_found',
- message => 'Keine Zugfahrten im angefragten Monat gefunden.',
+ message => 'Keine Fahrten im angefragten Monat gefunden.',
status => 404
);
return;
@@ -1682,13 +1644,15 @@ sub monthly_history {
}
},
any => {
- template => 'history_by_month',
- title => "travelynx: $month_name $year",
- journeys => [@journeys],
- year => $year,
- month => $month,
- month_name => $month_name,
- statistics => $stats
+ template => 'history_by_month',
+ title => "travelynx: $month_name $year",
+ journeys => [@journeys],
+ year => $year,
+ month => $month,
+ month_name => $month_name,
+ filter_from => $interval_start,
+ filter_to => $interval_end->clone->subtract( days => 1 ),
+ statistics => $stats
}
);
@@ -2112,6 +2076,7 @@ sub add_journey_form {
$self->render(
'add_journey',
with_autocomplete => 1,
+ status => 400,
error =>
'Zug muss als „Typ Nummer“ oder „Typ Linie Nummer“ eingegeben werden.'
);
@@ -2126,6 +2091,7 @@ sub add_journey_form {
$self->render(
'add_journey',
with_autocomplete => 1,
+ status => 400,
error => "${key}: Ungültiges Datums-/Zeitformat"
);
return;
@@ -2148,8 +2114,9 @@ sub add_journey_form {
my $db = $self->pg->db;
my $tx = $db->begin;
- $opt{db} = $db;
- $opt{uid} = $self->current_user->{id};
+ $opt{db} = $db;
+ $opt{uid} = $self->current_user->{id};
+ $opt{backend_id} = 1;
my ( $journey_id, $error ) = $self->journeys->add(%opt);
@@ -2167,6 +2134,7 @@ sub add_journey_form {
$self->render(
'add_journey',
with_autocomplete => 1,
+ status => 400,
error => $error,
);
}
diff --git a/lib/Travelynx/Helper/DBDB.pm b/lib/Travelynx/Helper/DBDB.pm
index b98a372..79e9c0b 100644
--- a/lib/Travelynx/Helper/DBDB.pm
+++ b/lib/Travelynx/Helper/DBDB.pm
@@ -27,39 +27,65 @@ sub new {
}
sub has_wagonorder_p {
- my ( $self, $ts, $train_no ) = @_;
- my $api_ts = $ts->strftime('%Y%m%d%H%M');
- my $url
- = "https://ist-wr.noncd.db.de/wagenreihung/1.0/${train_no}/${api_ts}";
- my $cache = $self->{cache};
+ my ( $self, %opt ) = @_;
+
+ $opt{train_type} //= q{};
+ my $datetime = $opt{datetime}->clone->set_time_zone('UTC');
+ my %param = (
+ administrationId => 80,
+ category => $opt{train_type},
+ date => $datetime->strftime('%Y-%m-%d'),
+ evaNumber => $opt{eva},
+ number => $opt{train_no},
+ time => $datetime->rfc3339 =~ s{(?=Z)}{.000}r
+ );
+
+ my $url = sprintf( '%s?%s',
+'https://www.bahn.de/web/api/reisebegleitung/wagenreihung/vehicle-sequence',
+ join( '&', map { $_ . '=' . $param{$_} } sort keys %param ) );
+
my $promise = Mojo::Promise->new;
+ my $debug_prefix
+ = "has_wagonorder_p($opt{train_type} $opt{train_no} @ $opt{eva})";
- if ( my $content = $cache->get("HEAD $url") ) {
+ if ( my $content = $self->{main_cache}->get("HEAD $url")
+ // $self->{realtime_cache}->get("HEAD $url") )
+ {
if ( $content eq 'n' ) {
+ $self->{log}->debug("${debug_prefix}: n (cached)");
return $promise->reject;
}
else {
+ $self->{log}->debug("${debug_prefix}: ${content} (cached)");
return $promise->resolve($content);
}
}
- $self->{user_agent}->request_timeout(5)->head_p( $url => $self->{header} )
+ $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} )
->then(
sub {
my ($tx) = @_;
if ( $tx->result->is_success ) {
- $cache->set( "HEAD $url", 'a' );
+ $self->{log}->debug("${debug_prefix}: a");
+ $self->{main_cache}->set( "HEAD $url", 'a' );
+ my $body = decode( 'utf-8', $tx->res->body );
+ my $json = JSON->new->decode($body);
+ $self->{main_cache}->freeze( $url, $json );
$promise->resolve('a');
}
else {
- $cache->set( "HEAD $url", 'n' );
+ my $code = $tx->res->code;
+ $self->{log}->debug("${debug_prefix}: n (HTTP $code)");
+ $self->{realtime_cache}->set( "HEAD $url", 'n' );
$promise->reject;
}
return;
}
)->catch(
sub {
- $cache->set( "HEAD $url", 'n' );
+ my ($err) = @_;
+ $self->{log}->debug("${debug_prefix}: n ($err)");
+ $self->{realtime_cache}->set( "HEAD $url", 'n' );
$promise->reject;
return;
}
@@ -68,15 +94,28 @@ sub has_wagonorder_p {
}
sub get_wagonorder_p {
- my ( $self, $api, $ts, $train_no ) = @_;
- my $api_ts = $ts->strftime('%Y%m%d%H%M');
- my $url
- = "https://ist-wr.noncd.db.de/wagenreihung/1.0/${train_no}/${api_ts}";
+ my ( $self, %opt ) = @_;
+
+ my $datetime = $opt{datetime}->clone->set_time_zone('UTC');
+ my %param = (
+ administrationId => 80,
+ category => $opt{train_type},
+ date => $datetime->strftime('%Y-%m-%d'),
+ evaNumber => $opt{eva},
+ number => $opt{train_no},
+ time => $datetime->rfc3339 =~ s{(?=Z)}{.000}r
+ );
+
+ my $url = sprintf( '%s?%s',
+'https://www.bahn.de/web/api/reisebegleitung/wagenreihung/vehicle-sequence',
+ join( '&', map { $_ . '=' . $param{$_} } sort keys %param ) );
+ my $debug_prefix
+ = "get_wagonorder_p($opt{train_type} $opt{train_no} @ $opt{eva})";
- my $cache = $self->{cache};
my $promise = Mojo::Promise->new;
- if ( my $content = $cache->thaw($url) ) {
+ if ( my $content = $self->{main_cache}->thaw($url) ) {
+ $self->{log}->debug("${debug_prefix}: (cached)");
$promise->resolve($content);
return $promise;
}
@@ -89,11 +128,13 @@ sub get_wagonorder_p {
if ( $tx->result->is_success ) {
my $body = decode( 'utf-8', $tx->res->body );
my $json = JSON->new->decode($body);
- $cache->freeze( $url, $json );
+ $self->{log}->debug("${debug_prefix}: success");
+ $self->{main_cache}->freeze( $url, $json );
$promise->resolve($json);
}
else {
- my $code = $tx->code;
+ my $code = $tx->res->code;
+ $self->{log}->debug("${debug_prefix}: HTTP ${code}");
$promise->reject("HTTP ${code}");
}
return;
@@ -101,6 +142,7 @@ sub get_wagonorder_p {
)->catch(
sub {
my ($err) = @_;
+ $self->{log}->debug("${debug_prefix}: error ${err}");
$promise->reject($err);
return;
}
@@ -113,10 +155,11 @@ sub get_stationinfo_p {
my $url = "https://lib.finalrewind.org/dbdb/s/${eva}.json";
- my $cache = $self->{cache};
+ my $cache = $self->{main_cache};
my $promise = Mojo::Promise->new;
if ( my $content = $cache->thaw($url) ) {
+ $self->{log}->debug("get_stationinfo_p(${eva}): (cached)");
return $promise->resolve($content);
}
@@ -126,12 +169,16 @@ sub get_stationinfo_p {
my ($tx) = @_;
if ( my $err = $tx->error ) {
+ $self->{log}->debug(
+"get_stationinfo_p(${eva}): HTTP $err->{code} $err->{message}"
+ );
$cache->freeze( $url, {} );
$promise->reject("HTTP $err->{code} $err->{message}");
return;
}
my $json = $tx->result->json;
+ $self->{log}->debug("get_stationinfo_p(${eva}): success");
$cache->freeze( $url, $json );
$promise->resolve($json);
return;
@@ -139,6 +186,7 @@ sub get_stationinfo_p {
)->catch(
sub {
my ($err) = @_;
+ $self->{log}->debug("get_stationinfo_p(${eva}): Error ${err}");
$cache->freeze( $url, {} );
$promise->reject($err);
return;
diff --git a/lib/Travelynx/Helper/HAFAS.pm b/lib/Travelynx/Helper/HAFAS.pm
index 7671d78..a8ab395 100644
--- a/lib/Travelynx/Helper/HAFAS.pm
+++ b/lib/Travelynx/Helper/HAFAS.pm
@@ -33,6 +33,12 @@ sub new {
return bless( \%opt, $class );
}
+sub get_service {
+ my ( $self, $service ) = @_;
+
+ return Travel::Status::DE::HAFAS::get_service($service);
+}
+
sub get_json_p {
my ( $self, $url, %opt ) = @_;
@@ -91,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},
@@ -105,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',
@@ -122,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},
@@ -133,11 +142,14 @@ sub get_tripid_p {
my @results = $hafas->results;
if ( not @results ) {
+ $self->{log}->debug("get_tripid_p($train_desc): no results");
$promise->reject(
"journeyMatch($train_desc) returned no results");
return;
}
+ $self->{log}->debug("get_tripid_p($train_desc): success");
+
my $result = $results[0];
if ( @results > 1 ) {
for my $journey (@results) {
@@ -154,6 +166,7 @@ sub get_tripid_p {
)->catch(
sub {
my ($err) = @_;
+ $self->{log}->debug("get_tripid_p($train_desc): error $err");
$promise->reject($err);
return;
}
@@ -169,6 +182,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},
},
@@ -182,15 +196,18 @@ sub get_journey_p {
my $journey = $hafas->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;
}
@@ -199,13 +216,14 @@ sub get_journey_p {
return $promise;
}
-sub get_route_timestamps_p {
+sub get_route_p {
my ( $self, %opt ) = @_;
my $promise = Mojo::Promise->new;
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
Travel::Status::DE::HAFAS->new_p(
+ service => $opt{service},
journey => {
id => $opt{trip_id},
@@ -219,13 +237,12 @@ sub get_route_timestamps_p {
sub {
my ($hafas) = @_;
my $journey = $hafas->result;
- my $ret = {};
+ my $ret = [];
my $polyline;
my $station_is_past = 1;
for my $stop ( $journey->route ) {
- my $name = $stop->loc->name;
- $ret->{$name} = $ret->{ $stop->loc->eva } = {
+ my $entry = {
name => $stop->loc->name,
eva => $stop->loc->eva,
sched_arr => _epoch( $stop->sched_arr ),
@@ -234,29 +251,32 @@ sub get_route_timestamps_p {
rt_dep => _epoch( $stop->rt_dep ),
arr_delay => $stop->arr_delay,
dep_delay => $stop->dep_delay,
- load => $stop->load
+ load => $stop->load,
+ lat => $stop->loc->lat,
+ lon => $stop->loc->lon,
};
if ( $stop->tz_offset ) {
- $ret->{$name}{tz_offset} = $stop->tz_offset;
+ $entry->{tz_offset} = $stop->tz_offset;
}
if ( ( $stop->arr_cancelled or not $stop->sched_arr )
and ( $stop->dep_cancelled or not $stop->sched_dep ) )
{
- $ret->{$name}{isCancelled} = 1;
+ $entry->{isCancelled} = 1;
}
if (
$station_is_past
- and not $ret->{$name}{isCancelled}
+ and not $entry->{isCancelled}
and $now->epoch < (
- $ret->{$name}{rt_arr} // $ret->{$name}{rt_dep}
- // $ret->{$name}{sched_arr}
- // $ret->{$name}{sched_dep} // $now->epoch
+ $entry->{rt_arr} // $entry->{rt_dep}
+ // $entry->{sched_arr} // $entry->{sched_dep}
+ // $now->epoch
)
)
{
$station_is_past = 0;
}
- $ret->{$name}{isPast} = $station_is_past;
+ $entry->{isPast} = $station_is_past;
+ push( @{$ret}, $entry );
}
if ( $journey->polyline ) {
@@ -298,12 +318,14 @@ sub get_route_timestamps_p {
}
}
+ $self->{log}->debug("get_route_p($opt{trip_id}): success");
$promise->resolve( $ret, $journey, $polyline );
return;
}
)->catch(
sub {
my ($err) = @_;
+ $self->{log}->debug("get_route_p($opt{trip_id}): error $err");
$promise->reject($err);
return;
}
diff --git a/lib/Travelynx/Helper/IRIS.pm b/lib/Travelynx/Helper/IRIS.pm
index deed79a..34739eb 100644
--- a/lib/Travelynx/Helper/IRIS.pm
+++ b/lib/Travelynx/Helper/IRIS.pm
@@ -41,8 +41,12 @@ sub get_departures {
my @station_matches
= Travel::Status::DE::IRIS::Stations::get_station($station);
+ if ( $station =~ m{ ^ \d+ $ }x ) {
+ @station_matches = ( [ undef, undef, $station ] );
+ }
+
if ( @station_matches == 1 ) {
- $station = $station_matches[0][0];
+ $station = $station_matches[0][2];
my $status = Travel::Status::DE::IRIS->new(
station => $station,
main_cache => $self->{main_cache},
@@ -108,8 +112,12 @@ sub get_departures_p {
my @station_matches
= Travel::Status::DE::IRIS::Stations::get_station($station);
+ if ( $station =~ m{ ^ \d+ $ }x ) {
+ @station_matches = ( [ undef, undef, $station ] );
+ }
+
if ( @station_matches == 1 ) {
- $station = $station_matches[0][0];
+ $station = $station_matches[0][2];
my $promise = Mojo::Promise->new;
Travel::Status::DE::IRIS->new_p(
station => $station,
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 69026ac..62e60f1 100644
--- a/lib/Travelynx/Model/InTransit.pm
+++ b/lib/Travelynx/Model/InTransit.pm
@@ -47,6 +47,16 @@ sub epoch_to_dt {
);
}
+sub epoch_or_dt_to_dt {
+ my ($input) = @_;
+
+ if ( ref($input) eq 'DateTime' ) {
+ return $input;
+ }
+
+ return epoch_to_dt($input);
+}
+
sub new {
my ( $class, %opt ) = @_;
@@ -83,11 +93,13 @@ 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};
my $checkin_station_id = $opt{departure_eva};
my $route = $opt{route};
+ my $data = $opt{data};
my $json = JSON->new;
@@ -114,9 +126,11 @@ sub add {
data => JSON->new->encode(
{
rt => $train->departure_has_realtime ? 1
- : 0
+ : 0,
+ %{ $data // {} }
}
),
+ backend_id => $backend_id,
}
);
}
@@ -137,7 +151,9 @@ sub add {
rt_dep => _epoch( $j_stop->rt_dep ),
arr_delay => $j_stop->arr_delay,
dep_delay => $j_stop->dep_delay,
- load => $j_stop->load
+ load => $j_stop->load,
+ lat => $j_stop->loc->lat,
+ lon => $j_stop->loc->lon,
}
]
);
@@ -162,7 +178,13 @@ sub add {
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 => JSON->new->encode(
+ {
+ rt => $stop->{rt_dep} ? 1 : 0,
+ %{ $data // {} }
+ }
+ ),
+ backend_id => $backend_id,
}
);
}
@@ -211,8 +233,15 @@ sub postprocess {
if ($is_after) {
push( @route_after, $station );
}
- if ( $ret->{dep_name}
- and $station->[0] eq $ret->{dep_name} )
+
+ # Note that the departure stop may be present more than once in @route,
+ # e.g. when traveling along ring lines such as S41 / S42 in Berlin.
+ if (
+ $ret->{dep_name}
+ and $station->[0] eq $ret->{dep_name}
+ and not($station->[2]{sched_dep}
+ and $station->[2]{sched_dep} < $ret->{sched_dep_ts} )
+ )
{
$is_after = 1;
if ( @{$station} > 1 and not $dep_info ) {
@@ -236,6 +265,11 @@ sub postprocess {
$ret->{extra_data} = $ret->{data};
$ret->{comment} = $ret->{user_data}{comment};
+ $ret->{platform_type} = 'Gleis';
+ if ( $ret->{train_type} =~ m{ ast | bus | ruf }ix ) {
+ $ret->{platform_type} = 'Steig';
+ }
+
$ret->{visibility_str}
= $ret->{visibility}
? $visibility_itoa{ $ret->{visibility} }
@@ -273,31 +307,25 @@ sub postprocess {
# station is present several times in a train's route, e.g.
# for Frankfurt Flughafen in some nightly connections.
my $times = $station->[2] // {};
- if ( $times->{sched_arr}
- and ref( $times->{sched_arr} ) ne 'DateTime' )
- {
- $times->{sched_arr}
- = epoch_to_dt( $times->{sched_arr} );
- if ( $times->{rt_arr} ) {
- $times->{rt_arr}
- = epoch_to_dt( $times->{rt_arr} );
- $times->{arr_delay}
- = $times->{rt_arr}->epoch - $times->{sched_arr}->epoch;
+ for my $key (qw(sched_arr rt_arr sched_dep rt_dep)) {
+ if ( $times->{$key} ) {
+ $times->{$key}
+ = epoch_or_dt_to_dt( $times->{$key} );
}
+ }
+ if ( $times->{sched_arr} and $times->{rt_arr} ) {
+ $times->{arr_delay}
+ = $times->{rt_arr}->epoch - $times->{sched_arr}->epoch;
+ }
+ if ( $times->{sched_arr} or $times->{rt_arr} ) {
$times->{arr} = $times->{rt_arr} || $times->{sched_arr};
$times->{arr_countdown} = $times->{arr}->epoch - $epoch;
}
- if ( $times->{sched_dep}
- and ref( $times->{sched_dep} ) ne 'DateTime' )
- {
- $times->{sched_dep}
- = epoch_to_dt( $times->{sched_dep} );
- if ( $times->{rt_dep} ) {
- $times->{rt_dep}
- = epoch_to_dt( $times->{rt_dep} );
- $times->{dep_delay}
- = $times->{rt_dep}->epoch - $times->{sched_dep}->epoch;
- }
+ if ( $times->{sched_dep} and $times->{rt_dep} ) {
+ $times->{dep_delay}
+ = $times->{rt_dep}->epoch - $times->{sched_dep}->epoch;
+ }
+ if ( $times->{sched_dep} or $times->{rt_dep} ) {
$times->{dep} = $times->{rt_dep} || $times->{sched_dep};
$times->{dep_countdown} = $times->{dep}->epoch - $epoch;
}
@@ -408,17 +436,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;
}
@@ -457,13 +488,6 @@ sub set_arrival {
my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db;
my $train = $opt{train};
- my $route = $opt{route};
-
- $route = $self->_merge_old_route(
- db => $db,
- uid => $uid,
- route => $route
- );
my $json = JSON->new;
@@ -474,7 +498,6 @@ sub set_arrival {
arr_platform => $train->platform,
sched_arrival => $train->sched_arrival,
real_arrival => $train->arrival,
- route => $json->encode($route),
messages => $json->encode(
[ map { [ $_->[0]->epoch, $_->[1] ] } $train->messages ]
)
@@ -566,7 +589,8 @@ sub set_polyline {
$self->set_polyline_id(
uid => $uid,
db => $db,
- polyline_id => $polyline_id
+ polyline_id => $polyline_id,
+ train_id => $opt{train_id},
);
}
@@ -579,11 +603,13 @@ sub set_polyline_id {
my $db = $opt{db} // $self->{pg}->db;
my $polyline_id = $opt{polyline_id};
- $db->update(
- 'in_transit',
- { polyline_id => $polyline_id },
- { user_id => $uid }
- );
+ my %where = ( user_id => $uid );
+
+ if ( $opt{train_id} ) {
+ $where{train_id} = $opt{train_id};
+ }
+
+ $db->update( 'in_transit', { polyline_id => $polyline_id }, \%where );
}
sub set_route_data {
@@ -596,6 +622,12 @@ sub set_route_data {
my $qos_msg = $opt{qos_messages};
my $him_msg = $opt{him_messages};
+ my %where = ( user_id => $uid );
+
+ if ( $opt{train_id} ) {
+ $where{train_id} = $opt{train_id};
+ }
+
my $res_h = $db->select( 'in_transit', ['data'], { user_id => $uid } )
->expand->hash;
@@ -612,7 +644,7 @@ sub set_route_data {
route => JSON->new->encode($route),
data => JSON->new->encode($data)
},
- { user_id => $uid }
+ \%where
);
}
@@ -778,7 +810,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(
@@ -793,7 +824,9 @@ sub update_arrival_hafas {
rt_dep => _epoch( $j_stop->rt_dep ),
arr_delay => $j_stop->arr_delay,
dep_delay => $j_stop->dep_delay,
- load => $j_stop->load
+ load => $j_stop->load,
+ lat => $j_stop->loc->lat,
+ lon => $j_stop->loc->lon,
}
]
);
@@ -839,6 +872,12 @@ sub update_data {
my $db = $opt{db} // $self->{pg}->db;
my $new_data = $opt{data} // {};
+ my %where = ( user_id => $uid );
+
+ if ( $opt{train_id} ) {
+ $where{train_id} = $opt{train_id};
+ }
+
my $res_h = $db->select( 'in_transit', ['data'], { user_id => $uid } )
->expand->hash;
@@ -848,11 +887,7 @@ sub update_data {
$data->{$k} = $v;
}
- $db->update(
- 'in_transit',
- { data => JSON->new->encode($data) },
- { user_id => $uid }
- );
+ $db->update( 'in_transit', { data => JSON->new->encode($data) }, \%where );
}
sub update_user_data {
@@ -862,6 +897,12 @@ sub update_user_data {
my $db = $opt{db} // $self->{pg}->db;
my $new_data = $opt{user_data} // {};
+ my %where = ( user_id => $uid );
+
+ if ( $opt{train_id} ) {
+ $where{train_id} = $opt{train_id};
+ }
+
my $res_h = $db->select( 'in_transit', ['user_data'], { user_id => $uid } )
->expand->hash;
@@ -871,11 +912,8 @@ sub update_user_data {
$data->{$k} = $v;
}
- $db->update(
- 'in_transit',
- { user_data => JSON->new->encode($data) },
- { user_id => $uid }
- );
+ $db->update( 'in_transit',
+ { user_data => JSON->new->encode($data) }, \%where );
}
sub update_visibility {
diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm
index 97c4681..343d680 100755
--- a/lib/Travelynx/Model/Journeys.pm
+++ b/lib/Travelynx/Model/Journeys.pm
@@ -118,8 +118,10 @@ sub add {
my $db = $opt{db};
my $uid = $opt{uid};
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
- my $dep_station = $self->{stations}->search( $opt{dep_station} );
- my $arr_station = $self->{stations}->search( $opt{arr_station} );
+ my $dep_station = $self->{stations}
+ ->search( $opt{dep_station}, backend_id => $opt{backend_id} );
+ my $arr_station = $self->{stations}
+ ->search( $opt{arr_station}, backend_id => $opt{backend_id} );
if ( not $dep_station ) {
return ( undef, 'Unbekannter Startbahnhof' );
@@ -167,16 +169,36 @@ sub add {
my @route;
if ( not $route_has_start ) {
- push( @route, [ $dep_station->{name}, $dep_station->{eva}, {} ] );
+ push(
+ @route,
+ [
+ $dep_station->{name},
+ $dep_station->{eva},
+ {
+ lat => $dep_station->{lat},
+ lon => $dep_station->{lon},
+ }
+ ]
+ );
}
if ( $opt{route} ) {
my @unknown_stations;
for my $station ( @{ $opt{route} } ) {
- my $station_info = $self->{stations}->search($station);
+ my $station_info = $self->{stations}
+ ->search( $station, backend_id => $opt{backend_id} );
if ($station_info) {
- push( @route,
- [ $station_info->{name}, $station_info->{eva}, {} ] );
+ push(
+ @route,
+ [
+ $station_info->{name},
+ $station_info->{eva},
+ {
+ lat => $station_info->{lat},
+ lon => $station_info->{lon},
+ }
+ ]
+ );
}
else {
push( @route, [ $station, undef, {} ] );
@@ -198,7 +220,17 @@ sub add {
}
if ( not $route_has_stop ) {
- push( @route, [ $arr_station->{name}, $arr_station->{eva}, {} ] );
+ push(
+ @route,
+ [
+ $arr_station->{name},
+ $arr_station->{eva},
+ {
+ lat => $arr_station->{lat},
+ lon => $arr_station->{lon},
+ }
+ ]
+ );
}
my $entry = {
@@ -218,6 +250,7 @@ sub add {
edited => 0x3fff,
cancelled => $opt{cancelled} ? 1 : 0,
route => JSON->new->encode( \@route ),
+ backend_id => $opt{backend_id},
};
if ( $opt{comment} ) {
@@ -515,7 +548,7 @@ sub get {
my @select
= (
- qw(journey_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_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,
@@ -573,6 +606,10 @@ sub get {
my $ref = {
id => $entry->{journey_id},
+ 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},
@@ -632,7 +669,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};
}
}
@@ -767,14 +807,40 @@ sub get_oldest_ts {
return undef;
}
-sub get_latest_checkout_station_id {
+sub get_latest_checkout_latlon {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+
+ my $res_h = $db->select(
+ 'journeys_str',
+ [ 'arr_lat', 'arr_lon', ],
+ {
+ user_id => $uid,
+ cancelled => 0
+ },
+ {
+ limit => 1,
+ order_by => { -desc => 'journey_id' }
+ }
+ )->hash;
+
+ if ( not $res_h ) {
+ return;
+ }
+
+ return $res_h->{arr_lat}, $res_h->{arr_lon};
+
+}
+
+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
@@ -789,7 +855,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 {
@@ -800,7 +866,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
@@ -821,9 +890,10 @@ sub get_latest_checkout_stations {
push(
@ret,
{
- name => $row->{arr_name},
- eva => $row->{arr_eva},
- hafas => ( $row->{train_id} =~ m{[|]} ? 1 : 0 ),
+ name => $row->{arr_name},
+ eva => $row->{arr_eva},
+ hafas => $row->{is_hafas} ? $row->{backend_name} : 0,
+ backend_id => $row->{backend_id},
}
);
}
@@ -1082,19 +1152,62 @@ sub get_travel_distance {
->warn("Journey $journey->{id} has no from_name for EVA $from_eva");
}
+ # Work around inconsistencies caused by a multiple EVA IDs mapping to the same station name
+ if (
+ @{ $polyline_ref // [] }
+ and not List::MoreUtils::any { $_->[2] and $_->[2] == $from_eva }
+ @{ $polyline_ref // [] }
+ )
+ {
+ $self->{log}->debug(
+"Journey $journey->{id} from_eva ($from_eva) is not part of polyline"
+ );
+ for my $entry ( @{$route_ref} ) {
+ if ( $entry->[0] eq $from and $entry->[1] ) {
+ $from_eva = $entry->[1];
+ $self->{log}->debug("... setting to $from_eva");
+ last;
+ }
+ }
+ }
+ if (
+ @{ $polyline_ref // [] }
+ and not List::MoreUtils::any { $_->[2] and $_->[2] == $to_eva }
+ @{ $polyline_ref // [] }
+ )
+ {
+ $self->{log}->debug(
+ "Journey $journey->{id} to_eva ($to_eva) is not part of polyline");
+ for my $entry ( @{$route_ref} ) {
+ if ( $entry->[0] eq $to and $entry->[1] ) {
+ $to_eva = $entry->[1];
+ $self->{log}->debug("... setting to $to_eva");
+ last;
+ }
+ }
+ }
+
my $distance_polyline = 0;
my $distance_intermediate = 0;
- my $distance_beeline = 0;
- my $skipped = 0;
my $geo = GIS::Distance->new();
- my @stations = map { $_->[0] } @{$route_ref};
- my @route = after_incl { $_ eq $from } @stations;
- @route = before_incl { $_ eq $to } @route;
+ my $distance_beeline
+ = $geo->distance_metal( @{$from_latlon}, @{$to_latlon} );
+ my @route
+ = after_incl { ( $_->[1] and $_->[1] == $from_eva ) or $_->[0] eq $from }
+ @{$route_ref};
+ @route
+ = before_incl { ( $_->[1] and $_->[1] == $to_eva ) or $_->[0] eq $to }
+ @route;
- if ( @route < 2 ) {
+ if (
+ @route < 2
+ or ( $route[-1][0] ne $to
+ and ( not $route[-1][1] or $route[-1][1] != $to_eva ) )
+ )
+ {
# I AM ERROR
- return ( 0, 0, 0 );
+ return ( 0, 0, $distance_beeline );
}
my @polyline = after_incl { $_->[2] and $_->[2] == $from_eva }
@@ -1102,34 +1215,32 @@ sub get_travel_distance {
@polyline
= before_incl { $_->[2] and $_->[2] == $to_eva } @polyline;
- my $prev_station = shift @polyline;
- for my $station (@polyline) {
- $distance_polyline += $geo->distance_metal(
- $prev_station->[1], $prev_station->[0],
- $station->[1], $station->[0]
- );
- $prev_station = $station;
- }
-
- $prev_station = $self->{latlon_by_station}->{ shift @route };
- if ( not $prev_station ) {
- return ( $distance_polyline, 0, 0 );
- }
-
- for my $station_name (@route) {
- if ( my $station = $self->{latlon_by_station}->{$station_name} ) {
- $distance_intermediate += $geo->distance_metal(
- $prev_station->[0], $prev_station->[1],
- $station->[0], $station->[1]
+ # ensure that before_incl matched -- otherwise, @polyline is too long
+ if ( @polyline and $polyline[-1][2] == $to_eva ) {
+ my $prev_station = shift @polyline;
+ for my $station (@polyline) {
+ $distance_polyline += $geo->distance_metal(
+ $prev_station->[1], $prev_station->[0],
+ $station->[1], $station->[0]
);
$prev_station = $station;
}
}
- $distance_beeline = $geo->distance_metal( @{$from_latlon}, @{$to_latlon} );
+ if ( defined $route[0][2]{lat} and defined $route[0][2]{lon} ) {
+ my $prev_station = shift @route;
+ for my $station (@route) {
+ if ( defined $station->[2]{lat} and defined $station->[2]{lon} ) {
+ $distance_intermediate += $geo->distance_metal(
+ $prev_station->[2]{lat}, $prev_station->[2]{lon},
+ $station->[2]{lat}, $station->[2]{lon}
+ );
+ $prev_station = $station;
+ }
+ }
+ }
- return ( $distance_polyline, $distance_intermediate,
- $distance_beeline, $skipped );
+ return ( $distance_polyline, $distance_intermediate, $distance_beeline );
}
sub grep_single {
@@ -1694,28 +1805,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 ) = @_;
@@ -1724,21 +1836,32 @@ 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',
@@ -1746,7 +1869,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'],
@@ -1756,8 +1880,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 ac4019c..76fd452 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 }
);
@@ -74,7 +164,7 @@ sub add_meta {
sub get_db_iterator {
my ($self) = @_;
- return $self->{pg}->db->select( 'stations', '*' );
+ return $self->{pg}->db->select( 'stations_str', '*' );
}
sub get_meta {
@@ -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,43 +215,53 @@ 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 @ret
- = $self->{pg}->db->select( 'stations', '*', { eva => { '=', \@evas } } )
- ->hashes->each;
- return @ret;
-}
-
-# Slow
-sub get_latlon_by_name {
my ( $self, %opt ) = @_;
- my $db = $opt{db} // $self->{pg}->db;
+ $opt{db} //= $self->{pg}->db;
+ $opt{backend_id} //= $self->get_backend_id(%opt);
- my %location;
- my $res = $db->select( 'stations', [ 'name', 'lat', 'lon' ] );
- while ( my $row = $res->hash ) {
- $location{ $row->{name} } = [ $row->{lat}, $row->{lon} ];
- }
- return \%location;
+ my @ret = $self->{pg}->db->select(
+ 'stations',
+ '*',
+ {
+ eva => { '=', $opt{evas} },
+ source => $opt{backend_id}
+ }
+ )->hashes->each;
+ return @ret;
}
# Slow
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
@@ -166,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 4602fa2..7d3777b 100644
--- a/lib/Travelynx/Model/Users.pm
+++ b/lib/Travelynx/Model/Users.pm
@@ -40,14 +40,6 @@ my %predicate_atoi = (
is_blocked_by => 3,
);
-my @sb_templates = (
- undef,
- [ 'DBF', 'https://dbf.finalrewind.org/{name}?rt=1#{tt}{tn}' ],
- [ 'bahn.expert', 'https://bahn.expert/{name}#{id}' ],
- [ 'DBF HAFAS', 'https://dbf.finalrewind.org/{name}?rt=1&hafas=1#{tt}{tn}' ],
- [ 'bahn.expert/regional', 'https://bahn.expert/regional/{name}#{id}' ],
-);
-
my %token_id = (
status => 1,
history => 2,
@@ -213,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;
@@ -409,12 +408,13 @@ sub get {
my $uid = $opt{uid};
my $user = $db->select(
- 'users',
+ 'users_with_backend',
'id, name, status, public_level, email, '
- . 'external_services, accept_follows, notifications, '
+ . '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) {
@@ -435,12 +435,8 @@ sub get {
past_status => $user->{public_level} & 0x08000 ? 1 : 0,
past_all => $user->{public_level} & 0x10000 ? 1 : 0,
email => $user->{email},
- sb_name => $user->{external_services}
- ? $sb_templates[ $user->{external_services} & 0x07 ][0]
- : undef,
- sb_template => $user->{external_services}
- ? $sb_templates[ $user->{external_services} & 0x07 ][1]
- : undef,
+ sb_template =>
+ 'https://dbf.finalrewind.org/{name}?rt=1&hafas={hafas}#{tt}{tn}',
registered_at => DateTime->from_epoch(
epoch => $user->{registered_at_ts},
time_zone => 'Europe/Berlin'
@@ -455,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;
@@ -659,24 +658,6 @@ sub use_history {
}
}
-sub use_external_services {
- my ( $self, %opt ) = @_;
- my $db = $opt{db} // $self->{pg}->db;
- my $uid = $opt{uid};
- my $value = $opt{set};
-
- if ( defined $value ) {
- if ( $value < 0 or $value > 4 ) {
- $value = 0;
- }
- $db->update( 'users', { external_services => $value }, { id => $uid } );
- }
- else {
- return $db->select( 'users', ['external_services'], { id => $uid } )
- ->hash->{external_services};
- }
-}
-
sub get_webhook {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;