summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2022-09-24 18:53:04 +0200
committerDaniel Friesel <derf@finalrewind.org>2022-09-24 18:53:04 +0200
commite54130ad6ea805cc04db17a3f614cb4bd25ca3f0 (patch)
tree4ffe1b52cc44a2c75c1614d5c36c244d06716609
parent1c7779e94ef6859348570b9c1b5f2f8a27e32c1f (diff)
optionally show local transit connections as well
-rw-r--r--lib/Travelynx/Command/database.pm23
-rw-r--r--lib/Travelynx/Command/maintenance.pm1
-rw-r--r--lib/Travelynx/Controller/Account.pm32
-rwxr-xr-xlib/Travelynx/Controller/Traveling.pm285
-rw-r--r--lib/Travelynx/Model/Users.pm27
-rw-r--r--templates/_checked_in.html.ep7
-rw-r--r--templates/_transit_fyi.html.ep19
-rw-r--r--templates/account.html.ep2
-rw-r--r--templates/use_history.html.ep24
9 files changed, 293 insertions, 127 deletions
diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm
index d580c05..33612c3 100644
--- a/lib/Travelynx/Command/database.pm
+++ b/lib/Travelynx/Command/database.pm
@@ -1083,6 +1083,29 @@ my @migrations = (
}
);
},
+
+ # v25 -> v26
+ # travelynx 1.24 adds local transit connections and needs to know targets
+ # for that to work, as local transit does not support checkins yet.
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ create table localtransit (
+ user_id integer not null references users (id) primary key,
+ data jsonb
+ );
+ create view user_transit as select
+ id,
+ use_history,
+ localtransit.data as data
+ from users
+ left join localtransit on localtransit.user_id = id
+ ;
+ update schema_version set version = 26;
+ }
+ );
+ },
);
sub setup_db {
diff --git a/lib/Travelynx/Command/maintenance.pm b/lib/Travelynx/Command/maintenance.pm
index d6380fd..60667b7 100644
--- a/lib/Travelynx/Command/maintenance.pm
+++ b/lib/Travelynx/Command/maintenance.pm
@@ -149,6 +149,7 @@ sub run {
my $transit_res = $db->delete( 'in_transit', { user_id => $uid } );
my $hooks_res = $db->delete( 'webhooks', { user_id => $uid } );
my $trwl_res = $db->delete( 'traewelling', { user_id => $uid } );
+ my $lt_res = $db->delete( 'localtransit', { user_id => $uid } );
my $password_res
= $db->delete( 'pending_passwords', { user_id => $uid } );
my $user_res = $db->delete( 'users', { id => $uid } );
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm
index d55b470..12b179b 100644
--- a/lib/Travelynx/Controller/Account.pm
+++ b/lib/Travelynx/Controller/Account.pm
@@ -6,7 +6,7 @@ package Travelynx::Controller::Account;
use Mojo::Base 'Mojolicious::Controller';
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
-use UUID::Tiny qw(:std);
+use UUID::Tiny qw(:std);
# Internal Helpers
@@ -498,8 +498,11 @@ sub privacy {
sub insight {
my ($self) = @_;
- my $user = $self->current_user;
- my $use_history = $self->users->use_history( uid => $user->{id} );
+ my $user = $self->current_user;
+ my ( $use_history, $destinations ) = $self->users->use_history(
+ uid => $user->{id},
+ with_local_transit => 1
+ );
if ( $self->param('action') and $self->param('action') eq 'save' ) {
if ( $self->param('on_departure') ) {
@@ -516,16 +519,31 @@ sub insight {
$use_history &= ~0x02;
}
+ if ( $self->param('local_transit') ) {
+ $use_history |= 0x04;
+ }
+ else {
+ $use_history &= ~0x04;
+ }
+
+ if ( $self->param('destinations') ) {
+ $destinations
+ = [ split( qr{\r?\n\r?}, $self->param('destinations') ) ];
+ }
+
$self->users->use_history(
- uid => $user->{id},
- set => $use_history
+ uid => $user->{id},
+ set => $use_history,
+ destinations => $destinations
);
$self->flash( success => 'use_history' );
$self->redirect_to('account');
}
- $self->param( on_departure => $use_history & 0x01 ? 1 : 0 );
- $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 );
+ $self->param( on_departure => $use_history & 0x01 ? 1 : 0 );
+ $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 );
+ $self->param( local_transit => $use_history & 0x04 ? 1 : 0 );
+ $self->param( destinations => join( "\n", @{$destinations} ) );
$self->render('use_history');
}
diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm
index bdf57e2..a4fe72b 100755
--- a/lib/Travelynx/Controller/Traveling.pm
+++ b/lib/Travelynx/Controller/Traveling.pm
@@ -28,8 +28,11 @@ sub has_str_in_list {
sub get_connecting_trains_p {
my ( $self, %opt ) = @_;
- my $uid = $opt{uid} //= $self->current_user->{id};
- my $use_history = $self->users->use_history( uid => $uid );
+ my $uid = $opt{uid} //= $self->current_user->{id};
+ my ( $use_history, $lt_stops ) = $self->users->use_history(
+ uid => $uid,
+ with_local_transit => 1
+ );
my ( $eva, $exclude_via, $exclude_train_id, $exclude_before );
my $now = $self->now->epoch;
@@ -72,7 +75,7 @@ sub get_connecting_trains_p {
@destinations = grep { $_ ne $exclude_via } @destinations;
}
- if ( not @destinations ) {
+ if ( not( @destinations or $use_history & 0x04 and @{$lt_stops} ) ) {
return $promise->reject;
}
@@ -82,44 +85,45 @@ sub get_connecting_trains_p {
my $iris_promise = Mojo::Promise->new;
- $self->iris->get_departures_p(
- station => $eva,
- lookbehind => 10,
- lookahead => $lookahead,
- with_related => 1
- )->then(
- sub {
- my ($stationboard) = @_;
- if ( $stationboard->{errstr} ) {
- $iris_promise->reject( $stationboard->{errstr} );
- return;
- }
-
- @{ $stationboard->{results} } = map { $_->[0] }
- sort { $a->[1] <=> $b->[1] }
- map { [ $_, $_->departure ? $_->departure->epoch : 0 ] }
- @{ $stationboard->{results} };
- my @results;
- my @cancellations;
- my $excluded_train;
- 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 )
- {
- $excluded_train = $train;
- next;
+ if (@destinations) {
+ $self->iris->get_departures_p(
+ station => $eva,
+ lookbehind => 10,
+ lookahead => $lookahead,
+ with_related => 1
+ )->then(
+ sub {
+ my ($stationboard) = @_;
+ if ( $stationboard->{errstr} ) {
+ $iris_promise->reject( $stationboard->{errstr} );
+ return;
}
+ @{ $stationboard->{results} } = map { $_->[0] }
+ sort { $a->[1] <=> $b->[1] }
+ map { [ $_, $_->departure ? $_->departure->epoch : 0 ] }
+ @{ $stationboard->{results} };
+ my @results;
+ my @cancellations;
+ my $excluded_train;
+ 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 )
+ {
+ $excluded_train = $train;
+ 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.
@@ -136,97 +140,105 @@ sub get_connecting_trains_p {
# 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 ( has_str_in_list( $dest, @via ) ) {
- push( @cancellations, [ $train, $dest ] );
- next;
+ if ( $train->departure_is_cancelled ) {
+ my @via = (
+ $train->sched_route_post, $train->sched_route_end
+ );
+ for my $dest (@destinations) {
+ if ( has_str_in_list( $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 has_str_in_list( $dest, @via ) )
- {
- push( @results, [ $train, $dest ] );
+ else {
+ my @via = ( $train->route_post, $train->route_end );
+ for my $dest (@destinations) {
+ if ( $via_count{$dest} < 2
+ and has_str_in_list( $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}++;
+ if ( not $train->departure
+ or $train->departure->epoch >= $now )
+ {
+ $via_count{$dest}++;
+ }
+ next;
}
- 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;
-
- # remove trains whose route matches the excluded one's
- if ($excluded_train) {
- my $route_pre = join( '|', reverse $excluded_train->route_pre );
- @results
- = grep { join( '|', $_->[0]->route_post ) ne $route_pre }
- @results;
- my $route_post = join( '|', $excluded_train->route_post );
- @results
- = grep { join( '|', $_->[0]->route_post ) ne $route_post }
- @results;
- }
- # add message IDs and 'transfer short' hints
- 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}{"*"};
+ @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;
+
+ # remove trains whose route matches the excluded one's
+ if ($excluded_train) {
+ my $route_pre
+ = join( '|', reverse $excluded_train->route_pre );
+ @results
+ = grep { join( '|', $_->[0]->route_post ) ne $route_pre }
+ @results;
+ my $route_post = join( '|', $excluded_train->route_post );
+ @results
+ = grep { join( '|', $_->[0]->route_post ) ne $route_post }
+ @results;
}
- 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} = 'directions_run';
+
+ # add message IDs and 'transfer short' hints
+ 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}{"*"};
}
- elsif ( $interchange_time == $interchange_duration ) {
- $train->{interchange_text}
- = 'Anschluss könnte knapp werden';
- $train->{interchange_icon} = 'directions_run';
+ 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} = 'directions_run';
+ }
+ elsif ( $interchange_time == $interchange_duration ) {
+ $train->{interchange_text}
+ = 'Anschluss könnte knapp werden';
+ $train->{interchange_icon} = 'directions_run';
+ }
}
}
- }
- $iris_promise->resolve( [ @results, @cancellations ] );
- return;
- }
- )->catch(
- sub {
- $iris_promise->reject(@_);
- return;
- }
- )->wait;
+ $iris_promise->resolve( [ @results, @cancellations ] );
+ return;
+ }
+ )->catch(
+ sub {
+ $iris_promise->reject(@_);
+ return;
+ }
+ )->wait;
+ }
+ else {
+ $iris_promise->resolve( [] );
+ }
my $hafas_promise = Mojo::Promise->new;
my $rest_api = $self->config->{backend}{hafas_rest_api};
@@ -254,6 +266,7 @@ sub get_connecting_trains_p {
my ( $iris, $hafas ) = @_;
my @iris_trains = @{ $iris->[0] };
my @hafas_trains = @{ $hafas->[0] };
+ my @transit_fyi;
my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S%z',
@@ -296,6 +309,44 @@ sub get_connecting_trains_p {
}
}
}
+ if ( $use_history & 0x04 and @{$lt_stops} ) {
+ my %via_count = map { $_ => 0 } @{$lt_stops};
+ for my $hafas_train (@hafas_trains) {
+ for
+ my $stop ( @{ $hafas_train->{nextStopovers} // [] } )
+ {
+ for my $dest ( @{$lt_stops} ) {
+ if ( $stop->{stop}{name}
+ and $stop->{stop}{name} eq $dest
+ and $via_count{$dest} < 2
+ and $hafas_train->{when} )
+ {
+ my $departure = $strp->parse_datetime(
+ $hafas_train->{when} );
+ my $arrival
+ = $strp->parse_datetime(
+ $stop->{arrival} );
+ if ( $departure->epoch >= $exclude_before )
+ {
+ $via_count{$dest}++;
+ push(
+ @transit_fyi,
+ [
+ {
+ line =>
+ $hafas_train->{line}
+ {name},
+ departure => $departure,
+ },
+ $dest, $arrival
+ ]
+ );
+ }
+ }
+ }
+ }
+ }
+ }
};
if ($@) {
$self->app->log->error(
@@ -303,7 +354,7 @@ sub get_connecting_trains_p {
);
}
- $promise->resolve( \@iris_trains );
+ $promise->resolve( \@iris_trains, \@transit_fyi );
return;
}
)->catch(
diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm
index dbd2a00..c36fa6d 100644
--- a/lib/Travelynx/Model/Users.pm
+++ b/lib/Travelynx/Model/Users.pm
@@ -9,6 +9,7 @@ use warnings;
use 5.020;
use DateTime;
+use JSON;
my @sb_templates = (
undef,
@@ -483,12 +484,34 @@ sub use_history {
my $uid = $opt{uid};
my $value = $opt{set};
+ if ( $opt{destinations} ) {
+ $db->insert(
+ 'localtransit',
+ {
+ user_id => $uid,
+ data =>
+ JSON->new->encode( { destinations => $opt{destinations} } )
+ },
+ { on_conflict => \'(user_id) do update set data = EXCLUDED.data' }
+ );
+ }
+
if ($value) {
$db->update( 'users', { use_history => $value }, { id => $uid } );
}
else {
- return $db->select( 'users', ['use_history'], { id => $uid } )
- ->hash->{use_history};
+ if ( $opt{with_local_transit} ) {
+ my $res = $db->select(
+ 'user_transit',
+ [ 'use_history', 'data' ],
+ { id => $uid }
+ )->expand->hash;
+ return ( $res->{use_history}, $res->{data}{destinations} // [] );
+ }
+ else {
+ return $db->select( 'users', ['use_history'], { id => $uid } )
+ ->hash->{use_history};
+ }
}
}
diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep
index 040c5de..dcf9ddc 100644
--- a/templates/_checked_in.html.ep
+++ b/templates/_checked_in.html.ep
@@ -200,6 +200,13 @@
% }
%= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef;
% }
+ % if (my @transit_fyi = @{stash('transit_fyi') // []}) {
+ <span class="card-title" style="margin-top: 2ex;">Nahverkehr</span>
+ % if ($journey->{arrival_countdown} < 0) {
+ <p>Nur zur Information – kein Checkin möglich.</p>
+ % }
+ %= include '_transit_fyi', transit_fyi => \@transit_fyi;
+ % }
% if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) {
<p style="margin-top: 2ex;">
Der automatische Checkout erfolgt wegen gelegentlich veralteter
diff --git a/templates/_transit_fyi.html.ep b/templates/_transit_fyi.html.ep
new file mode 100644
index 0000000..42233e5
--- /dev/null
+++ b/templates/_transit_fyi.html.ep
@@ -0,0 +1,19 @@
+<table class="striped"><tbody>
+ % for my $res (@{$transit_fyi}) {
+ % my ($info, $via, $via_arr) = @{$res};
+ % $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{};
+ <tr>
+ <td>
+ %= $info->{line}
+ <br/>
+ %= $info->{departure}->strftime('%H:%M')
+ % if ($info->{departure_delay}) {
+ %= sprintf('(%+d)', $info->{departure_delay})
+ % }
+ </td>
+ <td>
+ <%= $via %><br/><%= $via_arr %>
+ </td>
+ </tr>
+ % }
+</tbody></table>
diff --git a/templates/account.html.ep b/templates/account.html.ep
index 7658237..418291c 100644
--- a/templates/account.html.ep
+++ b/templates/account.html.ep
@@ -61,7 +61,7 @@
<th scope="row">Verbindungen</th>
<td>
<a href="/account/insight"><i class="material-icons">edit</i></a>
- % if ($use_history & 0x03) {
+ % if ($use_history & 0x07) {
Vorschläge aktiv
% }
% else {
diff --git a/templates/use_history.html.ep b/templates/use_history.html.ep
index e8e129f..1632b8b 100644
--- a/templates/use_history.html.ep
+++ b/templates/use_history.html.ep
@@ -48,6 +48,30 @@
</div>
</div>
<div class="row">
+ <div class="input-field col s12">
+ <label>
+ %= check_box local_transit => 1
+ <span>Nahverkehr</span>
+ </label>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col s12">
+ Zeige beim Reisestatus zusätzlich Anschlussmöglichkeiten an den
+ Nahverkehr. Diese dienen lediglich zur Information; ein Checkin ist
+ nicht möglich. Es werden nur Anschlussmöglichkeiten zu Zielen
+ angezeigt, die im folgenden Feld gelistet sind (ein Ziel pro
+ Zeile, z.B. „Eichlinghofen H-Bahn, Dortmund“). Falls travelynx in
+ Zukunft eine Möglichkeit für Checkins in Nahverkehrsmittel erhält,
+ wird diese Liste ggf. gelöscht.
+ </div>
+ </div>
+ <div class="row">
+ <div class="col s12">
+ %= text_area 'destinations', id => 'destinations', class => 'materialize-textarea'
+ </div>
+ </div>
+ <div class="row">
<div class="col s3 m3 l3">
</div>
<div class="col s6 m6 l6 center-align">