summaryrefslogtreecommitdiff
path: root/lib/Travelynx.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Travelynx.pm')
-rwxr-xr-xlib/Travelynx.pm2342
1 files changed, 1103 insertions, 1239 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index 91de1c6..4749d65 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -1,6 +1,6 @@
package Travelynx;
-# Copyright (C) 2020 Daniel Friesel
+# Copyright (C) 2020-2023 Birte Kristina Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -13,14 +13,13 @@ use Cache::File;
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
use DateTime;
use DateTime::Format::Strptime;
-use Encode qw(decode encode);
+use Encode qw(decode encode);
use File::Slurp qw(read_file);
use JSON;
use List::Util;
-use List::UtilsBy qw(uniq_by);
+use List::UtilsBy qw(uniq_by);
use List::MoreUtils qw(first_index);
use Travel::Status::DE::DBWagenreihung;
-use Travel::Status::DE::IRIS::Stations;
use Travelynx::Helper::DBDB;
use Travelynx::Helper::HAFAS;
use Travelynx::Helper::IRIS;
@@ -29,14 +28,14 @@ use Travelynx::Helper::Traewelling;
use Travelynx::Model::InTransit;
use Travelynx::Model::Journeys;
use Travelynx::Model::JourneyStatsCache;
+use Travelynx::Model::Stations;
use Travelynx::Model::Traewelling;
use Travelynx::Model::Users;
-use XML::LibXML;
sub check_password {
my ( $password, $hash ) = @_;
- if ( bcrypt( $password, $hash ) eq $hash ) {
+ if ( bcrypt( substr( $password, 0, 10000 ), $hash ) eq $hash ) {
return 1;
}
return 0;
@@ -56,27 +55,6 @@ sub epoch_to_dt {
);
}
-sub get_station {
- my ( $station_name, $exact_match ) = @_;
-
- my @candidates
- = Travel::Status::DE::IRIS::Stations::get_station($station_name);
-
- if ( @candidates == 1 ) {
- if ( not $exact_match ) {
- return $candidates[0];
- }
- if ( $candidates[0][0] eq $station_name
- or $candidates[0][1] eq $station_name
- or $candidates[0][2] eq $station_name )
- {
- return $candidates[0];
- }
- return undef;
- }
- return undef;
-}
-
sub startup {
my ($self) = @_;
@@ -94,6 +72,7 @@ sub startup {
}
chomp $self->config->{version};
+ $self->defaults( version => $self->config->{version} // 'UNKNOWN' );
$self->plugin(
authentication => {
@@ -121,6 +100,23 @@ sub startup {
},
}
);
+
+ if ( my $oa = $self->config->{traewelling}{oauth} ) {
+ $self->plugin(
+ OAuth2 => {
+ providers => {
+ traewelling => {
+ key => $oa->{id},
+ secret => $oa->{secret},
+ authorize_url =>
+'https://traewelling.de/oauth/authorize?response_type=code',
+ token_url => 'https://traewelling.de/oauth/token',
+ }
+ }
+ }
+ );
+ }
+
$self->sessions->default_expiration( 60 * 60 * 24 * 180 );
# Starting with v8.11, Mojolicious sends SameSite=Lax Cookies by default.
@@ -128,7 +124,7 @@ sub startup {
# security and usability for websites that want to maintain user's logged-in
# session after the user arrives from an external link". In practice,
# Safari (both iOS and macOS) does not send a SameSite=lax cookie when
- # following a link from an external site. So, marudor.de providing a
+ # following a link from an external site. So, bahn.expert providing a
# checkin link to travelynx.de/s/whatever does not work because the user
# is not logged in due to Safari not sending the cookie.
#
@@ -144,10 +140,10 @@ sub startup {
before_dispatch => sub {
my ($self) = @_;
- # The "theme" cookie is set client-side if the theme we delivered was
- # changed by dark mode detection or by using the theme switcher. It's
- # not part of Mojolicious' session data (and can't be, due to
- # signing and HTTPOnly), so we need to add it here.
+ # The "theme" cookie is set client-side if the theme we delivered was
+ # changed by dark mode detection or by using the theme switcher. It's
+ # not part of Mojolicious' session data (and can't be, due to
+ # signing and HTTPOnly), so we need to add it here.
for my $cookie ( @{ $self->req->cookies } ) {
if ( $cookie->name eq 'theme' ) {
$self->session( theme => $cookie->value );
@@ -182,70 +178,18 @@ sub startup {
);
$self->attr(
- token_type => sub {
- return {
- status => 1,
- history => 2,
- travel => 3,
- import => 4,
- };
- }
- );
- $self->attr(
- token_types => sub {
- return [qw(status history travel import)];
- }
- );
-
- $self->attr(
- account_public_mask => sub {
- return {
- status_intern => 0x01,
- status_extern => 0x02,
- status_comment => 0x04,
- history_intern => 0x10,
- history_latest => 0x20,
- history_full => 0x40,
- };
- }
- );
-
- $self->attr(
- journey_edit_mask => sub {
- return {
- sched_departure => 0x0001,
- real_departure => 0x0002,
- from_station => 0x0004,
- route => 0x0010,
- is_cancelled => 0x0020,
- sched_arrival => 0x0100,
- real_arrival => 0x0200,
- to_station => 0x0400,
- };
- }
- );
-
- $self->attr(
coordinates_by_station => sub {
my $legacy_names = $self->app->renamed_station;
- my %location;
- for
- my $station ( Travel::Status::DE::IRIS::Stations::get_stations() )
- {
- if ( $station->[3] ) {
- $location{ $station->[1] }
- = [ $station->[4], $station->[3] ];
- }
- }
+ my $location = $self->stations->get_latlon_by_name;
while ( my ( $old_name, $new_name ) = each %{$legacy_names} ) {
- $location{$old_name} = $location{$new_name};
+ $location->{$old_name} = $location->{$new_name};
}
- return \%location;
+ 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/BahnhofsAbfahrten/blob/master/src/server/Reihung/ICENaming.ts
+ # 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(
@@ -262,15 +206,22 @@ sub startup {
}
);
- $self->attr(
- station_by_eva => sub {
- my %map;
- for
- my $station ( Travel::Status::DE::IRIS::Stations::get_stations() )
+ if ( not $self->app->config->{base_url} ) {
+ $self->app->log->error(
+"travelynx.conf: 'base_url' is missing. Links in maintenance/work/worker-generated E-Mails will be incorrect. This variable was introduced in travelynx 1.22; see examples/travelynx.conf for documentation."
+ );
+ }
+
+ $self->helper(
+ base_url_for => sub {
+ my ( $self, $path ) = @_;
+ if ( ( my $url = $self->url_for($path) )->base ne q{}
+ or not $self->app->config->{base_url} )
{
- $map{ $station->[2] } = $station;
+ return $url;
}
- return \%map;
+ return $self->url_for($path)
+ ->base( $self->app->config->{base_url} );
}
);
@@ -281,7 +232,7 @@ sub startup {
log => $self->app->log,
main_cache => $self->app->cache_iris_main,
realtime_cache => $self->app->cache_iris_rt,
- root_url => $self->url_for('/')->to_abs,
+ root_url => $self->base_url_for('/')->to_abs,
user_agent => $self->ua,
version => $self->app->config->{version},
);
@@ -295,7 +246,7 @@ sub startup {
log => $self->app->log,
main_cache => $self->app->cache_iris_main,
realtime_cache => $self->app->cache_iris_rt,
- root_url => $self->url_for('/')->to_abs,
+ root_url => $self->base_url_for('/')->to_abs,
version => $self->app->config->{version},
);
}
@@ -314,7 +265,7 @@ sub startup {
state $trwl_api = Travelynx::Helper::Traewelling->new(
log => $self->app->log,
model => $self->traewelling,
- root_url => $self->url_for('/')->to_abs,
+ root_url => $self->base_url_for('/')->to_abs,
user_agent => $self->ua,
version => $self->app->config->{version},
);
@@ -346,11 +297,13 @@ sub startup {
journeys => sub {
my ($self) = @_;
state $journeys = Travelynx::Model::Journeys->new(
- log => $self->app->log,
- pg => $self->pg,
- stats_cache => $self->journey_stats_cache,
- renamed_station => $self->app->renamed_station,
- station_by_eva => $self->app->station_by_eva,
+ 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,
);
}
);
@@ -391,6 +344,14 @@ sub startup {
);
$self->helper(
+ stations => sub {
+ my ($self) = @_;
+ state $stations
+ = Travelynx::Model::Stations->new( pg => $self->pg );
+ }
+ );
+
+ $self->helper(
users => sub {
my ($self) = @_;
state $users = Travelynx::Model::Users->new( pg => $self->pg );
@@ -403,7 +364,7 @@ sub startup {
state $dbdb = Travelynx::Helper::DBDB->new(
log => $self->app->log,
cache => $self->app->cache_iris_main,
- root_url => $self->url_for('/')->to_abs,
+ root_url => $self->base_url_for('/')->to_abs,
user_agent => $self->ua,
version => $self->app->config->{version},
);
@@ -433,69 +394,104 @@ sub startup {
);
$self->helper(
- 'grep_unknown_stations' => sub {
- my ( $self, @stations ) = @_;
-
- my @unknown_stations;
- for my $station (@stations) {
- my $station_info = get_station($station);
- if ( not $station_info ) {
- push( @unknown_stations, $station );
- }
+ 'sprintf_km' => sub {
+ my ( $self, $km ) = @_;
+
+ if ( $km < 1 ) {
+ return sprintf( '%.f m', $km * 1000 );
+ }
+ if ( $km < 10 ) {
+ return sprintf( '%.1f km', $km );
}
- return @unknown_stations;
+ return sprintf( '%.f km', $km );
+ }
+ );
+
+ $self->helper(
+ 'load_icon' => sub {
+ my ( $self, $load ) = @_;
+ my $first = $load->{FIRST} // 0;
+ my $second = $load->{SECOND} // 0;
+
+ my @symbols
+ = (
+ qw(help_outline person_outline people priority_high not_interested)
+ );
+
+ return ( $symbols[$first], $symbols[$second] );
}
);
$self->helper(
- 'checkin' => sub {
+ 'visibility_icon' => sub {
+ my ( $self, $visibility ) = @_;
+ if ( $visibility eq 'public' ) {
+ return 'language';
+ }
+ if ( $visibility eq 'travelynx' ) {
+ return 'lock_open';
+ }
+ if ( $visibility eq 'followers' ) {
+ return 'group';
+ }
+ if ( $visibility eq 'unlisted' ) {
+ return 'lock_outline';
+ }
+ if ( $visibility eq 'private' ) {
+ return 'lock';
+ }
+ return 'help_outline';
+ }
+ );
+
+ $self->helper(
+ 'checkin_p' => sub {
my ( $self, %opt ) = @_;
my $station = $opt{station};
my $train_id = $opt{train_id};
my $uid = $opt{uid} // $self->current_user->{id};
- my $db = $opt{db} // $self->pg->db;
+ my $db = $opt{db} // $self->pg->db;
+ my $hafas;
+
+ my $user = $self->get_user_status( $uid, $db );
+ if ( $user->{checked_in} or $user->{cancelled} ) {
+ return Mojo::Promise->reject('You are already checked in');
+ }
+
+ if ( $train_id =~ m{[|]} ) {
+ return $self->_checkin_hafas_p(%opt);
+ }
+
+ my $promise = Mojo::Promise->new;
- my $status = $self->iris->get_departures(
+ $self->iris->get_departures_p(
station => $station,
lookbehind => 140,
lookahead => 40
- );
- if ( $status->{errstr} ) {
- return ( undef, $status->{errstr} );
- }
- else {
- my ($train) = List::Util::first { $_->train_id eq $train_id }
- @{ $status->{results} };
- if ( not defined $train ) {
- return ( undef, "Train ${train_id} not found" );
- }
- else {
+ )->then(
+ sub {
+ my ($status) = @_;
- my $user = $self->get_user_status( $uid, $db );
- if ( $user->{checked_in} or $user->{cancelled} ) {
+ if ( $status->{errstr} ) {
+ $promise->reject( $status->{errstr} );
+ return;
+ }
- if ( $user->{train_id} eq $train_id
- and $user->{dep_eva} eq $status->{station_eva} )
- {
- # checking in twice is harmless
- return ( $train, undef );
- }
+ my $eva = $status->{station_eva};
+ my $train = List::Util::first { $_->train_id eq $train_id }
+ @{ $status->{results} };
- # Otherwise, someone forgot to check out first
- $self->checkout(
- station => $station,
- force => 1,
- uid => $uid,
- db => $db
- );
+ if ( not defined $train ) {
+ $promise->reject("Train ${train_id} not found");
+ return;
}
eval {
$self->in_transit->add(
uid => $uid,
db => $db,
- departure_eva => $status->{station_eva},
+ departure_eva => $eva,
train => $train,
route => [ $self->iris->route_diff($train) ],
);
@@ -503,17 +499,151 @@ sub startup {
if ($@) {
$self->app->log->error(
"Checkin($uid): INSERT failed: $@");
- return ( undef, 'INSERT failed: ' . $@ );
+ $promise->reject( 'INSERT failed: ' . $@ );
+ return;
}
- if ( not $opt{in_transaction} ) {
- # mustn't be called during a transaction
+ # mustn't be called during a transaction
+ if ( not $opt{in_transaction} ) {
$self->add_route_timestamps( $uid, $train, 1 );
$self->run_hook( $uid, 'checkin' );
}
- return ( $train, undef );
+
+ $promise->resolve($train);
+ return;
}
- }
+ )->catch(
+ sub {
+ my ( $err, $status ) = @_;
+ $promise->reject( $status->{errstr} );
+ return;
+ }
+ )->wait;
+
+ return $promise;
+ }
+ );
+
+ $self->helper(
+ '_checkin_hafas_p' => sub {
+ my ( $self, %opt ) = @_;
+
+ my $station = $opt{station};
+ my $train_id = $opt{train_id};
+ my $uid = $opt{uid} // $self->current_user->{id};
+ my $db = $opt{db} // $self->pg->db;
+ my $hafas;
+
+ my $promise = Mojo::Promise->new;
+
+ $self->hafas->get_journey_p(
+ trip_id => $train_id,
+ with_polyline => 1
+ )->then(
+ sub {
+ my ($journey) = @_;
+ my $found;
+ for my $stop ( $journey->route ) {
+ if ( $stop->loc->name eq $station
+ or $stop->loc->eva == $station )
+ {
+ $found = $stop;
+ last;
+ }
+ }
+ if ( not $found ) {
+ $promise->reject(
+ "Did not find journey $train_id at $station");
+ return;
+ }
+ for my $stop ( $journey->route ) {
+ $self->stations->add_or_update(
+ stop => $stop,
+ db => $db,
+ );
+ }
+ eval {
+ $self->in_transit->add(
+ uid => $uid,
+ db => $db,
+ journey => $journey,
+ stop => $found,
+ );
+ };
+ if ($@) {
+ $self->app->log->error(
+ "Checkin($uid): INSERT failed: $@");
+ $promise->reject( 'INSERT failed: ' . $@ );
+ return;
+ }
+ $self->in_transit->update_data(
+ uid => $uid,
+ db => $db,
+ data => { trip_id => $journey->id }
+ );
+
+ my $polyline;
+ if ( $journey->polyline ) {
+ my @station_list;
+ my @coordinate_list;
+ for my $coord ( $journey->polyline ) {
+ if ( $coord->{name} ) {
+ push(
+ @coordinate_list,
+ [
+ $coord->{lon}, $coord->{lat},
+ $coord->{eva}
+ ]
+ );
+ push( @station_list, $coord->{name} );
+ }
+ else {
+ push( @coordinate_list,
+ [ $coord->{lon}, $coord->{lat} ] );
+ }
+ }
+
+ # equal length → polyline only consists of straight
+ # lines between stops. that's not helpful.
+ if ( @station_list == @coordinate_list ) {
+ $self->log->debug( 'Ignoring polyline for '
+ . $journey->line
+ . ' as it only consists of straight lines between stops.'
+ );
+ }
+ else {
+ $polyline = {
+ from_eva => ( $journey->route )[0]->loc->eva,
+ to_eva => ( $journey->route )[-1]->loc->eva,
+ coords => \@coordinate_list,
+ };
+ }
+ }
+
+ if ($polyline) {
+ $self->in_transit->set_polyline(
+ uid => $uid,
+ db => $db,
+ polyline => $polyline,
+ );
+ }
+
+ # mustn't be called during a transaction
+ if ( not $opt{in_transaction} ) {
+ $self->run_hook( $uid, 'checkin' );
+ }
+
+ $promise->resolve($journey);
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
}
);
@@ -554,6 +684,26 @@ sub startup {
delete $journey->{edited};
delete $journey->{id};
+ # users may force checkouts at stations that are not part of
+ # the train's scheduled (or real-time) route. re-adding those
+ # to in-transit violates the assumption that each train has
+ # a valid destination. Remove the target in this case.
+ my $route = JSON->new->decode( $journey->{route} );
+ my $found_checkout_id;
+ for my $stop ( @{$route} ) {
+ if ( $stop->[1] == $journey->{checkout_station_id} ) {
+ $found_checkout_id = 1;
+ last;
+ }
+ }
+ if ( not $found_checkout_id ) {
+ $journey->{checkout_station_id} = undef;
+ $journey->{checkout_time} = undef;
+ $journey->{arr_platform} = undef;
+ $journey->{sched_arrival} = undef;
+ $journey->{real_arrival} = undef;
+ }
+
$self->in_transit->add_from_journey(
db => $db,
journey => $journey
@@ -587,27 +737,41 @@ sub startup {
);
$self->helper(
- 'checkout' => sub {
+ 'checkout_p' => sub {
my ( $self, %opt ) = @_;
- my $station = $opt{station};
- my $force = $opt{force};
- my $uid = $opt{uid};
- my $db = $opt{db} // $self->pg->db;
- my $status = $self->iris->get_departures(
- station => $station,
- lookbehind => 120,
- lookahead => 120
- );
- $uid //= $self->current_user->{id};
- my $user = $self->get_user_status( $uid, $db );
- my $train_id = $user->{train_id};
+ my $station = $opt{station};
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $with_related = $opt{with_related} // 0;
+ my $force = $opt{force};
+ my $uid = $opt{uid} // $self->current_user->{id};
+ my $db = $opt{db} // $self->pg->db;
+ my $user = $self->get_user_status( $uid, $db );
+ my $train_id = $user->{train_id};
+
+ my $promise = Mojo::Promise->new;
+
+ if ( not $station ) {
+ $self->app->log->error("Checkout($uid): station is empty");
+ return $promise->resolve( 1,
+ 'BUG: Checkout station is empty.' );
+ }
if ( not $user->{checked_in} and not $user->{cancelled} ) {
- return ( 0, 'You are not checked into any train' );
+ return $promise->resolve( 0,
+ 'You are not checked into any train' );
}
- if ( $status->{errstr} and not $force ) {
- return ( 1, $status->{errstr} );
+
+ if ( $dep_eva and $dep_eva != $user->{dep_eva} ) {
+ return $promise->resolve( 0, 'race condition' );
+ }
+ if ( $arr_eva and $arr_eva != $user->{arr_eva} ) {
+ return $promise->resolve( 0, 'race condition' );
+ }
+
+ if ( $train_id =~ m{[|]} ) {
+ return $self->_checkout_hafas_p(%opt);
}
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
@@ -616,151 +780,306 @@ sub startup {
with_data => 1
);
- # Note that a train may pass the same station several times.
- # Notable example: S41 / S42 ("Ringbahn") both starts and
- # terminates at Berlin Südkreuz
- my ($train) = List::Util::first {
- $_->train_id eq $train_id
- and $_->sched_arrival
- and $_->sched_arrival->epoch > $user->{sched_departure}->epoch
- }
- @{ $status->{results} };
-
- $train //= List::Util::first { $_->train_id eq $train_id }
- @{ $status->{results} };
-
- my $new_checkout_station_id = $status->{station_eva};
-
- # When a checkout is triggered by a checkin, there is an edge case
- # with related stations.
- # Assume a user travels from A to B1, then from B2 to C. B1 and B2 are
- # relatd stations (e.g. "Frankfurt Hbf" and "Frankfurt Hbf(tief)").
- # Now, if they check in for the journey from B2 to C, and have not yet
- # checked out of the previous train, $train is undef as B2 is not B1.
- # Redo the request with with_related => 1 to avoid this case.
- # While at it, we increase the lookahead to handle long journeys as
- # well.
- if ( not $train ) {
- $status = $self->iris->get_departures(
- station => $station,
- lookbehind => 120,
- lookahead => 180,
- with_related => 1
- );
- ($train) = List::Util::first { $_->train_id eq $train_id }
- @{ $status->{results} };
- if ( $train
- and $self->app->station_by_eva->{ $train->station_uic } )
- {
- $new_checkout_station_id = $train->station_uic;
- }
- }
+ $self->iris->get_departures_p(
+ station => $station,
+ lookbehind => 120,
+ lookahead => 180,
+ with_related => $with_related,
+ )->then(
+ sub {
+ my ($status) = @_;
+
+ my $new_checkout_station_id = $status->{station_eva};
+
+ # Store the intended checkout station regardless of this operation's
+ # success.
+ # TODO for with_related == 1, the correct EVA may be different
+ # and should be fetched from $train later on
+ $self->in_transit->set_arrival_eva(
+ uid => $uid,
+ db => $db,
+ arrival_eva => $new_checkout_station_id
+ );
- # Store the intended checkout station regardless of this operation's
- # success.
- $self->in_transit->set_arrival_eva(
- uid => $uid,
- db => $db,
- arrival_eva => $new_checkout_station_id
- );
+ # If in_transit already contains arrival data for another estimated
+ # destination, we must invalidate it.
+ if ( defined $journey->{checkout_station_id}
+ and $journey->{checkout_station_id}
+ != $new_checkout_station_id )
+ {
+ $self->in_transit->unset_arrival_data(
+ uid => $uid,
+ db => $db
+ );
+ }
- # If in_transit already contains arrival data for another estimated
- # destination, we must invalidate it.
- if ( defined $journey->{checkout_station_id}
- and $journey->{checkout_station_id}
- != $new_checkout_station_id )
- {
- $self->in_transit->unset_arrival_data(
- uid => $uid,
- db => $db
- );
- }
+ # Note that a train may pass the same station several times.
+ # Notable example: S41 / S42 ("Ringbahn") both starts and
+ # terminates at Berlin Südkreuz
+ my $train = List::Util::first {
+ $_->train_id eq $train_id
+ and $_->sched_arrival
+ and $_->sched_arrival->epoch
+ > $user->{sched_departure}->epoch
+ }
+ @{ $status->{results} };
- if ( not defined $train ) {
+ $train //= List::Util::first { $_->train_id eq $train_id }
+ @{ $status->{results} };
- # Arrival time via IRIS is unknown, so the train probably has not
- # arrived yet. Fall back to HAFAS.
- # TODO support cases where $station is EVA or DS100 code
- if (
- my $station_data
- = List::Util::first { $_->[0] eq $station }
- @{ $journey->{route} }
- )
- {
- $station_data = $station_data->[1];
- if ( $station_data->{sched_arr} ) {
- my $sched_arr
- = epoch_to_dt( $station_data->{sched_arr} );
- my $rt_arr = $sched_arr->clone;
- if ( $station_data->{adelay}
- and $station_data->{adelay} =~ m{^\d+$} )
+ if ( not defined $train ) {
+
+ # Arrival time via IRIS is unknown, so the train probably
+ # has not arrived yet. Fall back to HAFAS.
+ # TODO support cases where $station is EVA or DS100 code
+ if (
+ my $station_data
+ = List::Util::first { $_->[0] eq $station }
+ @{ $journey->{route} }
+ )
{
- $rt_arr->add( minutes => $station_data->{adelay} );
+ $station_data = $station_data->[2];
+ if ( $station_data->{sched_arr} ) {
+ my $sched_arr
+ = epoch_to_dt( $station_data->{sched_arr} );
+ my $rt_arr
+ = epoch_to_dt( $station_data->{rt_arr} );
+ if ( $rt_arr->epoch == 0 ) {
+ $rt_arr = $sched_arr->clone;
+ if ( $station_data->{arr_delay}
+ and $station_data->{arr_delay}
+ =~ m{^\d+$} )
+ {
+ $rt_arr->add( minutes =>
+ $station_data->{arr_delay} );
+ }
+ }
+ $self->in_transit->set_arrival_times(
+ uid => $uid,
+ db => $db,
+ sched_arrival => $sched_arr,
+ rt_arrival => $rt_arr
+ );
+ }
}
- $self->in_transit->set_arrival_times(
- uid => $uid,
- db => $db,
- sched_arrival => $sched_arr,
- rt_arrival => $rt_arr
+ if ( not $force ) {
+
+ # mustn't be called during a transaction
+ if ( not $opt{in_transaction} ) {
+ $self->run_hook( $uid, 'update' );
+ }
+ $promise->resolve( 1, undef );
+ return;
+ }
+ }
+ my $has_arrived = 0;
+
+ eval {
+
+ my $tx;
+ if ( not $opt{in_transaction} ) {
+ $tx = $db->begin;
+ }
+
+ if ( defined $train
+ and not $train->arrival
+ and not $force )
+ {
+ my $train_no = $train->train_no;
+ die("Train ${train_no} has no arrival timestamp\n");
+ }
+ elsif ( defined $train and $train->arrival ) {
+ $self->in_transit->set_arrival(
+ uid => $uid,
+ db => $db,
+ train => $train,
+ route => [ $self->iris->route_diff($train) ]
+ );
+
+ $has_arrived
+ = $train->arrival->epoch < $now->epoch ? 1 : 0;
+ if ($has_arrived) {
+ my @unknown_stations
+ = $self->stations->grep_unknown(
+ $train->route );
+ if (@unknown_stations) {
+ $self->app->log->warn(
+ sprintf(
+'Route of %s %s (%s -> %s) contains unknown stations: %s',
+ $train->type,
+ $train->train_no,
+ $train->origin,
+ $train->destination,
+ join( ', ', @unknown_stations )
+ )
+ );
+ }
+ }
+ }
+
+ $journey = $self->in_transit->get(
+ uid => $uid,
+ db => $db
);
+
+ if ( $has_arrived or $force ) {
+ $self->journeys->add_from_in_transit(
+ db => $db,
+ journey => $journey
+ );
+ $self->in_transit->delete(
+ uid => $uid,
+ db => $db
+ );
+
+ my $cache_ts = $now->clone;
+ if ( $journey->{real_departure}
+ =~ m{ ^ (?<year> \d{4} ) - (?<month> \d{2} ) }x
+ )
+ {
+ $cache_ts->set(
+ year => $+{year},
+ month => $+{month}
+ );
+ }
+ $self->journey_stats_cache->invalidate(
+ ts => $cache_ts,
+ db => $db,
+ uid => $uid
+ );
+ }
+ elsif ( defined $train
+ and $train->arrival_is_cancelled )
+ {
+
+ # This branch is only taken if the deparure was not cancelled,
+ # i.e., if the train was supposed to go here but got
+ # redirected or cancelled on the way and not from the start on.
+ # If the departure itself was cancelled, the user route is
+ # cancelled_from action -> 'cancelled journey' panel on main page
+ # -> cancelled_to action -> force checkout (causing the
+ # previous branch to be taken due to $force)
+ $journey->{cancelled} = 1;
+ $self->journeys->add_from_in_transit(
+ db => $db,
+ journey => $journey
+ );
+ $self->in_transit->set_cancelled_destination(
+ uid => $uid,
+ db => $db,
+ cancelled_destination => $train->station,
+ );
+ }
+
+ if ( not $opt{in_transaction} ) {
+ $tx->commit;
+ }
+ };
+
+ if ($@) {
+ $self->app->log->error("Checkout($uid): $@");
+ $promise->resolve( 1, 'Checkout error: ' . $@ );
+ return;
}
- }
- if ( not $force ) {
- # mustn't be called during a transaction
+ if ( $has_arrived or $force ) {
+ if ( not $opt{in_transaction} ) {
+ $self->run_hook( $uid, 'checkout' );
+ }
+ $promise->resolve( 0, undef );
+ return;
+ }
if ( not $opt{in_transaction} ) {
$self->run_hook( $uid, 'update' );
+ $self->add_route_timestamps( $uid, $train, 0, 1 );
}
- return ( 1, undef );
+ $promise->resolve( 1, undef );
+ return;
+
}
- }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $promise->resolve( 1, $err );
+ return;
+ }
+ )->wait;
- my $has_arrived = 0;
+ return $promise;
+ }
+ );
- eval {
+ $self->helper(
+ '_checkout_hafas_p' => sub {
+ my ( $self, %opt ) = @_;
- my $tx;
- if ( not $opt{in_transaction} ) {
- $tx = $db->begin;
- }
+ my $station = $opt{station};
+ my $force = $opt{force};
+ my $uid = $opt{uid} // $self->current_user->{id};
+ my $db = $opt{db} // $self->pg->db;
- if ( defined $train and not $train->arrival and not $force ) {
- my $train_no = $train->train_no;
- die("Train ${train_no} has no arrival timestamp\n");
- }
- elsif ( defined $train and $train->arrival ) {
- $self->in_transit->set_arrival(
- uid => $uid,
- db => $db,
- train => $train,
- route => [ $self->iris->route_diff($train) ]
- );
+ my $promise = Mojo::Promise->new;
- $has_arrived = $train->arrival->epoch < $now->epoch ? 1 : 0;
- if ($has_arrived) {
- my @unknown_stations
- = $self->grep_unknown_stations( $train->route );
- if (@unknown_stations) {
- $self->app->log->warn(
- sprintf(
-'Route of %s %s (%s -> %s) contains unknown stations: %s',
- $train->type,
- $train->train_no,
- $train->origin,
- $train->destination,
- join( ', ', @unknown_stations )
- )
- );
- }
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ my $journey = $self->in_transit->get(
+ uid => $uid,
+ db => $db,
+ with_data => 1,
+ with_timestamps => 1,
+ with_visibility => 1,
+ postprocess => 1,
+ );
+
+ # with_visibility needed due to postprocess
+
+ my $found;
+ my $has_arrived;
+ for my $stop ( @{ $journey->{route_after} } ) {
+ if ( $station eq $stop->[0] or $station eq $stop->[1] ) {
+ $found = 1;
+ $self->in_transit->set_arrival_eva(
+ uid => $uid,
+ db => $db,
+ arrival_eva => $stop->[1],
+ );
+ if ( defined $journey->{checkout_station_id}
+ and $journey->{checkout_station_id} != $stop->{eva} )
+ {
+ $self->in_transit->unset_arrival_data(
+ uid => $uid,
+ db => $db
+ );
+ }
+ $self->in_transit->set_arrival_times(
+ uid => $uid,
+ db => $db,
+ sched_arrival => $stop->[2]{sched_arr},
+ rt_arrival =>
+ ( $stop->[2]{rt_arr} || $stop->[2]{sched_arr} )
+ );
+ if (
+ $now > ( $stop->[2]{rt_arr} || $stop->[2]{sched_arr} ) )
+ {
+ $has_arrived = 1;
}
+ last;
}
+ }
+ if ( not $found ) {
+ return $promise->resolve( 1, 'station not found in route' );
+ }
- $journey = $self->in_transit->get(
- uid => $uid,
- db => $db
- );
+ eval {
+ my $tx;
+ if ( not $opt{in_transaction} ) {
+ $tx = $db->begin;
+ }
if ( $has_arrived or $force ) {
+ $journey = $self->in_transit->get(
+ uid => $uid,
+ db => $db
+ );
$self->journeys->add_from_in_transit(
db => $db,
journey => $journey
@@ -785,48 +1104,27 @@ sub startup {
uid => $uid
);
}
- elsif ( defined $train and $train->arrival_is_cancelled ) {
-
- # This branch is only taken if the deparure was not cancelled,
- # i.e., if the train was supposed to go here but got
- # redirected or cancelled on the way and not from the start on.
- # If the departure itself was cancelled, the user route is
- # cancelled_from action -> 'cancelled journey' panel on main page
- # -> cancelled_to action -> force checkout (causing the
- # previous branch to be taken due to $force)
- $journey->{cancelled} = 1;
- $self->journeys->add_from_in_transit(
- db => $db,
- journey => $journey
- );
- $self->in_transit->set_cancelled_destination(
- uid => $uid,
- db => $db,
- cancelled_destination => $train->station,
- );
- }
- if ( not $opt{in_transaction} ) {
+ if ($tx) {
$tx->commit;
}
};
if ($@) {
$self->app->log->error("Checkout($uid): $@");
- return ( 1, 'Checkout error: ' . $@ );
+ return $promise->resolve( 1, 'Checkout error: ' . $@ );
}
if ( $has_arrived or $force ) {
if ( not $opt{in_transaction} ) {
$self->run_hook( $uid, 'checkout' );
}
- return ( 0, undef );
+ return $promise->resolve( 0, undef );
}
if ( not $opt{in_transaction} ) {
$self->run_hook( $uid, 'update' );
- $self->add_route_timestamps( $uid, $train, 0 );
}
- return ( 1, undef );
+ return $promise->resolve( 1, undef );
}
);
@@ -839,92 +1137,7 @@ sub startup {
$uid //= $self->current_user->{id};
- return $self->users->get_data( uid => $uid );
- }
- );
-
- $self->helper(
- 'get_api_token' => sub {
- my ( $self, $uid ) = @_;
- $uid //= $self->current_user->{id};
-
- my $token = {};
- my $res = $self->pg->db->select(
- 'tokens',
- [ 'type', 'token' ],
- { user_id => $uid }
- );
-
- for my $entry ( $res->hashes->each ) {
- $token->{ $self->app->token_types->[ $entry->{type} - 1 ] }
- = $entry->{token};
- }
-
- return $token;
- }
- );
-
- $self->helper(
- 'get_webhook' => sub {
- my ( $self, $uid ) = @_;
- $uid //= $self->current_user->{id};
-
- my $res_h
- = $self->pg->db->select( 'webhooks_str', '*',
- { user_id => $uid } )->hash;
-
- $res_h->{latest_run} = epoch_to_dt( $res_h->{latest_run_ts} );
-
- return $res_h;
- }
- );
-
- $self->helper(
- 'set_webhook' => sub {
- my ( $self, %opt ) = @_;
-
- $opt{uid} //= $self->current_user->{id};
-
- if ( $opt{token} ) {
- $opt{token} =~ tr{\r\n}{}d;
- }
-
- my $res = $self->pg->db->insert(
- 'webhooks',
- {
- user_id => $opt{uid},
- enabled => $opt{enabled},
- url => $opt{url},
- token => $opt{token}
- },
- {
- on_conflict => \
-'(user_id) do update set enabled = EXCLUDED.enabled, url = EXCLUDED.url, token = EXCLUDED.token, errored = null, latest_run = null, output = null'
- }
- );
- }
- );
-
- $self->helper(
- 'mark_hook_status' => sub {
- my ( $self, $uid, $url, $success, $text ) = @_;
-
- if ( length($text) > 1000 ) {
- $text = substr( $text, 0, 1000 ) . '…';
- }
-
- $self->pg->db->update(
- 'webhooks',
- {
- errored => $success ? 0 : 1,
- latest_run => DateTime->now( time_zone => 'Europe/Berlin' ),
- output => $text,
- },
- {
- user_id => $uid,
- url => $url
- }
- );
+ return $self->users->get( uid => $uid );
}
);
@@ -932,7 +1145,7 @@ sub startup {
'run_hook' => sub {
my ( $self, $uid, $reason, $callback ) = @_;
- my $hook = $self->get_webhook($uid);
+ my $hook = $self->users->get_webhook( uid => $uid );
if ( not $hook->{enabled} or not $hook->{url} =~ m{^ https?:// }x )
{
@@ -942,7 +1155,7 @@ sub startup {
return;
}
- my $status = $self->get_user_status_json_v1($uid);
+ my $status = $self->get_user_status_json_v1( uid => $uid );
my $header = {};
my $hook_body = {
reason => $reason,
@@ -967,12 +1180,20 @@ sub startup {
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
- $self->mark_hook_status( $uid, $hook->{url}, 0,
- "HTTP $err->{code} $err->{message}" );
+ $self->users->update_webhook_status(
+ uid => $uid,
+ url => $hook->{url},
+ success => 0,
+ text => "HTTP $err->{code} $err->{message}"
+ );
}
else {
- $self->mark_hook_status( $uid, $hook->{url}, 1,
- $tx->result->body );
+ $self->users->update_webhook_status(
+ uid => $uid,
+ url => $hook->{url},
+ success => 1,
+ text => $tx->result->body
+ );
}
if ($callback) {
&$callback();
@@ -982,7 +1203,12 @@ sub startup {
)->catch(
sub {
my ($err) = @_;
- $self->mark_hook_status( $uid, $hook->{url}, 0, $err );
+ $self->users->update_webhook_status(
+ uid => $uid,
+ url => $hook->{url},
+ success => 0,
+ text => $err
+ );
if ($callback) {
&$callback();
}
@@ -992,165 +1218,34 @@ sub startup {
}
);
+ # This helper is only ever called from an IRIS context.
+ # HAFAS already has all relevant information.
$self->helper(
'add_route_timestamps' => sub {
- my ( $self, $uid, $train, $is_departure ) = @_;
+ my ( $self, $uid, $train, $is_departure, $update_polyline ) = @_;
$uid //= $self->current_user->{id};
my $db = $self->pg->db;
-# TODO "with_timestamps" is misleading, there are more differences between in_transit and in_transit_str
-# Here it's only needed because of dep_eva / arr_eva names
- my $journey = $self->in_transit->get(
+ # TODO "with_timestamps" is misleading, there are more differences between in_transit and in_transit_str
+ # Here it's only needed because of dep_eva / arr_eva names
+ my $in_transit = $self->in_transit->get(
db => $db,
uid => $uid,
with_data => 1,
with_timestamps => 1
);
- if ( not $journey ) {
+ if ( not $in_transit ) {
return;
}
- if ( $journey->{data}{trip_id}
- and not $journey->{polyline} )
- {
- my ( $origin_eva, $destination_eva, $polyline_str );
- $self->hafas->get_polyline_p( $train,
- $journey->{data}{trip_id} )->then(
- sub {
- my ($ret) = @_;
- my $polyline = $ret->{polyline};
- $origin_eva = 0 + $ret->{raw}{origin}{id};
- $destination_eva = 0 + $ret->{raw}{destination}{id};
-
- # work around Cache::File turning floats into strings
- for my $coord ( @{$polyline} ) {
- @{$coord} = map { 0 + $_ } @{$coord};
- }
-
- $polyline_str = JSON->new->encode($polyline);
-
- my $pl_res = $db->select(
- 'polylines',
- ['id'],
- {
- origin_eva => $origin_eva,
- destination_eva => $destination_eva,
- polyline => $polyline_str
- },
- { limit => 1 }
- );
-
- my $polyline_id;
- if ( my $h = $pl_res->hash ) {
- $polyline_id = $h->{id};
- }
- else {
- eval {
- $polyline_id = $db->insert(
- 'polylines',
- {
- origin_eva => $origin_eva,
- destination_eva => $destination_eva,
- polyline => $polyline_str
- },
- { returning => 'id' }
- )->hash->{id};
- };
- if ($@) {
- $self->app->log->warn(
- "add_route_timestamps: insert polyline: $@"
- );
- }
- }
- if ($polyline_id) {
- $self->in_transit->set_polyline_id(
- uid => $uid,
- db => $db,
- polyline_id => $polyline_id
- );
- }
- return;
- }
- )->catch(
- sub {
- my ($err) = @_;
- if ( $err =~ m{extra content at the end}i ) {
- $self->app->log->debug(
- "add_route_timestamps: $err");
- }
- else {
- $self->app->log->warn("add_route_timestamps: $err");
- }
- return;
- }
- )->wait;
- }
-
- my ($platform) = ( ( $train->platform // 0 ) =~ m{(\d+)} );
-
- my $route = $journey->{route};
+ my $route = $in_transit->{route};
- my $base
- = 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json.vs_hap&start=yes&rt=1';
- my $date_yy = $train->start->strftime('%d.%m.%y');
- my $date_yyyy = $train->start->strftime('%d.%m.%Y');
- my $train_no = $train->type . ' ' . $train->train_no;
-
- my ( $trainlink, $route_data );
-
- $self->hafas->get_json_p(
- "${base}&date=${date_yy}&trainname=${train_no}")->then(
+ $self->hafas->get_tripid_p( train => $train )->then(
sub {
- my ($trainsearch) = @_;
-
- # Fallback: Take first result
- my $result = $trainsearch->{suggestions}[0];
- $trainlink = $result->{trainLink};
-
- # Try finding a result for the current date
- for
- my $suggestion ( @{ $trainsearch->{suggestions} // [] } )
- {
-
- # Drunken API, sail with care. Both date formats are used interchangeably
- if (
- $suggestion->{depDate}
- and ( $suggestion->{depDate} eq $date_yy
- or $suggestion->{depDate} eq $date_yyyy )
- )
- {
- # Train numbers are not unique, e.g. IC 149 refers both to the
- # InterCity service Amsterdam -> Berlin and to the InterCity service
- # Koebenhavns Lufthavn st -> Aarhus. One workaround is making
- # requests with the stationFilter=80 parameter. Checking the origin
- # station seems to be the more generic solution, so we do that
- # instead.
- if ( $suggestion->{dep} eq $train->origin ) {
- $result = $suggestion;
- $trainlink = $suggestion->{trainLink};
- last;
- }
- }
- }
-
- if ( not $trainlink ) {
- $self->app->log->debug("trainlink not found");
- return Mojo::Promise->reject("trainlink not found");
- }
-
- # Calculate and store trip_id.
- # The trip_id's date part doesn't seem to matter -- so far,
- # HAFAS is happy as long as the date part starts with a number.
- # HAFAS-internal tripIDs use this format (withouth leading zero
- # for day of month < 10) though, so let's stick with it.
- my $date_map = $date_yyyy;
- $date_map =~ tr{.}{}d;
- my $trip_id = sprintf( '1|%d|%d|%d|%s',
- $result->{id}, $result->{cycle},
- $result->{pool}, $date_map );
+ my ($trip_id) = @_;
$self->in_transit->update_data(
uid => $uid,
@@ -1158,64 +1253,66 @@ sub startup {
data => { trip_id => $trip_id }
);
- my $base2
- = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
- return $self->hafas->get_json_p(
-"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_json.vs_hap"
+ return $self->hafas->get_route_timestamps_p(
+ train => $train,
+ trip_id => $trip_id,
+ with_polyline => (
+ $update_polyline
+ or not $in_transit->{polyline}
+ ) ? 1 : 0,
);
}
)->then(
sub {
- my ($traininfo) = @_;
- if ( not $traininfo or $traininfo->{error} ) {
- $self->app->log->debug("traininfo error");
- return Mojo::Promise->reject("traininfo error");
- }
- my $routeinfo
- = $traininfo->{suggestions}[0]{locations};
+ my ( $route_data, $journey, $polyline ) = @_;
- my $strp = DateTime::Format::Strptime->new(
- pattern => '%d.%m.%y %H:%M',
- time_zone => 'Europe/Berlin',
- );
-
- $route_data = {};
-
- for my $station ( @{$routeinfo} ) {
- my $arr
- = $strp->parse_datetime(
- $station->{arrDate} . ' ' . $station->{arrTime} );
- my $dep
- = $strp->parse_datetime(
- $station->{depDate} . ' ' . $station->{depTime} );
- $route_data->{ $station->{name} } = {
- sched_arr => $arr ? $arr->epoch : 0,
- sched_dep => $dep ? $dep->epoch : 0,
- };
- }
-
- my $base2
- = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
- return $self->hafas->get_xml_p(
- "${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3"
- );
- }
- )->then(
- sub {
- my ($traininfo2) = @_;
-
- for my $station ( keys %{$route_data} ) {
- for my $key (
- keys %{ $traininfo2->{station}{$station} // {} } )
+ for my $station ( @{$route} ) {
+ if ( $station->[0]
+ =~ m{^Betriebsstelle nicht bekannt (\d+)$} )
{
- $route_data->{$station}{$key}
- = $traininfo2->{station}{$station}{$key};
+ 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;
+ }
+
+ # 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};
+ }
+ if ( not $station->[2]{rt_dep} ) {
+ $station->[2]{rt_dep} = $old{rt_dep};
+ $station->[2]{dep_delay} = $old{dep_delay};
+ }
}
}
- for my $station ( @{$route} ) {
- $station->[1]
- = $route_data->{ $station->[0] };
+ my @messages;
+ for my $m ( $journey->messages ) {
+ if ( not $m->code ) {
+ push(
+ @messages,
+ {
+ header => $m->short,
+ lead => $m->text,
+ }
+ );
+ }
}
$self->in_transit->set_route_data(
@@ -1230,21 +1327,24 @@ sub startup {
map { [ $_->[0]->epoch, $_->[1] ] }
$train->qos_messages
],
- him_messages => $traininfo2->{messages},
+ him_messages => \@messages,
);
+
+ if ($polyline) {
+ $self->in_transit->set_polyline(
+ uid => $uid,
+ db => $db,
+ polyline => $polyline,
+ old_id => $in_transit->{polyline_id},
+ );
+ }
+
return;
}
)->catch(
sub {
my ($err) = @_;
- if ( $err
- =~ m{trainlink not found|extra content at the end}i )
- {
- $self->app->log->debug("add_route_timestamps: $err");
- }
- else {
- $self->app->log->warn("add_route_timestamps: $err");
- }
+ $self->app->log->debug("add_route_timestamps: $err");
return;
}
)->wait;
@@ -1282,7 +1382,7 @@ sub startup {
push(
@wagons,
{
- id => $wagon->{fahrzeugnummer},
+ id => $wagon->{fahrzeugnummer},
number =>
$wagon->{wagenordnungsnummer},
type => $wagon->{fahrzeugtyp},
@@ -1301,6 +1401,12 @@ sub startup {
wagons => [@wagons],
}
);
+ if ( $group->{fahrzeuggruppebezeichnung}
+ and $group->{fahrzeuggruppebezeichnung} eq
+ 'ICE0304' )
+ {
+ $data->{wagonorder_pride} = 1;
+ }
}
$self->in_transit->update_data(
uid => $uid,
@@ -1334,7 +1440,7 @@ sub startup {
}
if ($is_departure) {
- $self->dbdb->get_stationinfo_p( $journey->{dep_eva} )->then(
+ $self->dbdb->get_stationinfo_p( $in_transit->{dep_eva} )->then(
sub {
my ($station_info) = @_;
my $data = { stationinfo_dep => $station_info };
@@ -1354,8 +1460,8 @@ sub startup {
)->wait;
}
- if ( $journey->{arr_eva} and not $is_departure ) {
- $self->dbdb->get_stationinfo_p( $journey->{arr_eva} )->then(
+ if ( $in_transit->{arr_eva} and not $is_departure ) {
+ $self->dbdb->get_stationinfo_p( $in_transit->{arr_eva} )->then(
sub {
my ($station_info) = @_;
my $data = { stationinfo_arr => $station_info };
@@ -1378,249 +1484,16 @@ sub startup {
);
$self->helper(
- 'get_latest_dest_id' => sub {
- my ( $self, %opt ) = @_;
-
- my $uid = $opt{uid} // $self->current_user->{id};
- my $db = $opt{db} // $self->pg->db;
-
- if (
- my $id = $self->in_transit->get_checkout_station_id(
- uid => $uid,
- db => $db
- )
- )
- {
- return $id;
- }
-
- return $self->journeys->get_latest_checkout_station_id(
- uid => $uid,
- db => $db
- );
- }
- );
-
- $self->helper(
- 'get_connection_targets' => sub {
- my ( $self, %opt ) = @_;
-
- my $uid = $opt{uid} //= $self->current_user->{id};
- my $threshold = $opt{threshold}
- // DateTime->now( time_zone => 'Europe/Berlin' )
- ->subtract( months => 4 );
- my $db = $opt{db} //= $self->pg->db;
- my $min_count = $opt{min_count} // 3;
-
- if ( $opt{destination_name} ) {
- return ( $opt{destination_name} );
- }
-
- my $dest_id = $opt{eva} // $self->get_latest_dest_id(%opt);
-
- if ( not $dest_id ) {
- return;
- }
-
- my $res = $db->query(
- qq{
- select
- count(checkout_station_id) as count,
- checkout_station_id as dest
- from journeys
- where user_id = ?
- and checkin_station_id = ?
- and real_departure > ?
- group by checkout_station_id
- order by count desc;
- },
- $uid,
- $dest_id,
- $threshold
- );
- my @destinations
- = $res->hashes->grep( sub { shift->{count} >= $min_count } )
- ->map( sub { shift->{dest} } )->each;
- @destinations
- = grep { $self->app->station_by_eva->{$_} } @destinations;
- @destinations
- = map { $self->app->station_by_eva->{$_}->[1] } @destinations;
- return @destinations;
- }
- );
-
- $self->helper(
- 'get_connecting_trains' => sub {
- my ( $self, %opt ) = @_;
-
- my $uid = $opt{uid} //= $self->current_user->{id};
- my $use_history = $self->users->use_history( uid => $uid );
-
- my ( $eva, $exclude_via, $exclude_train_id, $exclude_before );
- my $now = $self->now->epoch;
- my ( $stationinfo, $arr_epoch, $arr_platform );
-
- if ( $opt{eva} ) {
- if ( $use_history & 0x01 ) {
- $eva = $opt{eva};
- }
- elsif ( $opt{destination_name} ) {
- $eva = $opt{eva};
- }
- }
- else {
- if ( $use_history & 0x02 ) {
- my $status = $self->get_user_status;
- $eva = $status->{arr_eva};
- $exclude_via = $status->{dep_name};
- $exclude_train_id = $status->{train_id};
- $arr_platform = $status->{arr_platform};
- $stationinfo = $status->{extra_data}{stationinfo_arr};
- if ( $status->{real_arrival} ) {
- $exclude_before = $arr_epoch
- = $status->{real_arrival}->epoch;
- }
- }
- }
-
- $exclude_before //= $now - 300;
-
- if ( not $eva ) {
- return;
- }
-
- my @destinations = $self->get_connection_targets(%opt);
-
- if ($exclude_via) {
- @destinations = grep { $_ ne $exclude_via } @destinations;
- }
-
- if ( not @destinations ) {
- return;
- }
-
- my $stationboard = $self->iris->get_departures(
- station => $eva,
- lookbehind => 10,
- lookahead => 40,
- with_related => 1
- );
- if ( $stationboard->{errstr} ) {
- return;
- }
- @{ $stationboard->{results} } = map { $_->[0] }
- sort { $a->[1] <=> $b->[1] }
- map { [ $_, $_->departure ? $_->departure->epoch : 0 ] }
- @{ $stationboard->{results} };
- my @results;
- my @cancellations;
- my %via_count = map { $_ => 0 } @destinations;
- for my $train ( @{ $stationboard->{results} } ) {
- if ( not $train->departure ) {
- next;
- }
- if ( $exclude_before
- and $train->departure
- and $train->departure->epoch < $exclude_before )
- {
- next;
- }
- if ( $exclude_train_id
- and $train->train_id eq $exclude_train_id )
- {
- next;
- }
-
- # In general, this function is meant to return feasible
- # connections. However, cancelled connections may also be of
- # interest and are also useful for logging cancellations.
- # To satisfy both demands with (hopefully) little confusion and
- # UI clutter, this function returns two concatenated arrays:
- # actual connections (ordered by actual departure time) followed
- # by cancelled connections (ordered by scheduled departure time).
- # This is easiest to achieve in two separate loops.
- #
- # Note that a cancelled train may still have a matching destination
- # in its route_post, e.g. if it leaves out $eva due to
- # unscheduled route changes but continues on schedule afterwards
- # -- so it is only cancelled at $eva, not on the remainder of
- # the route. Also note that this specific case is not yet handled
- # properly by the cancellation logic etc.
-
- if ( $train->departure_is_cancelled ) {
- my @via
- = ( $train->sched_route_post, $train->sched_route_end );
- for my $dest (@destinations) {
- if ( List::Util::any { $_ eq $dest } @via ) {
- push( @cancellations, [ $train, $dest ] );
- next;
- }
- }
- }
- else {
- my @via = ( $train->route_post, $train->route_end );
- for my $dest (@destinations) {
- if ( $via_count{$dest} < 2
- and List::Util::any { $_ eq $dest } @via )
- {
- push( @results, [ $train, $dest ] );
-
- # Show all past and up to two future departures per destination
- if ( not $train->departure
- or $train->departure->epoch >= $now )
- {
- $via_count{$dest}++;
- }
- next;
- }
- }
- }
- }
-
- @results = map { $_->[0] }
- sort { $a->[1] <=> $b->[1] }
- map {
- [
- $_,
- $_->[0]->departure->epoch // $_->[0]->sched_departure->epoch
- ]
- } @results;
- @cancellations = map { $_->[0] }
- sort { $a->[1] <=> $b->[1] }
- map { [ $_, $_->[0]->sched_departure->epoch ] } @cancellations;
-
- for my $result (@results) {
- my $train = $result->[0];
- my @message_ids
- = List::Util::uniq map { $_->[1] } $train->raw_messages;
- $train->{message_id} = { map { $_ => 1 } @message_ids };
- my $interchange_duration;
- if ( exists $stationinfo->{i} ) {
- $interchange_duration
- = $stationinfo->{i}{$arr_platform}{ $train->platform };
- $interchange_duration //= $stationinfo->{i}{"*"};
- }
- if ( defined $interchange_duration ) {
- my $interchange_time
- = ( $train->departure->epoch - $arr_epoch ) / 60;
- if ( $interchange_time < $interchange_duration ) {
- $train->{interchange_text} = 'Anschluss knapp';
- $train->{interchange_icon} = 'warning';
- }
- elsif ( $interchange_time == $interchange_duration ) {
- $train->{interchange_text}
- = 'Anschluss könnte knapp werden';
- $train->{interchange_icon} = 'directions_run';
- }
-
- #else {
- # $train->{interchange_text} = 'Anschluss wird voraussichtlich erreicht';
- # $train->{interchange_icon} = 'check';
- #}
- }
- }
-
- return ( @results, @cancellations );
+ 'resolve_sb_template' => sub {
+ my ( $self, $template, %opt ) = @_;
+ my $ret = $template;
+ my $name = $opt{name} =~ s{/}{%2F}gr;
+ $ret =~ s{[{]eva[}]}{$opt{eva}}g;
+ $ret =~ s{[{]name[}]}{$name}g;
+ $ret =~ s{[{]tt[}]}{$opt{tt}}g;
+ $ret =~ s{[{]tn[}]}{$opt{tn}}g;
+ $ret =~ s{[{]id[}]}{$opt{id}}g;
+ return $ret;
}
);
@@ -1688,21 +1561,17 @@ sub startup {
for my $station ( @{ $journey->{route_after} } ) {
my $station_desc = $station->[0];
- if ( $station->[1]{rt_arr} ) {
- $station_desc .= $station->[1]{sched_arr}->strftime(';%s');
- $station_desc .= $station->[1]{rt_arr}->strftime(';%s');
- if ( $station->[1]{rt_dep} ) {
- $station_desc
- .= $station->[1]{sched_dep}->strftime(';%s');
- $station_desc .= $station->[1]{rt_dep}->strftime(';%s');
- }
- else {
- $station_desc .= ';0;0';
- }
- }
- else {
- $station_desc .= ';0;0;0;0';
- }
+
+ my $sa = $station->[2]{sched_arr};
+ my $ra = $station->[2]{rt_arr} || $station->[2]{sched_arr};
+ my $sd = $station->[2]{sched_dep};
+ my $rd = $station->[2]{rt_dep} || $station->[2]{sched_dep};
+
+ $station_desc .= $sa ? $sa->strftime(';%s') : ';0';
+ $station_desc .= $ra ? $ra->strftime(';%s') : ';0';
+ $station_desc .= $sd ? $sd->strftime(';%s') : ';0';
+ $station_desc .= $rd ? $rd->strftime(';%s') : ';0';
+
push( @route, $station_desc );
}
@@ -1724,157 +1593,75 @@ sub startup {
uid => $uid,
db => $db,
with_data => 1,
- with_timestamps => 1
+ with_timestamps => 1,
+ with_visibility => 1,
+ postprocess => 1,
);
if ($in_transit) {
+ my $ret = $in_transit;
- if ( my $station
- = $self->app->station_by_eva->{ $in_transit->{dep_eva} } )
- {
- $in_transit->{dep_ds100} = $station->[0];
- $in_transit->{dep_name} = $station->[1];
- }
- if ( $in_transit->{arr_eva}
- and my $station
- = $self->app->station_by_eva->{ $in_transit->{arr_eva} } )
+ my $traewelling = $self->traewelling->get(
+ uid => $uid,
+ db => $db
+ );
+ if ( $traewelling->{latest_run}
+ >= epoch_to_dt( $in_transit->{checkin_ts} ) )
{
- $in_transit->{arr_ds100} = $station->[0];
- $in_transit->{arr_name} = $station->[1];
- }
-
- my @route = @{ $in_transit->{route} // [] };
- my @route_after;
- my $dep_info;
- my $stop_before_dest;
- my $is_after = 0;
- for my $station (@route) {
-
- if ( $in_transit->{arr_name}
- and @route_after
- and $station->[0] eq $in_transit->{arr_name} )
- {
- $stop_before_dest = $route_after[-1][0];
- }
- if ($is_after) {
- push( @route_after, $station );
- }
- if ( $in_transit->{dep_name}
- and $station->[0] eq $in_transit->{dep_name} )
+ $ret->{traewelling} = $traewelling;
+ if ( @{ $traewelling->{data}{log} // [] }
+ and ( my $log_entry = $traewelling->{data}{log}[0] ) )
{
- $is_after = 1;
- if ( @{$station} > 1 and not $dep_info ) {
- $dep_info = $station->[1];
+ if ( $log_entry->[2] ) {
+ $ret->{traewelling_status} = $log_entry->[2];
+ $ret->{traewelling_url}
+ = 'https://traewelling.de/status/'
+ . $log_entry->[2];
}
+ $ret->{traewelling_log_latest} = $log_entry->[1];
}
}
- my $stop_after_dep = @route_after ? $route_after[0][0] : undef;
-
- my $ts = $in_transit->{checkout_ts}
- // $in_transit->{checkin_ts};
- my $action_time = epoch_to_dt($ts);
-
- my $ret = {
- checked_in => !$in_transit->{cancelled},
- cancelled => $in_transit->{cancelled},
- timestamp => $action_time,
- timestamp_delta => $now->epoch - $action_time->epoch,
- train_type => $in_transit->{train_type},
- train_line => $in_transit->{train_line},
- train_no => $in_transit->{train_no},
- train_id => $in_transit->{train_id},
- boarding_countdown => -1,
- sched_departure =>
- epoch_to_dt( $in_transit->{sched_dep_ts} ),
- real_departure => epoch_to_dt( $in_transit->{real_dep_ts} ),
- dep_ds100 => $in_transit->{dep_ds100},
- dep_eva => $in_transit->{dep_eva},
- dep_name => $in_transit->{dep_name},
- dep_platform => $in_transit->{dep_platform},
- sched_arrival => epoch_to_dt( $in_transit->{sched_arr_ts} ),
- real_arrival => epoch_to_dt( $in_transit->{real_arr_ts} ),
- arr_ds100 => $in_transit->{arr_ds100},
- arr_eva => $in_transit->{arr_eva},
- arr_name => $in_transit->{arr_name},
- arr_platform => $in_transit->{arr_platform},
- route_after => \@route_after,
- messages => $in_transit->{messages},
- extra_data => $in_transit->{data},
- comment => $in_transit->{user_data}{comment},
- };
-
- my @parsed_messages;
- for my $message ( @{ $ret->{messages} // [] } ) {
- my ( $ts, $msg ) = @{$message};
- push( @parsed_messages, [ epoch_to_dt($ts), $msg ] );
- }
- $ret->{messages} = [ reverse @parsed_messages ];
- @parsed_messages = ();
- for my $message ( @{ $ret->{extra_data}{qos_msg} // [] } ) {
- my ( $ts, $msg ) = @{$message};
- push( @parsed_messages, [ epoch_to_dt($ts), $msg ] );
- }
- $ret->{extra_data}{qos_msg} = [@parsed_messages];
-
- if ( $dep_info and $dep_info->{sched_arr} ) {
- $dep_info->{sched_arr}
- = epoch_to_dt( $dep_info->{sched_arr} );
- $dep_info->{rt_arr} = $dep_info->{sched_arr}->clone;
- if ( $dep_info->{adelay}
- and $dep_info->{adelay} =~ m{^\d+$} )
+ my $stop_after_dep
+ = scalar @{ $ret->{route_after} }
+ ? $ret->{route_after}[0][0]
+ : undef;
+ my $stop_before_dest;
+ for my $i ( 1 .. $#{ $ret->{route_after} } ) {
+ if ( $ret->{arr_name}
+ and $ret->{route_after}[$i][0] eq $ret->{arr_name} )
{
- $dep_info->{rt_arr}
- ->add( minutes => $dep_info->{adelay} );
+ $stop_before_dest = $ret->{route_after}[ $i - 1 ][0];
+ last;
}
- $dep_info->{rt_arr_countdown} = $ret->{boarding_countdown}
- = $dep_info->{rt_arr}->epoch - $epoch;
}
- for my $station (@route_after) {
- if ( @{$station} > 1 ) {
-
- # Note: $station->[1]{sched_arr} may already have been
- # converted to a DateTime object in $station->[1] is
- # $dep_info. This can happen when a station is present
- # several times in a train's route, e.g. for Frankfurt
- # Flughafen in some nightly connections.
- my $times = $station->[1];
- if ( $times->{sched_arr}
- and ref( $times->{sched_arr} ) ne 'DateTime' )
- {
- $times->{sched_arr}
- = epoch_to_dt( $times->{sched_arr} );
- $times->{rt_arr} = $times->{sched_arr}->clone;
- if ( $times->{adelay}
- and $times->{adelay} =~ m{^\d+$} )
- {
- $times->{rt_arr}
- ->add( minutes => $times->{adelay} );
- }
- $times->{rt_arr_countdown}
- = $times->{rt_arr}->epoch - $epoch;
- }
- if ( $times->{sched_dep}
- and ref( $times->{sched_dep} ) ne 'DateTime' )
- {
- $times->{sched_dep}
- = epoch_to_dt( $times->{sched_dep} );
- $times->{rt_dep} = $times->{sched_dep}->clone;
- if ( $times->{ddelay}
- and $times->{ddelay} =~ m{^\d+$} )
- {
- $times->{rt_dep}
- ->add( minutes => $times->{ddelay} );
- }
- $times->{rt_dep_countdown}
- = $times->{rt_dep}->epoch - $epoch;
- }
- }
+ my ($dep_platform_number)
+ = ( ( $ret->{dep_platform} // 0 ) =~ m{(\d+)} );
+ if ( $dep_platform_number
+ and
+ exists $ret->{data}{stationinfo_dep}{$dep_platform_number} )
+ {
+ $ret->{dep_direction} = $self->stationinfo_to_direction(
+ $ret->{data}{stationinfo_dep}{$dep_platform_number},
+ $ret->{data}{wagonorder_dep},
+ undef, $stop_after_dep
+ );
}
- $ret->{departure_countdown}
- = $ret->{real_departure}->epoch - $now->epoch;
+ my ($arr_platform_number)
+ = ( ( $ret->{arr_platform} // 0 ) =~ m{(\d+)} );
+ if ( $arr_platform_number
+ and
+ exists $ret->{data}{stationinfo_arr}{$arr_platform_number} )
+ {
+ $ret->{arr_direction} = $self->stationinfo_to_direction(
+ $ret->{data}{stationinfo_arr}{$arr_platform_number},
+ $ret->{data}{wagonorder_arr},
+ $stop_before_dest,
+ undef
+ );
+ }
if ( $ret->{departure_countdown} > 0
and $in_transit->{data}{wagonorder_dep} )
@@ -1893,85 +1680,32 @@ sub startup {
}
}
- if ( $in_transit->{real_arr_ts} ) {
- $ret->{arrival_countdown}
- = $ret->{real_arrival}->epoch - $now->epoch;
- $ret->{journey_duration}
- = $ret->{real_arrival}->epoch
- - $ret->{real_departure}->epoch;
- $ret->{journey_completion}
- = $ret->{journey_duration}
- ? 1
- - ( $ret->{arrival_countdown} / $ret->{journey_duration} )
- : 1;
- if ( $ret->{journey_completion} > 1 ) {
- $ret->{journey_completion} = 1;
- }
- elsif ( $ret->{journey_completion} < 0 ) {
- $ret->{journey_completion} = 0;
- }
-
- my ($dep_platform_number)
- = ( ( $ret->{dep_platform} // 0 ) =~ m{(\d+)} );
- if ( $dep_platform_number
- and exists $in_transit->{data}{stationinfo_dep}
- {$dep_platform_number} )
- {
- $ret->{dep_direction}
- = $self->stationinfo_to_direction(
- $in_transit->{data}{stationinfo_dep}
- {$dep_platform_number},
- $in_transit->{data}{wagonorder_dep},
- undef,
- $stop_after_dep
- );
- }
-
- my ($arr_platform_number)
- = ( ( $ret->{arr_platform} // 0 ) =~ m{(\d+)} );
- if ( $arr_platform_number
- and exists $in_transit->{data}{stationinfo_arr}
- {$arr_platform_number} )
- {
- $ret->{arr_direction}
- = $self->stationinfo_to_direction(
- $in_transit->{data}{stationinfo_arr}
- {$arr_platform_number},
- $in_transit->{data}{wagonorder_arr},
- $stop_before_dest,
- undef
- );
- }
-
- }
- else {
- $ret->{arrival_countdown} = undef;
- $ret->{journey_duration} = undef;
- $ret->{journey_completion} = undef;
- }
-
return $ret;
}
my ( $latest, $latest_cancellation ) = $self->journeys->get_latest(
uid => $uid,
- db => $db
+ db => $db,
);
if ( $latest_cancellation and $latest_cancellation->{cancelled} ) {
- if ( my $station
- = $self->app->station_by_eva
- ->{ $latest_cancellation->{dep_eva} } )
+ if (
+ my $station = $self->stations->get_by_eva(
+ $latest_cancellation->{dep_eva}
+ )
+ )
{
- $latest_cancellation->{dep_ds100} = $station->[0];
- $latest_cancellation->{dep_name} = $station->[1];
+ $latest_cancellation->{dep_ds100} = $station->{ds100};
+ $latest_cancellation->{dep_name} = $station->{name};
}
- if ( my $station
- = $self->app->station_by_eva
- ->{ $latest_cancellation->{arr_eva} } )
+ if (
+ my $station = $self->stations->get_by_eva(
+ $latest_cancellation->{arr_eva}
+ )
+ )
{
- $latest_cancellation->{arr_ds100} = $station->[0];
- $latest_cancellation->{arr_name} = $station->[1];
+ $latest_cancellation->{arr_ds100} = $station->{ds100};
+ $latest_cancellation->{arr_name} = $station->{name};
}
}
else {
@@ -1982,16 +1716,16 @@ sub startup {
my $ts = $latest->{checkout_ts};
my $action_time = epoch_to_dt($ts);
if ( my $station
- = $self->app->station_by_eva->{ $latest->{dep_eva} } )
+ = $self->stations->get_by_eva( $latest->{dep_eva} ) )
{
- $latest->{dep_ds100} = $station->[0];
- $latest->{dep_name} = $station->[1];
+ $latest->{dep_ds100} = $station->{ds100};
+ $latest->{dep_name} = $station->{name};
}
if ( my $station
- = $self->app->station_by_eva->{ $latest->{arr_eva} } )
+ = $self->stations->get_by_eva( $latest->{arr_eva} ) )
{
- $latest->{arr_ds100} = $station->[0];
- $latest->{arr_name} = $station->[1];
+ $latest->{arr_ds100} = $station->{ds100};
+ $latest->{arr_name} = $station->{name};
}
return {
checked_in => 0,
@@ -2009,14 +1743,23 @@ sub startup {
dep_ds100 => $latest->{dep_ds100},
dep_eva => $latest->{dep_eva},
dep_name => $latest->{dep_name},
+ dep_lat => $latest->{dep_lat},
+ dep_lon => $latest->{dep_lon},
dep_platform => $latest->{dep_platform},
sched_arrival => epoch_to_dt( $latest->{sched_arr_ts} ),
real_arrival => epoch_to_dt( $latest->{real_arr_ts} ),
arr_ds100 => $latest->{arr_ds100},
arr_eva => $latest->{arr_eva},
arr_name => $latest->{arr_name},
+ arr_lat => $latest->{arr_lat},
+ arr_lon => $latest->{arr_lon},
arr_platform => $latest->{arr_platform},
comment => $latest->{user_data}{comment},
+ visibility => $latest->{visibility},
+ visibility_str => $latest->{visibility_str},
+ effective_visibility => $latest->{effective_visibility},
+ effective_visibility_str =>
+ $latest->{effective_visibility_str},
};
}
@@ -2033,10 +1776,11 @@ sub startup {
$self->helper(
'get_user_status_json_v1' => sub {
- my ( $self, $uid ) = @_;
- my $status = $self->get_user_status($uid);
-
- # TODO simplify lon/lat (can be returned from get_user_status)
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $privacy = $opt{privacy}
+ // $self->users->get_privacy_by( uid => $uid );
+ my $status = $opt{status} // $self->get_user_status($uid);
my $ret = {
deprecated => \0,
@@ -2044,12 +1788,13 @@ sub startup {
$status->{checked_in}
or $status->{cancelled}
) ? \1 : \0,
+ comment => $status->{comment},
fromStation => {
ds100 => $status->{dep_ds100},
name => $status->{dep_name},
uic => $status->{dep_eva},
- longitude => undef,
- latitude => undef,
+ longitude => $status->{dep_lon},
+ latitude => $status->{dep_lat},
scheduledTime => $status->{sched_departure}
? $status->{sched_departure}->epoch
: undef,
@@ -2061,8 +1806,8 @@ sub startup {
ds100 => $status->{arr_ds100},
name => $status->{arr_name},
uic => $status->{arr_eva},
- longitude => undef,
- latitude => undef,
+ longitude => $status->{arr_lon},
+ latitude => $status->{arr_lat},
scheduledTime => $status->{sched_arrival}
? $status->{sched_arrival}->epoch
: undef,
@@ -2071,17 +1816,31 @@ sub startup {
: undef,
},
train => {
- type => $status->{train_type},
- line => $status->{train_line},
- no => $status->{train_no},
- id => $status->{train_id},
+ type => $status->{train_type},
+ line => $status->{train_line},
+ no => $status->{train_no},
+ id => $status->{train_id},
+ hafasId => $status->{extra_data}{trip_id},
},
- actionTime => $status->{timestamp}
- ? $status->{timestamp}->epoch
- : undef,
intermediateStops => [],
+ visibility => {
+ level => $status->{effective_visibility},
+ desc => $status->{effective_visibility_str},
+ }
};
+ if ( $opt{public} ) {
+ if ( not $privacy->{comments_visible} ) {
+ delete $ret->{comment};
+ }
+ }
+ else {
+ $ret->{actionTime}
+ = $status->{timestamp}
+ ? $status->{timestamp}->epoch
+ : undef;
+ }
+
for my $stop ( @{ $status->{route_after} // [] } ) {
if ( $status->{arr_name} and $stop->[0] eq $status->{arr_name} )
{
@@ -2091,64 +1850,40 @@ sub startup {
@{ $ret->{intermediateStops} },
{
name => $stop->[0],
- scheduledArrival => $stop->[1]{sched_arr}
- ? $stop->[1]{sched_arr}->epoch
+ scheduledArrival => $stop->[2]{sched_arr}
+ ? $stop->[2]{sched_arr}->epoch
: undef,
- realArrival => $stop->[1]{rt_arr}
- ? $stop->[1]{rt_arr}->epoch
+ realArrival => $stop->[2]{rt_arr}
+ ? $stop->[2]{rt_arr}->epoch
: undef,
- scheduledDeparture => $stop->[1]{sched_dep}
- ? $stop->[1]{sched_dep}->epoch
+ scheduledDeparture => $stop->[2]{sched_dep}
+ ? $stop->[2]{sched_dep}->epoch
: undef,
- realDeparture => $stop->[1]{rt_dep}
- ? $stop->[1]{rt_dep}->epoch
+ realDeparture => $stop->[2]{rt_dep}
+ ? $stop->[2]{rt_dep}->epoch
: undef,
}
);
}
- if ( $status->{dep_eva} ) {
- my @station_descriptions
- = Travel::Status::DE::IRIS::Stations::get_station(
- $status->{dep_eva} );
- if ( @station_descriptions == 1 ) {
- (
- undef, undef, undef,
- $ret->{fromStation}{longitude},
- $ret->{fromStation}{latitude}
- ) = @{ $station_descriptions[0] };
- }
- }
-
- if ( $status->{arr_ds100} ) {
- my @station_descriptions
- = Travel::Status::DE::IRIS::Stations::get_station(
- $status->{arr_ds100} );
- if ( @station_descriptions == 1 ) {
- (
- undef, undef, undef,
- $ret->{toStation}{longitude},
- $ret->{toStation}{latitude}
- ) = @{ $station_descriptions[0] };
- }
- }
-
return $ret;
}
);
$self->helper(
- 'traewelling_to_travelynx' => sub {
+ 'traewelling_to_travelynx_p' => sub {
my ( $self, %opt ) = @_;
my $traewelling = $opt{traewelling};
my $user_data = $opt{user_data};
my $uid = $user_data->{user_id};
+ my $promise = Mojo::Promise->new;
+
if ( not $traewelling->{checkin}
or $self->now->epoch - $traewelling->{checkin}->epoch > 900 )
{
$self->log->debug("... not checked in");
- return;
+ return $promise->resolve;
}
if ( $traewelling->{status_id}
and $user_data->{data}{latest_pull_status_id}
@@ -2156,106 +1891,61 @@ sub startup {
== $user_data->{data}{latest_pull_status_id} )
{
$self->log->debug("... already handled");
- return;
+ return $promise->resolve;
}
- $self->log->debug("... checked in");
+ $self->log->debug(
+"... checked in : $traewelling->{dep_name} $traewelling->{dep_eva} -> $traewelling->{arr_name} $traewelling->{arr_eva}"
+ );
my $user_status = $self->get_user_status($uid);
if ( $user_status->{checked_in} ) {
$self->log->debug(
"... also checked in via travelynx. aborting.");
- return;
+ return $promise->resolve;
}
if ( $traewelling->{category}
!~ m{^ (?: national .* | regional .* | suburban ) $ }x )
{
- $self->log->debug(
- "... status is not a train, but $traewelling->{category}");
- $self->traewelling->log(
- uid => $uid,
- message =>
-"$traewelling->{line} nach $traewelling->{arr_name} ist keine Zugfahrt (HAFAS-Kategorie '$traewelling->{category}')",
- status_id => $traewelling->{status_id},
- );
- $self->traewelling->set_latest_pull_status_id(
- uid => $uid,
- status_id => $traewelling->{status_id}
- );
- return;
- }
-
- my $dep = $self->iris->get_departures(
- station => $traewelling->{dep_eva},
- lookbehind => 60,
- lookahead => 40
- );
- if ( $dep->{errstr} ) {
- $self->traewelling->log(
- uid => $uid,
- message =>
-"Fehler bei $traewelling->{line} nach $traewelling->{arr_name}: $dep->{errstr}",
- status_id => $traewelling->{status_id},
- is_error => 1,
- );
- return;
- }
- my ( $train_ref, $train_id );
- 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 ($train_id) {
- $self->log->debug("... found train: $train_id");
my $db = $self->pg->db;
my $tx = $db->begin;
- my ( undef, $err ) = $self->checkin(
+ $self->checkin_p(
station => $traewelling->{dep_eva},
- train_id => $train_id,
+ train_id => $traewelling->{trip_id},
uid => $uid,
in_transaction => 1,
db => $db
- );
-
- if ( not $err ) {
- ( undef, $err ) = $self->checkout(
- station => $traewelling->{arr_eva},
- train_id => 0,
- uid => $uid,
- in_transaction => 1,
- db => $db
- );
- if ( not $err ) {
- $self->log->debug("... success!");
+ )->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,
+ uid => $uid,
+ db => $db,
user_data =>
{ comment => $traewelling->{message} }
);
}
$self->traewelling->log(
- uid => $uid,
- db => $db,
+ uid => $uid,
+ db => $db,
message =>
"Eingecheckt in $traewelling->{line} nach $traewelling->{arr_name}",
status_id => $traewelling->{status_id},
@@ -2267,28 +1957,171 @@ sub startup {
);
$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
+ )->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,
+ 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 =>
+"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;
}
- if ($err) {
- $self->log->debug("... error: $err");
+ )->catch(
+ sub {
+ my ( $err, $dep ) = @_;
$self->traewelling->log(
- uid => $uid,
+ uid => $uid,
message =>
-"Fehler bei $traewelling->{line} nach $traewelling->{arr_name}: $err",
+"Konnte $traewelling->{line} nach $traewelling->{arr_name} nicht übernehmen: $dep->{errstr}",
status_id => $traewelling->{status_id},
- is_error => 1
+ is_error => 1,
);
+ $promise->resolve;
+ return;
}
- }
- else {
- $self->traewelling->log(
- uid => $uid,
- message =>
-"$traewelling->{line} nach $traewelling->{arr_name} nicht gefunden",
- status_id => $traewelling->{status_id},
- is_error => 1
- );
- }
+ )->wait;
+
+ return $promise;
}
);
@@ -2415,16 +2248,16 @@ sub startup {
next;
}
- # Manual journey entries are only included if one of the following
- # conditions is satisfied:
- # * their route has more than two elements (-> probably more than just
- # start and stop station), or
- # * $include_manual is true (-> user wants to see incomplete routes)
- # This avoids messing up the map in case an A -> B connection has been
- # tracked both with a regular checkin (-> detailed route shown on map)
- # and entered manually (-> beeline also shown on map, typically
- # significantly differs from detailed route) -- unless the user
- # sets include_manual, of course.
+ # Manual journey entries are only included if one of the following
+ # conditions is satisfied:
+ # * their route has more than two elements (-> probably more than just
+ # start and stop station), or
+ # * $include_manual is true (-> user wants to see incomplete routes)
+ # This avoids messing up the map in case an A -> B connection has been
+ # tracked both with a regular checkin (-> detailed route shown on map)
+ # and entered manually (-> beeline also shown on map, typically
+ # significantly differs from detailed route) -- unless the user
+ # sets include_manual, of course.
if ( $journey->{edited} & 0x0010
and @route <= 2
and not $include_manual )
@@ -2470,7 +2303,11 @@ sub startup {
{
polylines => $json->encode( \@station_pairs ),
color => '#673ab7',
- opacity => $with_polyline ? 0.4 : 0.6,
+ opacity => @polylines
+ ? $with_polyline
+ ? 0.4
+ : 0.6
+ : 0.8,
},
{
polylines => $json->encode( \@polylines ),
@@ -2481,8 +2318,8 @@ sub startup {
};
if (@station_coordinates) {
- my @lats = map { $_->[0][0] } @station_coordinates;
- my @lons = map { $_->[0][1] } @station_coordinates;
+ my @lats = map { $_->[0][0] } @station_coordinates;
+ my @lons = map { $_->[0][1] } @station_coordinates;
my $min_lat = List::Util::min @lats;
my $max_lat = List::Util::max @lats;
my $min_lon = List::Util::min @lons;
@@ -2514,26 +2351,37 @@ sub startup {
$r->get('/changelog')->to('static#changelog');
$r->get('/impressum')->to('static#imprint');
$r->get('/imprint')->to('static#imprint');
- $r->get('/offline')->to('static#offline');
+ $r->get('/legend')->to('static#legend');
+ $r->get('/offline.html')->to('static#offline');
$r->get('/api/v1/:user_action/:token')->to('api#get_v1');
$r->get('/login')->to('account#login_form');
$r->get('/recover')->to('account#request_password_reset');
$r->get('/recover/:id/:token')->to('account#recover_password');
$r->get('/reg/:id/:token')->to('account#verify');
- $r->get('/status/:name')->to('traveling#user_status');
- $r->get('/status/:name/:ts')->to('traveling#user_status');
- $r->get('/ajax/status/:name')->to('traveling#public_status_card');
- $r->get('/ajax/status/:name/:ts')->to('traveling#public_status_card');
- $r->get('/p/:name')->to('traveling#public_profile');
- $r->get('/p/:name/j/:id')->to('traveling#public_journey_details');
+ $r->get( '/status/:name' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'profile#user_status', format => undef );
+ $r->get( '/status/:name/:ts' => [ format => [ 'html', 'json' ] ] )
+ ->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/j/:id' => 'public_journey' )
+ ->to('profile#journey_details');
+ $r->get('/.well-known/webfinger')->to('account#webfinger');
+ $r->get('/dyn/:av/autocomplete.js')->to('api#autocomplete');
$r->post('/api/v1/import')->to('api#import_v1');
$r->post('/api/v1/travel')->to('api#travel_v1');
- $r->post('/action')->to('traveling#log_action');
+ $r->post('/action')->to('traveling#travel_action');
$r->post('/geolocation')->to('traveling#geolocation');
$r->post('/list_departures')->to('traveling#redirect_to_station');
$r->post('/login')->to('account#do_login');
$r->post('/recover')->to('account#request_password_reset');
+ if ( $self->config->{traewelling}{oauth} ) {
+ $r->get('/oauth/traewelling')->to('traewelling#oauth');
+ $r->post('/oauth/traewelling')->to('traewelling#oauth');
+ }
+
if ( not $self->config->{registration}{disabled} ) {
$r->get('/register')->to('account#registration_form');
$r->post('/register')->to('account#register');
@@ -2545,16 +2393,24 @@ sub startup {
if ( $self->is_user_authenticated ) {
return 1;
}
- $self->render( 'login', redirect_to => $self->req->url );
+ $self->render(
+ 'login',
+ redirect_to => $self->req->url,
+ from => 'auth_required'
+ );
return undef;
}
);
$authed_r->get('/account')->to('account#account');
$authed_r->get('/account/privacy')->to('account#privacy');
+ $authed_r->get('/account/social')->to('account#social');
+ $authed_r->get('/account/social/:kind')->to('account#social_list');
+ $authed_r->get('/account/profile')->to('account#profile');
$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('/fgr')->to('passengerrights#list_candidates');
@@ -2568,27 +2424,35 @@ sub startup {
$authed_r->get('/history/commute')->to('traveling#commute');
$authed_r->get('/history/map')->to('traveling#map_history');
$authed_r->get('/history/:year')->to('traveling#yearly_history');
+ $authed_r->get('/history/:year/review')->to('traveling#year_in_review');
$authed_r->get('/history/:year/:month')->to('traveling#monthly_history');
$authed_r->get('/journey/add')->to('traveling#add_journey_form');
$authed_r->get('/journey/comment')->to('traveling#comment_form');
+ $authed_r->get('/journey/visibility')->to('traveling#visibility_form');
$authed_r->get('/journey/:id')->to('traveling#journey_details');
$authed_r->get('/s/*station')->to('traveling#station');
$authed_r->get('/confirm_mail/:token')->to('account#confirm_mail');
$authed_r->post('/account/privacy')->to('account#privacy');
+ $authed_r->post('/account/social')->to('account#social');
+ $authed_r->post('/account/profile')->to('account#profile');
$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('/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');
$authed_r->post('/journey/edit')->to('traveling#edit_journey');
$authed_r->post('/journey/passenger_rights/*filename')
->to('passengerrights#generate');
$authed_r->post('/account/password')->to('account#change_password');
$authed_r->post('/account/mail')->to('account#change_mail');
$authed_r->post('/account/name')->to('account#change_name');
+ $authed_r->post('/social-action')->to('account#social_action');
$authed_r->post('/delete')->to('account#delete');
$authed_r->post('/logout')->to('account#do_logout');
$authed_r->post('/set_token')->to('api#set_token');
+ $authed_r->get('/timeline/in-transit')->to('profile#checked_in');
}