support non-DB HAFAS backends (WiP)

This commit is contained in:
Birte Kristina Friesel 2024-07-24 20:50:12 +02:00
parent 7811520a30
commit 2759d7258c
No known key found for this signature in database
GPG key ID: 19E6E524EBB177BA
30 changed files with 905 additions and 378 deletions

View file

@ -448,7 +448,7 @@ sub startup {
return Mojo::Promise->reject('You are already checked in'); return Mojo::Promise->reject('You are already checked in');
} }
if ( $train_id =~ m{[|]} ) { if ( $opt{hafas} ) {
return $self->_checkin_hafas_p(%opt); return $self->_checkin_hafas_p(%opt);
} }
@ -482,7 +482,9 @@ sub startup {
db => $db, db => $db,
departure_eva => $eva, departure_eva => $eva,
train => $train, train => $train,
route => [ $self->iris->route_diff($train) ], route => [ $self->iris->route_diff($train) ],
backend_id =>
$self->stations->get_backend_id( iris => 1 ),
); );
}; };
if ($@) { if ($@) {
@ -530,6 +532,7 @@ sub startup {
my $promise = Mojo::Promise->new; my $promise = Mojo::Promise->new;
$self->hafas->get_journey_p( $self->hafas->get_journey_p(
service => $opt{hafas},
trip_id => $train_id, trip_id => $train_id,
with_polyline => 1 with_polyline => 1
)->then( )->then(
@ -551,17 +554,21 @@ sub startup {
} }
for my $stop ( $journey->route ) { for my $stop ( $journey->route ) {
$self->stations->add_or_update( $self->stations->add_or_update(
stop => $stop, stop => $stop,
db => $db, db => $db,
hafas => $opt{hafas},
); );
} }
eval { eval {
$self->in_transit->add( $self->in_transit->add(
uid => $uid, uid => $uid,
db => $db, db => $db,
journey => $journey, journey => $journey,
stop => $found, stop => $found,
data => { trip_id => $journey->id } data => { trip_id => $journey->id },
backend_id => $self->stations->get_backend_id(
hafas => $opt{hafas}
),
); );
}; };
if ($@) { if ($@) {
@ -620,8 +627,8 @@ sub startup {
# mustn't be called during a transaction # mustn't be called during a transaction
if ( not $opt{in_transaction} ) { if ( not $opt{in_transaction} ) {
$self->run_hook( $uid, 'checkin' ); $self->run_hook( $uid, 'checkin' );
if ( $journey->class <= 16 ) { if ( $opt{hafas} eq 'DB' and $journey->class <= 16 ) {
$self->app->add_wagonorder( $uid, 1, $journey->id, $self->add_wagonorder( $uid, 1, $journey->id,
$found->sched_dep, $journey->number ); $found->sched_dep, $journey->number );
$self->add_stationinfo( $uid, 1, $journey->id, $self->add_stationinfo( $uid, 1, $journey->id,
$found->loc->eva ); $found->loc->eva );
@ -744,6 +751,7 @@ sub startup {
my $db = $opt{db} // $self->pg->db; my $db = $opt{db} // $self->pg->db;
my $user = $self->get_user_status( $uid, $db ); my $user = $self->get_user_status( $uid, $db );
my $train_id = $user->{train_id}; my $train_id = $user->{train_id};
my $hafas = $opt{hafas};
my $promise = Mojo::Promise->new; my $promise = Mojo::Promise->new;
@ -765,7 +773,7 @@ sub startup {
return $promise->resolve( 0, 'race condition' ); return $promise->resolve( 0, 'race condition' );
} }
if ( $train_id =~ m{[|]} ) { if ( $user->{is_hafas} ) {
return $self->_checkout_hafas_p(%opt); return $self->_checkout_hafas_p(%opt);
} }
@ -1736,7 +1744,8 @@ sub startup {
if ( $latest_cancellation and $latest_cancellation->{cancelled} ) { if ( $latest_cancellation and $latest_cancellation->{cancelled} ) {
if ( if (
my $station = $self->stations->get_by_eva( my $station = $self->stations->get_by_eva(
$latest_cancellation->{dep_eva} $latest_cancellation->{dep_eva},
backend_id => $latest_cancellation->{backend_id},
) )
) )
{ {
@ -1745,7 +1754,8 @@ sub startup {
} }
if ( if (
my $station = $self->stations->get_by_eva( my $station = $self->stations->get_by_eva(
$latest_cancellation->{arr_eva} $latest_cancellation->{arr_eva},
backend_id => $latest_cancellation->{backend_id},
) )
) )
{ {
@ -1760,14 +1770,20 @@ sub startup {
if ($latest) { if ($latest) {
my $ts = $latest->{checkout_ts}; my $ts = $latest->{checkout_ts};
my $action_time = epoch_to_dt($ts); my $action_time = epoch_to_dt($ts);
if ( my $station if (
= $self->stations->get_by_eva( $latest->{dep_eva} ) ) my $station = $self->stations->get_by_eva(
$latest->{dep_eva}, backend_id => $latest->{backend_id}
)
)
{ {
$latest->{dep_ds100} = $station->{ds100}; $latest->{dep_ds100} = $station->{ds100};
$latest->{dep_name} = $station->{name}; $latest->{dep_name} = $station->{name};
} }
if ( my $station if (
= $self->stations->get_by_eva( $latest->{arr_eva} ) ) my $station = $self->stations->get_by_eva(
$latest->{arr_eva}, backend_id => $latest->{backend_id}
)
)
{ {
$latest->{arr_ds100} = $station->{ds100}; $latest->{arr_ds100} = $station->{ds100};
$latest->{arr_name} = $station->{name}; $latest->{arr_name} = $station->{name};
@ -1776,6 +1792,10 @@ sub startup {
checked_in => 0, checked_in => 0,
cancelled => 0, cancelled => 0,
cancellation => $latest_cancellation, cancellation => $latest_cancellation,
backend_id => $latest->{backend_id},
backend_name => $latest->{backend_name},
is_iris => $latest->{is_iris},
is_hafas => $latest->{is_hafas},
journey_id => $latest->{journey_id}, journey_id => $latest->{journey_id},
timestamp => $action_time, timestamp => $action_time,
timestamp_delta => $now->epoch - $action_time->epoch, timestamp_delta => $now->epoch - $action_time->epoch,
@ -1833,7 +1853,12 @@ sub startup {
$status->{checked_in} $status->{checked_in}
or $status->{cancelled} or $status->{cancelled}
) ? \1 : \0, ) ? \1 : \0,
comment => $status->{comment}, comment => $status->{comment},
backend => {
id => $status->{backend_id},
type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS',
name => $status->{backend_name},
},
fromStation => { fromStation => {
ds100 => $status->{dep_ds100}, ds100 => $status->{dep_ds100},
name => $status->{dep_name}, name => $status->{dep_name},
@ -1992,6 +2017,7 @@ sub startup {
"Eingecheckt in $traewelling->{line} nach $traewelling->{arr_name}", "Eingecheckt in $traewelling->{line} nach $traewelling->{arr_name}",
status_id => $traewelling->{status_id}, status_id => $traewelling->{status_id},
); );
$self->traewelling->set_latest_pull_status_id( $self->traewelling->set_latest_pull_status_id(
uid => $uid, uid => $uid,
status_id => $traewelling->{status_id}, status_id => $traewelling->{status_id},
@ -2324,6 +2350,7 @@ sub startup {
$authed_r->get('/account/password')->to('account#password_form'); $authed_r->get('/account/password')->to('account#password_form');
$authed_r->get('/account/mail')->to('account#change_mail'); $authed_r->get('/account/mail')->to('account#change_mail');
$authed_r->get('/account/name')->to('account#change_name'); $authed_r->get('/account/name')->to('account#change_name');
$authed_r->get('/account/select_backend')->to('account#backend_form');
$authed_r->get('/export.json')->to('account#json_export'); $authed_r->get('/export.json')->to('account#json_export');
$authed_r->get('/history.json')->to('traveling#json_history'); $authed_r->get('/history.json')->to('traveling#json_history');
$authed_r->get('/history.csv')->to('traveling#csv_history'); $authed_r->get('/history.csv')->to('traveling#csv_history');
@ -2345,6 +2372,7 @@ sub startup {
$authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/account/hooks')->to('account#webhook');
$authed_r->post('/account/traewelling')->to('traewelling#settings'); $authed_r->post('/account/traewelling')->to('traewelling#settings');
$authed_r->post('/account/insight')->to('account#insight'); $authed_r->post('/account/insight')->to('account#insight');
$authed_r->post('/account/select_backend')->to('account#change_backend');
$authed_r->post('/journey/add')->to('traveling#add_journey_form'); $authed_r->post('/journey/add')->to('traveling#add_journey_form');
$authed_r->post('/journey/comment')->to('traveling#comment_form'); $authed_r->post('/journey/comment')->to('traveling#comment_form');
$authed_r->post('/journey/visibility')->to('traveling#visibility_form'); $authed_r->post('/journey/visibility')->to('traveling#visibility_form');

View file

@ -8,6 +8,7 @@ use Mojo::Base 'Mojolicious::Command';
use DateTime; use DateTime;
use File::Slurp qw(read_file); use File::Slurp qw(read_file);
use JSON; use JSON;
use Travel::Status::DE::HAFAS;
use Travel::Status::DE::IRIS::Stations; use Travel::Status::DE::IRIS::Stations;
has description => 'Initialize or upgrade database layout'; has description => 'Initialize or upgrade database layout';
@ -1918,7 +1919,7 @@ my @migrations = (
# v49 -> v50 # v49 -> v50
# travelynx 2.0 introduced proper HAFAS support, so there is no need for # travelynx 2.0 introduced proper HAFAS support, so there is no need for
# the 'FYI, here is some hAFAS data' kludge anymore. # the 'FYI, here is some HAFAS data' kludge anymore.
sub { sub {
my ($db) = @_; my ($db) = @_;
$db->query( $db->query(
@ -2310,6 +2311,235 @@ my @migrations = (
); );
}, },
# v54 -> v55
# do not share stations between backends
sub {
my ($db) = @_;
$db->query(
qq{
alter table schema_version add column hafas varchar(12);
alter table users drop column external_services;
alter table users add column backend_id smallint references backends (id) default 1;
alter table stations drop constraint stations_pkey;
alter table stations add unique (eva, source);
create index eva_by_source on stations (eva, source);
create index eva on stations (eva);
alter table related_stations drop constraint related_stations_eva_meta_key;
drop index rel_eva;
alter table related_stations add column backend_id smallint;
update related_stations set backend_id = 1;
alter table related_stations alter column backend_id set not null;
alter table related_stations add constraint backend_fk foreign key (backend_id) references backends (id);
alter table related_stations add unique (eva, meta, backend_id);
create index related_stations_eva_backend_key on related_stations (eva, backend_id);
}
);
# up until now, IRIS and DB HAFAS shared stations, with IRIS taking
# preference. As of v2.7, this is no longer the case. However, old DB
# HAFAS journeys may still reference IRIS-specific stations. So, we
# make all IRIS stations available as DB HAFAS stations as well.
my $total
= $db->select( 'stations', 'count(*) as count', { source => 0 } )
->hash->{count};
my $count = 0;
# Caveat: If this is a fresh installation, there are no IRIS stations
# in the database yet. So we have to populate it first.
if ( not $total ) {
say
'Preparing to untangle IRIS / HAFAS stations, this may take a while ...';
$total = scalar Travel::Status::DE::IRIS::Stations::get_stations();
for my $s ( Travel::Status::DE::IRIS::Stations::get_stations() ) {
my ( $ds100, $name, $eva, $lon, $lat ) = @{$s};
if ( $ENV{__TRAVELYNX_TEST_MINI_IRIS}
and ( $eva < 8000000 or $eva > 8000100 ) )
{
next;
}
$db->insert(
'stations',
{
eva => $eva,
ds100 => $ds100,
name => $name,
lat => $lat,
lon => $lon,
source => 0,
archived => 0
},
);
if ( $count++ % 1000 == 0 ) {
printf( " %2.0f%% complete\n", $count * 100 / $total );
}
}
$count = 0;
}
say 'Untangling IRIS / HAFAS stations, this may take a while ...';
my $res = $db->query(
qq{
select eva, ds100, name, lat, lon, archived
from stations
where source = 0;
}
);
while ( my $row = $res->hash ) {
$db->insert(
'stations',
{
eva => $row->{eva},
ds100 => $row->{ds100},
name => $row->{name},
lat => $row->{lat},
lon => $row->{lon},
archived => $row->{archived},
source => 1,
}
);
if ( $count++ % 1000 == 0 ) {
printf( " %2.0f%% complete\n", $count * 100 / $total );
}
}
$db->query(
qq{
alter table in_transit add constraint in_transit_checkin_eva_fk
foreign key (checkin_station_id, backend_id)
references stations (eva, source);
alter table in_transit add constraint in_transit_checkout_eva_fk
foreign key (checkout_station_id, backend_id)
references stations (eva, source);
alter table journeys add constraint journeys_checkin_eva_fk
foreign key (checkin_station_id, backend_id)
references stations (eva, source);
alter table journeys add constraint journeys_checkout_eva_fk
foreign key (checkout_station_id, backend_id)
references stations (eva, source);
drop view in_transit_str;
drop view journeys_str;
drop view follows_in_transit;
create view in_transit_str as select
user_id,
backend.iris as is_iris, backend.hafas as is_hafas,
backend.efa as is_efa, backend.ris as is_ris,
backend.name as backend_name, in_transit.backend_id as backend_id,
train_type, train_line, train_no, train_id,
extract(epoch from checkin_time) as checkin_ts,
extract(epoch from sched_departure) as sched_dep_ts,
extract(epoch from real_departure) as real_dep_ts,
checkin_station_id as dep_eva,
dep_station.ds100 as dep_ds100,
dep_station.name as dep_name,
dep_station.lat as dep_lat,
dep_station.lon as dep_lon,
extract(epoch from checkout_time) as checkout_ts,
extract(epoch from sched_arrival) as sched_arr_ts,
extract(epoch from real_arrival) as real_arr_ts,
checkout_station_id as arr_eva,
arr_station.ds100 as arr_ds100,
arr_station.name as arr_name,
arr_station.lat as arr_lat,
arr_station.lon as arr_lon,
polyline_id,
polylines.polyline as polyline,
visibility,
coalesce(visibility, users.public_level & 127) as effective_visibility,
cancelled, route, messages, user_data,
dep_platform, arr_platform, data
from in_transit
left join polylines on polylines.id = polyline_id
left join users on users.id = user_id
left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source
left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source
left join backends as backend on in_transit.backend_id = backend.id
;
create view journeys_str as select
journeys.id as journey_id, user_id,
backend.iris as is_iris, backend.hafas as is_hafas,
backend.efa as is_efa, backend.ris as is_ris,
backend.name as backend_name, journeys.backend_id as backend_id,
train_type, train_line, train_no, train_id,
extract(epoch from checkin_time) as checkin_ts,
extract(epoch from sched_departure) as sched_dep_ts,
extract(epoch from real_departure) as real_dep_ts,
checkin_station_id as dep_eva,
dep_station.ds100 as dep_ds100,
dep_station.name as dep_name,
dep_station.lat as dep_lat,
dep_station.lon as dep_lon,
extract(epoch from checkout_time) as checkout_ts,
extract(epoch from sched_arrival) as sched_arr_ts,
extract(epoch from real_arrival) as real_arr_ts,
checkout_station_id as arr_eva,
arr_station.ds100 as arr_ds100,
arr_station.name as arr_name,
arr_station.lat as arr_lat,
arr_station.lon as arr_lon,
polylines.polyline as polyline,
visibility,
coalesce(visibility, users.public_level & 127) as effective_visibility,
cancelled, edited, route, messages, user_data,
dep_platform, arr_platform
from journeys
left join polylines on polylines.id = polyline_id
left join users on users.id = user_id
left join stations as dep_station on checkin_station_id = dep_station.eva and journeys.backend_id = dep_station.source
left join stations as arr_station on checkout_station_id = arr_station.eva and journeys.backend_id = arr_station.source
left join backends as backend on journeys.backend_id = backend.id
;
create view follows_in_transit as select
r1.subject_id as follower_id, user_id as followee_id,
users.name as followee_name,
train_type, train_line, train_no, train_id,
in_transit.backend_id as backend_id,
extract(epoch from checkin_time) as checkin_ts,
extract(epoch from sched_departure) as sched_dep_ts,
extract(epoch from real_departure) as real_dep_ts,
checkin_station_id as dep_eva,
dep_station.ds100 as dep_ds100,
dep_station.name as dep_name,
dep_station.lat as dep_lat,
dep_station.lon as dep_lon,
extract(epoch from checkout_time) as checkout_ts,
extract(epoch from sched_arrival) as sched_arr_ts,
extract(epoch from real_arrival) as real_arr_ts,
checkout_station_id as arr_eva,
arr_station.ds100 as arr_ds100,
arr_station.name as arr_name,
arr_station.lat as arr_lat,
arr_station.lon as arr_lon,
polyline_id,
polylines.polyline as polyline,
visibility,
coalesce(visibility, users.public_level & 127) as effective_visibility,
cancelled, route, messages, user_data,
dep_platform, arr_platform, data
from in_transit
left join polylines on polylines.id = polyline_id
left join users on users.id = user_id
left join relations as r1 on r1.predicate = 1 and r1.object_id = user_id
left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source
left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source
order by checkin_time desc
;
create view users_with_backend as select
users.id as id, users.name as name, status, public_level,
email, password, registered_at, last_seen,
deletion_requested, deletion_notified, use_history,
accept_follows, notifications, profile, backend_id, iris,
hafas, efa, ris, backend.name as backend_name
from users
left join backends as backend on users.backend_id = backend.id
;
update schema_version set version = 55;
update schema_version set hafas = '0';
}
);
say
'This travelynx instance now has support for non-DB HAFAS backends.';
say
'If the migration fails due to a deadlock, re-run it after stopping all background workers';
},
); );
sub sync_stations { sub sync_stations {
@ -2341,7 +2571,7 @@ sub sync_stations {
}, },
{ {
on_conflict => \ on_conflict => \
'(eva) do update set archived = false, source = 0, ds100 = EXCLUDED.ds100, name=EXCLUDED.name, lat=EXCLUDED.lat, lon=EXCLUDED.lon' '(eva, source) do update set archived = false, source = 0, ds100 = EXCLUDED.ds100, name=EXCLUDED.name, lat=EXCLUDED.lat, lon=EXCLUDED.lon'
} }
); );
if ( $count++ % 1000 == 0 ) { if ( $count++ % 1000 == 0 ) {
@ -2500,6 +2730,26 @@ sub sync_stations {
} }
} }
sub sync_backends {
my ($db) = @_;
for my $service ( Travel::Status::DE::HAFAS::get_services()) {
$db->insert(
'backends',
{
iris => 0,
hafas => 1,
efa => 0,
ris => 0,
name => $service->{shortname},
},
{ on_conflict => undef }
);
}
$db->update( 'schema_version',
{ hafas => $Travel::Status::DE::HAFAS::VERSION } );
}
sub setup_db { sub setup_db {
my ($db) = @_; my ($db) = @_;
my $tx = $db->begin; my $tx = $db->begin;
@ -2566,9 +2816,9 @@ sub migrate_db {
} }
my $iris_version = get_schema_version( $db, 'iris' ); my $iris_version = get_schema_version( $db, 'iris' );
say "Found IRIS station database v${iris_version}"; say "Found IRIS station table v${iris_version}";
if ( $iris_version eq $Travel::Status::DE::IRIS::Stations::VERSION ) { if ( $iris_version eq $Travel::Status::DE::IRIS::Stations::VERSION ) {
say 'Station database is up-to-date'; say 'Station table is up-to-date';
} }
else { else {
eval { eval {
@ -2587,6 +2837,18 @@ sub migrate_db {
} }
} }
my $hafas_version = get_schema_version( $db, 'hafas' );
say "Found backend table for HAFAS v${hafas_version}";
if ( $hafas_version eq $Travel::Status::DE::HAFAS::VERSION ) {
say 'Backend table is up-to-date';
}
else {
say
"Synchronizing with Travel::Status::DE::HAFAS $Travel::Status::DE::HAFAS::VERSION";
sync_backends($db);
}
$db->update( 'schema_version', $db->update( 'schema_version',
{ travelynx => $self->app->config->{version} } ); { travelynx => $self->app->config->{version} } );

View file

@ -1,4 +1,5 @@
package Travelynx::Command::dumpconfig; package Travelynx::Command::dumpconfig;
# Copyright (C) 2020-2023 Birte Kristina Friesel # Copyright (C) 2020-2023 Birte Kristina Friesel
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later

View file

@ -47,9 +47,12 @@ sub run {
my $arr = $entry->{arr_eva}; my $arr = $entry->{arr_eva};
my $train_id = $entry->{train_id}; my $train_id = $entry->{train_id};
if ( $train_id =~ m{[|]} ) { if ( $entry->{is_hafas} ) {
$self->app->hafas->get_journey_p( trip_id => $train_id )->then( $self->app->hafas->get_journey_p(
trip_id => $train_id,
service => $entry->{backend_name}
)->then(
sub { sub {
my ($journey) = @_; my ($journey) = @_;
@ -135,6 +138,9 @@ sub run {
next; next;
} }
# TODO irgendwo ist hier ne race condition wo ein neuer checkin (in HAFAS) mit IRIS-Daten überschrieben wird.
# Die ganzen updates brauchen wirklich mal sanity checks mit train id ...
# Note: IRIS data is not always updated in real-time. Both departure and # Note: IRIS data is not always updated in real-time. Both departure and
# arrival delays may take several minutes to appear, especially in case # arrival delays may take several minutes to appear, especially in case
# of large-scale disturbances. We work around this by continuing to # of large-scale disturbances. We work around this by continuing to

View file

@ -999,6 +999,52 @@ sub password_form {
$self->render('change_password'); $self->render('change_password');
} }
sub backend_form {
my ($self) = @_;
my $user = $self->current_user;
my @backends = $self->stations->get_backends;
for my $backend (@backends) {
my $type = 'UNKNOWN';
if ( $backend->{iris} ) {
$type = 'IRIS-TTS';
$backend->{name} = 'DB';
}
elsif ( $backend->{hafas} ) {
$type = 'HAFAS';
$backend->{longname}
= $self->hafas->get_service( $backend->{name} )->{name};
}
$backend->{type} = $type;
}
$self->render(
'select_backend',
backends => \@backends,
user => $user,
redirect_to => $self->req->param('redirect_to') // '/',
);
}
sub change_backend {
my ($self) = @_;
my $backend_id = $self->req->param('backend');
my $redir = $self->req->param('redirect_to') // '/';
if ( $backend_id !~ m{ ^ \d+ $ }x ) {
$self->redirect_to($redir);
}
$self->users->set_backend(
uid => $self->current_user->{id},
backend_id => $backend_id,
);
$self->redirect_to($redir);
}
sub change_password { sub change_password {
my ($self) = @_; my ($self) = @_;
my $old_password = $self->req->param('oldpw'); my $old_password = $self->req->param('oldpw');

View file

@ -117,6 +117,7 @@ sub travel_v1 {
deprecated => \0, deprecated => \0,
error => 'Malformed JSON', error => 'Malformed JSON',
}, },
status => 400,
); );
return; return;
} }
@ -130,6 +131,7 @@ sub travel_v1 {
deprecated => \0, deprecated => \0,
error => 'Malformed token', error => 'Malformed token',
}, },
status => 400,
); );
return; return;
} }
@ -143,6 +145,7 @@ sub travel_v1 {
deprecated => \0, deprecated => \0,
error => 'Malformed token', error => 'Malformed token',
}, },
status => 400,
); );
return; return;
} }
@ -155,6 +158,7 @@ sub travel_v1 {
deprecated => \0, deprecated => \0,
error => 'Invalid token', error => 'Invalid token',
}, },
status => 400,
); );
return; return;
} }
@ -169,6 +173,7 @@ sub travel_v1 {
error => 'Missing or invalid action', error => 'Missing or invalid action',
status => $self->get_user_status_json_v1( uid => $uid ) status => $self->get_user_status_json_v1( uid => $uid )
}, },
status => 400,
); );
return; return;
} }
@ -177,7 +182,8 @@ sub travel_v1 {
my $from_station = sanitize( q{}, $payload->{fromStation} ); my $from_station = sanitize( q{}, $payload->{fromStation} );
my $to_station = sanitize( q{}, $payload->{toStation} ); my $to_station = sanitize( q{}, $payload->{toStation} );
my $train_id; my $train_id;
my $hafas = exists $payload->{train}{journeyID} ? 1 : 0; my $hafas = sanitize(undef, $payload->{hafas});
$hafas //= exists $payload->{train}{journeyID} ? 'DB' : undef;
if ( if (
not( not(
@ -195,11 +201,12 @@ sub travel_v1 {
error => 'Missing fromStation or train data', error => 'Missing fromStation or train data',
status => $self->get_user_status_json_v1( uid => $uid ) status => $self->get_user_status_json_v1( uid => $uid )
}, },
status => 400,
); );
return; return;
} }
if ( not $hafas and not $self->stations->search($from_station) ) { if ( not $hafas and not $self->stations->search($from_station, iris => 1) ) {
$self->render( $self->render(
json => { json => {
success => \0, success => \0,
@ -207,13 +214,14 @@ sub travel_v1 {
error => 'Unknown fromStation', error => 'Unknown fromStation',
status => $self->get_user_status_json_v1( uid => $uid ) status => $self->get_user_status_json_v1( uid => $uid )
}, },
status => 400,
); );
return; return;
} }
if ( $to_station if ( $to_station
and not $hafas and not $hafas
and not $self->stations->search($to_station) ) and not $self->stations->search($to_station, iris => 1) )
{ {
$self->render( $self->render(
json => { json => {
@ -222,6 +230,7 @@ sub travel_v1 {
error => 'Unknown toStation', error => 'Unknown toStation',
status => $self->get_user_status_json_v1( uid => $uid ) status => $self->get_user_status_json_v1( uid => $uid )
}, },
status => 400,
); );
return; return;
} }
@ -273,7 +282,8 @@ sub travel_v1 {
return $self->checkin_p( return $self->checkin_p(
station => $from_station, station => $from_station,
train_id => $train_id, train_id => $train_id,
uid => $uid uid => $uid,
hafas => $hafas,
); );
} }
)->then( )->then(
@ -654,10 +664,13 @@ sub autocomplete {
$self->res->headers->cache_control('max-age=86400, immutable'); $self->res->headers->cache_control('max-age=86400, immutable');
my $backend_id = $self->param('backend_id') // 1;
my $output my $output
= "document.addEventListener('DOMContentLoaded',function(){M.Autocomplete.init(document.querySelectorAll('.autocomplete'),{\n"; = "document.addEventListener('DOMContentLoaded',function(){M.Autocomplete.init(document.querySelectorAll('.autocomplete'),{\n";
$output .= 'minLength:3,limit:50,data:'; $output .= 'minLength:3,limit:50,data:';
$output .= encode_json( $self->stations->get_for_autocomplete ); $output
.= encode_json( $self->stations->get_for_autocomplete( backend_id => $backend_id ) );
$output .= "\n});});\n"; $output .= "\n});});\n";
$self->render( $self->render(

View file

@ -27,7 +27,8 @@ sub has_str_in_list {
sub get_connecting_trains_p { sub get_connecting_trains_p {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid} //= $self->current_user->{id}; my $user = $self->current_user;
my $uid = $opt{uid} //= $user->{id};
my $use_history = $self->users->use_history( uid => $uid ); my $use_history = $self->users->use_history( uid => $uid );
my ( $eva, $exclude_via, $exclude_train_id, $exclude_before ); my ( $eva, $exclude_via, $exclude_train_id, $exclude_before );
@ -43,10 +44,19 @@ sub get_connecting_trains_p {
elsif ( $opt{destination_name} ) { elsif ( $opt{destination_name} ) {
$eva = $opt{eva}; $eva = $opt{eva};
} }
if (not defined $opt{backend_id}) {
if ($opt{hafas}) {
$opt{backend_id} = $self->stations->get_backend_id(hafas => $opt{hafas});
}
else {
$opt{backend_id} = $user->{backend_id};
}
}
} }
else { else {
if ( $use_history & 0x02 ) { if ( $use_history & 0x02 ) {
my $status = $self->get_user_status; my $status = $self->get_user_status;
$opt{backend_id} = $status->{backend_id};
$eva = $status->{arr_eva}; $eva = $status->{arr_eva};
$exclude_via = $status->{dep_name}; $exclude_via = $status->{dep_name};
$exclude_train_id = $status->{train_id}; $exclude_train_id = $status->{train_id};
@ -65,8 +75,9 @@ sub get_connecting_trains_p {
return $promise->reject; return $promise->reject;
} }
my ( $dest_ids, $destinations ) $self->log->debug("get_connecting_trains_p(backend_id => $opt{backend_id}, eva => $eva)");
= $self->journeys->get_connection_targets(%opt);
my $destinations = $self->journeys->get_connection_targets(%opt);
my @destinations = uniq_by { $_->{name} } @{$destinations}; my @destinations = uniq_by { $_->{name} } @{$destinations};
@ -78,11 +89,7 @@ sub get_connecting_trains_p {
return $promise->reject; return $promise->reject;
} }
my $iris_eva = $eva; $self->log->debug(join(q{, }, map { $_->{name} } @destinations));
if ( $eva < 8000000 ) {
$iris_eva = ( List::Util::first { $_ >= 8000000 } @{$dest_ids} )
// $eva;
}
my $can_check_in = not $arr_epoch || ( $arr_countdown // 1 ) < 0; my $can_check_in = not $arr_epoch || ( $arr_countdown // 1 ) < 0;
my $lookahead my $lookahead
@ -91,11 +98,9 @@ sub get_connecting_trains_p {
my $iris_promise = Mojo::Promise->new; my $iris_promise = Mojo::Promise->new;
my %via_count = map { $_->{name} => 0 } @destinations; my %via_count = map { $_->{name} => 0 } @destinations;
if ( $iris_eva >= 8000000 if ( $opt{backend_id} == 0 ) {
and List::Util::any { $_->{eva} >= 8000000 } @destinations )
{
$self->iris->get_departures_p( $self->iris->get_departures_p(
station => $iris_eva, station => $eva,
lookbehind => 10, lookbehind => 10,
lookahead => $lookahead, lookahead => $lookahead,
with_related => 1 with_related => 1
@ -103,7 +108,7 @@ sub get_connecting_trains_p {
sub { sub {
my ($stationboard) = @_; my ($stationboard) = @_;
if ( $stationboard->{errstr} ) { if ( $stationboard->{errstr} ) {
$iris_promise->resolve( [] ); $promise->resolve( [], [] );
return; return;
} }
@ -237,105 +242,28 @@ sub get_connecting_trains_p {
} }
} }
$iris_promise->resolve( [ @results, @cancellations ] ); $promise->resolve( [ @results, @cancellations ], [] );
return; return;
} }
)->catch( )->catch(
sub { sub {
$iris_promise->resolve( [] ); $promise->resolve( [], [] );
return; return;
} }
)->wait; )->wait;
} }
else { else {
$iris_promise->resolve( [] ); $self->hafas->get_departures_p(
} service => $self->stations->get_hafas_name(backend_id => $opt{backend_id}),
eva => $eva,
my $hafas_promise = Mojo::Promise->new; lookbehind => 10,
$self->hafas->get_departures_p( lookahead => $lookahead
eva => $eva, )->then(
lookbehind => 10, sub {
lookahead => $lookahead my ($status) = @_;
)->then( my @hafas_trains;
sub { my @all_hafas_trains = $status->results;
my ($status) = @_;
$hafas_promise->resolve( [ $status->results ] );
return;
}
)->catch(
sub {
# HAFAS data is optional.
# Errors are logged by get_json_p and can be silently ignored here.
$hafas_promise->resolve( [] );
return;
}
)->wait;
Mojo::Promise->all( $iris_promise, $hafas_promise )->then(
sub {
my ( $iris, $hafas ) = @_;
my @iris_trains = @{ $iris->[0] };
my @all_hafas_trains = @{ $hafas->[0] };
my @hafas_trains;
# We've already got a list of connecting trains; this function
# only adds further information to them. We ignore errors, as
# partial data is better than no data.
eval {
for my $iris_train (@iris_trains) {
if ( $iris_train->[0]->departure_is_cancelled ) {
for my $hafas_train (@all_hafas_trains) {
if ( $hafas_train->number
and $hafas_train->number
== $iris_train->[0]->train_no )
{
$hafas_train->{iris_seen} = 1;
next;
}
}
next;
}
for my $hafas_train (@all_hafas_trains) {
if ( $hafas_train->number
and $hafas_train->number
== $iris_train->[0]->train_no )
{
$hafas_train->{iris_seen} = 1;
if ( $hafas_train->load
and $hafas_train->load->{SECOND} )
{
$iris_train->[3] = $hafas_train->load;
}
for my $stop ( $hafas_train->route ) {
if ( $stop->loc->name
and $stop->loc->name eq
$iris_train->[1]->{name}
and $stop->arr )
{
$iris_train->[2] = $stop->arr;
if ( $iris_train->[0]->departure_delay
and not $stop->arr_delay )
{
$iris_train->[2]
->add( minutes => $iris_train->[0]
->departure_delay );
}
last;
}
}
last;
}
}
}
for my $hafas_train (@all_hafas_trains) { for my $hafas_train (@all_hafas_trains) {
if ( $hafas_train->{iris_seen} ) {
next;
}
if ( $hafas_train->station_eva >= 8000000 ) {
# better safe than sorry, for now
next;
}
for my $stop ( $hafas_train->route ) { for my $stop ( $hafas_train->route ) {
for my $dest (@destinations) { for my $dest (@destinations) {
if ( $stop->loc->name if ( $stop->loc->name
@ -353,30 +281,30 @@ sub get_connecting_trains_p {
} }
if ( $departure->epoch >= $exclude_before ) { if ( $departure->epoch >= $exclude_before ) {
$via_count{ $dest->{name} }++; $via_count{ $dest->{name} }++;
push( @hafas_trains, push(
[ $hafas_train, $dest, $arrival ] ); @hafas_trains,
[
$hafas_train, $dest,
$arrival, $opt{hafas}
]
);
} }
} }
} }
} }
} }
}; $promise->resolve( [], \@hafas_trains );
if ($@) { return;
$self->app->log->error(
"get_connecting_trains_p($uid): IRIS/HAFAS merge failed: $@"
);
} }
)->catch(
$promise->resolve( \@iris_trains, \@hafas_trains ); sub {
return; my ($err) = @_;
} $self->log->debug("get_connection_trains: hafas: $err");
)->catch( $promise->resolve( [], [] );
sub { return;
my ($err) = @_; }
$promise->reject($err); )->wait;
return; }
}
)->wait;
return $promise; return $promise;
} }
@ -394,7 +322,8 @@ sub compute_effective_visibility {
sub homepage { sub homepage {
my ($self) = @_; my ($self) = @_;
if ( $self->is_user_authenticated ) { if ( $self->is_user_authenticated ) {
my $uid = $self->current_user->{id}; my $user = $self->current_user;
my $uid = $user->{id};
my $status = $self->get_user_status; my $status = $self->get_user_status;
my @timeline = $self->in_transit->get_timeline( my @timeline = $self->in_transit->get_timeline(
uid => $uid, uid => $uid,
@ -405,17 +334,19 @@ sub homepage {
if ( $status->{checked_in} ) { if ( $status->{checked_in} ) {
my $journey_visibility my $journey_visibility
= $self->compute_effective_visibility( = $self->compute_effective_visibility(
$self->current_user->{default_visibility_str}, $user->{default_visibility_str},
$status->{visibility_str} ); $status->{visibility_str} );
if ( defined $status->{arrival_countdown} if ( defined $status->{arrival_countdown}
and $status->{arrival_countdown} < ( 40 * 60 ) ) and $status->{arrival_countdown} < ( 40 * 60 ) )
{ {
$self->render_later; $self->render_later;
$self->get_connecting_trains_p->then( $self->get_connecting_trains_p(
backend_id => $status->{backend_id} )->then(
sub { sub {
my ( $connections_iris, $connections_hafas ) = @_; my ( $connections_iris, $connections_hafas ) = @_;
$self->render( $self->render(
'landingpage', 'landingpage',
user => $user,
user_status => $status, user_status => $status,
journey_visibility => $journey_visibility, journey_visibility => $journey_visibility,
connections_iris => $connections_iris, connections_iris => $connections_iris,
@ -427,6 +358,7 @@ sub homepage {
sub { sub {
$self->render( $self->render(
'landingpage', 'landingpage',
user => $user,
user_status => $status, user_status => $status,
journey_visibility => $journey_visibility, journey_visibility => $journey_visibility,
); );
@ -438,6 +370,7 @@ sub homepage {
else { else {
$self->render( $self->render(
'landingpage', 'landingpage',
user => $user,
user_status => $status, user_status => $status,
journey_visibility => $journey_visibility, journey_visibility => $journey_visibility,
); );
@ -451,10 +384,12 @@ sub homepage {
} }
$self->render( $self->render(
'landingpage', 'landingpage',
user => $user,
user_status => $status, user_status => $status,
recent_targets => \@recent_targets, recent_targets => \@recent_targets,
with_autocomplete => 1, with_autocomplete => 1,
with_geolocation => 1 with_geolocation => 1,
backend_id => $user->{backend_id},
); );
$self->users->mark_seen( uid => $uid ); $self->users->mark_seen( uid => $uid );
} }
@ -484,7 +419,8 @@ sub status_card {
and $status->{arrival_countdown} < ( 40 * 60 ) ) and $status->{arrival_countdown} < ( 40 * 60 ) )
{ {
$self->render_later; $self->render_later;
$self->get_connecting_trains_p->then( $self->get_connecting_trains_p(
backend_id => $status->{backend_id} )->then(
sub { sub {
my ( $connections_iris, $connections_hafas ) = @_; my ( $connections_iris, $connections_hafas ) = @_;
$self->render( $self->render(
@ -515,6 +451,7 @@ sub status_card {
elsif ( $status->{cancellation} ) { elsif ( $status->{cancellation} ) {
$self->render_later; $self->render_later;
$self->get_connecting_trains_p( $self->get_connecting_trains_p(
backend_id => $status->{backend_id},
eva => $status->{cancellation}{dep_eva}, eva => $status->{cancellation}{dep_eva},
destination_name => $status->{cancellation}{arr_name} destination_name => $status->{cancellation}{arr_name}
)->then( )->then(
@ -539,7 +476,8 @@ sub status_card {
my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $now = DateTime->now( time_zone => 'Europe/Berlin' );
if ( $now->epoch - $status->{timestamp}->epoch < ( 30 * 60 ) ) { if ( $now->epoch - $status->{timestamp}->epoch < ( 30 * 60 ) ) {
$self->render_later; $self->render_later;
$self->get_connecting_trains_p->then( $self->get_connecting_trains_p( hafas => $self->param('hafas') )
->then(
sub { sub {
my ( $connections_iris, $connections_hafas ) = @_; my ( $connections_iris, $connections_hafas ) = @_;
$self->render( $self->render(
@ -563,14 +501,63 @@ sub status_card {
sub geolocation { sub geolocation {
my ($self) = @_; my ($self) = @_;
my $lon = $self->param('lon'); my $lon = $self->param('lon');
my $lat = $self->param('lat'); my $lat = $self->param('lat');
my $backend_id = $self->param('backend') // 0;
if ( not $lon or not $lat ) { if ( not $lon or not $lat or $backend_id !~ m{ ^ \d+ $ }x ) {
$self->render( json => { error => 'Invalid lon/lat received' } ); $self->render( json => { error => 'Invalid lon/lat received' } );
return; return;
} }
$self->render_later;
my $hafas_service
= $self->stations->get_hafas_name( backend_id => $backend_id );
if ($hafas_service) {
$self->render_later;
Travel::Status::DE::HAFAS->new_p(
promise => 'Mojo::Promise',
user_agent => $self->ua,
service => $hafas_service,
geoSearch => {
lat => $lat,
lon => $lon
}
)->then(
sub {
my ($hafas) = @_;
my @hafas = map {
{
name => $_->name,
eva => $_->eva,
distance => $_->distance_m / 1000,
hafas => $hafas_service
}
} $hafas->results;
if ( @hafas > 10 ) {
@hafas = @hafas[ 0 .. 9 ];
}
$self->render(
json => {
candidates => [@hafas],
}
);
}
)->catch(
sub {
my ($err) = @_;
$self->render(
json => {
candidates => [],
warning => $err,
}
);
}
)->wait;
return;
}
my @iris = map { my @iris = map {
{ {
@ -580,7 +567,6 @@ sub geolocation {
lon => $_->[0][3], lon => $_->[0][3],
lat => $_->[0][4], lat => $_->[0][4],
distance => $_->[1], distance => $_->[1],
hafas => 0,
} }
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon, } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
$lat, 10 ); $lat, 10 );
@ -588,48 +574,12 @@ sub geolocation {
if ( @iris > 5 ) { if ( @iris > 5 ) {
@iris = @iris[ 0 .. 4 ]; @iris = @iris[ 0 .. 4 ];
} }
$self->render(
json => {
candidates => [@iris],
}
);
Travel::Status::DE::HAFAS->new_p(
promise => 'Mojo::Promise',
user_agent => $self->ua,
geoSearch => {
lat => $lat,
lon => $lon
}
)->then(
sub {
my ($hafas) = @_;
my @hafas = map {
{
name => $_->name,
eva => $_->eva,
distance => $_->distance_m / 1000,
hafas => 'DB'
}
} $hafas->results;
if ( @hafas > 10 ) {
@hafas = @hafas[ 0 .. 9 ];
}
my @results = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [ $_, $_->{distance} ] } ( @iris, @hafas );
$self->render(
json => {
candidates => [@results],
}
);
}
)->catch(
sub {
my ($err) = @_;
$self->render(
json => {
candidates => [@iris],
warning => $err,
}
);
}
)->wait;
} }
sub travel_action { sub travel_action {
@ -684,6 +634,7 @@ sub travel_action {
$promise->then( $promise->then(
sub { sub {
return $self->checkin_p( return $self->checkin_p(
hafas => $params->{hafas},
station => $params->{station}, station => $params->{station},
train_id => $params->{train} train_id => $params->{train}
); );
@ -713,8 +664,8 @@ sub travel_action {
my ( $still_checked_in, undef ) = @_; my ( $still_checked_in, undef ) = @_;
if ( my $destination = $params->{dest} ) { if ( my $destination = $params->{dest} ) {
my $station_link = '/s/' . $destination; my $station_link = '/s/' . $destination;
if ( $status->{train_id} =~ m{[|]} ) { if ( $status->{is_hafas} ) {
$station_link .= '?hafas=DB'; $station_link .= '?hafas=' . $status->{backend_name};
} }
$self->render( $self->render(
json => { json => {
@ -749,8 +700,8 @@ sub travel_action {
sub { sub {
my ( $still_checked_in, $error ) = @_; my ( $still_checked_in, $error ) = @_;
my $station_link = '/s/' . $params->{station}; my $station_link = '/s/' . $params->{station};
if ( $status->{train_id} =~ m{[|]} ) { if ( $status->{is_hafas} ) {
$station_link .= '?hafas=DB'; $station_link .= '?hafas=' . $status->{backend_name};
} }
if ($error) { if ($error) {
@ -800,8 +751,12 @@ sub travel_action {
else { else {
my $redir = '/'; my $redir = '/';
if ( $status->{checked_in} or $status->{cancelled} ) { if ( $status->{checked_in} or $status->{cancelled} ) {
if ( $status->{train_id} =~ m{[|]} ) { if ( $status->{is_hafas} ) {
$redir = '/s/' . $status->{dep_eva} . '?hafas=DB'; $redir
= '/s/'
. $status->{dep_eva}
. '?hafas='
. $status->{backend_name};
} }
else { else {
$redir = '/s/' . $status->{dep_ds100}; $redir = '/s/' . $status->{dep_ds100};
@ -818,6 +773,7 @@ sub travel_action {
elsif ( $params->{action} eq 'cancelled_from' ) { elsif ( $params->{action} eq 'cancelled_from' ) {
$self->render_later; $self->render_later;
$self->checkin_p( $self->checkin_p(
hafas => $params->{hafas},
station => $params->{station}, station => $params->{station},
train_id => $params->{train} train_id => $params->{train}
)->then( )->then(
@ -920,7 +876,8 @@ sub station {
my $train = $self->param('train'); my $train = $self->param('train');
my $trip_id = $self->param('trip_id'); my $trip_id = $self->param('trip_id');
my $timestamp = $self->param('timestamp'); my $timestamp = $self->param('timestamp');
my $uid = $self->current_user->{id}; my $user = $self->current_user;
my $uid = $user->{id};
my @timeline = $self->in_transit->get_timeline( my @timeline = $self->in_transit->get_timeline(
uid => $uid, uid => $uid,
@ -928,7 +885,6 @@ sub station {
); );
my %checkin_by_train; my %checkin_by_train;
for my $checkin (@timeline) { for my $checkin (@timeline) {
say $checkin->{train_id};
push( @{ $checkin_by_train{ $checkin->{train_id} } }, $checkin ); push( @{ $checkin_by_train{ $checkin->{train_id} } }, $checkin );
} }
$self->stash( checkin_by_train => \%checkin_by_train ); $self->stash( checkin_by_train => \%checkin_by_train );
@ -945,10 +901,12 @@ sub station {
$timestamp = DateTime->now( time_zone => 'Europe/Berlin' ); $timestamp = DateTime->now( time_zone => 'Europe/Berlin' );
} }
my $use_hafas = $self->param('hafas'); my $hafas_service = $self->param('hafas')
// ( $user->{backend_hafas} ? $user->{backend_name} : undef );
my $promise; my $promise;
if ($use_hafas) { if ($hafas_service) {
$promise = $self->hafas->get_departures_p( $promise = $self->hafas->get_departures_p(
service => $hafas_service,
eva => $station, eva => $station,
timestamp => $timestamp, timestamp => $timestamp,
lookbehind => 30, lookbehind => 30,
@ -966,27 +924,21 @@ sub station {
$promise->then( $promise->then(
sub { sub {
my ($status) = @_; my ($status) = @_;
my $api_link;
my @results; my @results;
my $now = $self->now->epoch; my $now = $self->now->epoch;
my $now_within_range my $now_within_range
= abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0; = abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0;
if ($use_hafas) { if ($hafas_service) {
my $iris_eva = List::Util::min grep { $_ >= 1000000 }
@{ $status->station->{evas} // [] };
if ($iris_eva) {
$api_link = '/s/' . $iris_eva;
}
@results = map { $_->[0] } @results = map { $_->[0] }
sort { $b->[1] <=> $a->[1] } sort { $b->[1] <=> $a->[1] }
map { [ $_, $_->datetime->epoch ] } $status->results; map { [ $_, $_->datetime->epoch ] } $status->results;
$self->stations->add_meta( $self->stations->add_meta(
eva => $status->station->{eva}, eva => $status->station->{eva},
meta => $status->station->{evas} // [] meta => $status->station->{evas} // [],
hafas => $hafas_service,
); );
$status = { $status = {
station_eva => $status->station->{eva}, station_eva => $status->station->{eva},
@ -999,8 +951,6 @@ sub station {
} }
else { else {
$api_link = '/s/' . $status->{station_eva} . '?hafas=DB';
# You can't check into a train which terminates here # You can't check into a train which terminates here
@results = grep { $_->departure } @{ $status->{results} }; @results = grep { $_->departure } @{ $status->{results} };
@ -1029,10 +979,10 @@ sub station {
} }
my $connections_p; my $connections_p;
if ( $trip_id and $use_hafas ) { if ( $trip_id and $hafas_service ) {
@results = grep { $_->id eq $trip_id } @results; @results = grep { $_->id eq $trip_id } @results;
} }
elsif ( $train and not $use_hafas ) { elsif ( $train and not $hafas_service ) {
@results @results
= grep { $_->type . ' ' . $_->train_no eq $train } @results; = grep { $_->type . ' ' . $_->train_no eq $train } @results;
} }
@ -1059,18 +1009,18 @@ sub station {
my ( $connections_iris, $connections_hafas ) = @_; my ( $connections_iris, $connections_hafas ) = @_;
$self->render( $self->render(
'departures', 'departures',
user => $user,
hafas => $hafas_service,
eva => $status->{station_eva}, eva => $status->{station_eva},
datetime => $timestamp, datetime => $timestamp,
now_in_range => $now_within_range, now_in_range => $now_within_range,
results => \@results, results => \@results,
hafas => $use_hafas,
station => $status->{station_name}, station => $status->{station_name},
related_stations => $status->{related_stations}, related_stations => $status->{related_stations},
user_status => $user_status, user_status => $user_status,
can_check_out => $can_check_out, can_check_out => $can_check_out,
connections_iris => $connections_iris, connections_iris => $connections_iris,
connections_hafas => $connections_hafas, connections_hafas => $connections_hafas,
api_link => $api_link,
title => "travelynx: $status->{station_name}", title => "travelynx: $status->{station_name}",
); );
} }
@ -1078,16 +1028,16 @@ sub station {
sub { sub {
$self->render( $self->render(
'departures', 'departures',
user => $user,
hafas => $hafas_service,
eva => $status->{station_eva}, eva => $status->{station_eva},
datetime => $timestamp, datetime => $timestamp,
now_in_range => $now_within_range, now_in_range => $now_within_range,
results => \@results, results => \@results,
hafas => $use_hafas,
station => $status->{station_name}, station => $status->{station_name},
related_stations => $status->{related_stations}, related_stations => $status->{related_stations},
user_status => $user_status, user_status => $user_status,
can_check_out => $can_check_out, can_check_out => $can_check_out,
api_link => $api_link,
title => "travelynx: $status->{station_name}", title => "travelynx: $status->{station_name}",
); );
} }
@ -1096,16 +1046,16 @@ sub station {
else { else {
$self->render( $self->render(
'departures', 'departures',
user => $user,
hafas => $hafas_service,
eva => $status->{station_eva}, eva => $status->{station_eva},
datetime => $timestamp, datetime => $timestamp,
now_in_range => $now_within_range, now_in_range => $now_within_range,
results => \@results, results => \@results,
hafas => $use_hafas,
station => $status->{station_name}, station => $status->{station_name},
related_stations => $status->{related_stations}, related_stations => $status->{related_stations},
user_status => $user_status, user_status => $user_status,
can_check_out => $can_check_out, can_check_out => $can_check_out,
api_link => $api_link,
title => "travelynx: $status->{station_name}", title => "travelynx: $status->{station_name}",
); );
} }
@ -1120,15 +1070,22 @@ sub station {
status => 300, status => 300,
); );
} }
elsif ( $use_hafas and $status and $status->errcode eq 'LOCATION' ) elsif ( $hafas_service
and $status
and $status->errcode eq 'LOCATION' )
{ {
$self->hafas->search_location_p( query => $station )->then( $self->hafas->search_location_p(
service => $hafas_service,
query => $station
)->then(
sub { sub {
my ($hafas2) = @_; my ($hafas2) = @_;
my @suggestions = $hafas2->results; my @suggestions = $hafas2->results;
if ( @suggestions == 1 ) { if ( @suggestions == 1 ) {
$self->redirect_to( $self->redirect_to( '/s/'
'/s/' . $suggestions[0]->eva . '?hafas=DB' ); . $suggestions[0]->eva
. '?hafas='
. $hafas_service );
} }
else { else {
$self->render( $self->render(
@ -1169,17 +1126,7 @@ sub redirect_to_station {
my ($self) = @_; my ($self) = @_;
my $station = $self->param('station'); my $station = $self->param('station');
if ( my $s = $self->app->stations->search($station) ) { $self->redirect_to("/s/${station}");
if ( $s->{source} == 1 ) {
$self->redirect_to("/s/${station}?hafas=DB");
}
else {
$self->redirect_to("/s/${station}");
}
}
else {
$self->redirect_to("/s/${station}?hafas=DB");
}
} }
sub cancelled { sub cancelled {

View file

@ -97,6 +97,7 @@ sub get_departures_p {
: DateTime->now( time_zone => 'Europe/Berlin' ) : DateTime->now( time_zone => 'Europe/Berlin' )
)->subtract( minutes => $opt{lookbehind} ); )->subtract( minutes => $opt{lookbehind} );
return Travel::Status::DE::HAFAS->new_p( return Travel::Status::DE::HAFAS->new_p(
service => $opt{service},
station => $opt{eva}, station => $opt{eva},
datetime => $when, datetime => $when,
lookahead => $opt{lookahead} + $opt{lookbehind}, lookahead => $opt{lookahead} + $opt{lookbehind},
@ -111,6 +112,7 @@ sub search_location_p {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
return Travel::Status::DE::HAFAS->new_p( return Travel::Status::DE::HAFAS->new_p(
service => $opt{service},
locationSearch => $opt{query}, locationSearch => $opt{query},
cache => $self->{realtime_cache}, cache => $self->{realtime_cache},
promise => 'Mojo::Promise', promise => 'Mojo::Promise',
@ -128,6 +130,7 @@ sub get_tripid_p {
$train_desc =~ s{^- }{}; $train_desc =~ s{^- }{};
Travel::Status::DE::HAFAS->new_p( Travel::Status::DE::HAFAS->new_p(
service => $opt{service},
journeyMatch => $train_desc, journeyMatch => $train_desc,
datetime => $train->start, datetime => $train->start,
cache => $self->{realtime_cache}, cache => $self->{realtime_cache},
@ -175,6 +178,7 @@ sub get_journey_p {
my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $now = DateTime->now( time_zone => 'Europe/Berlin' );
Travel::Status::DE::HAFAS->new_p( Travel::Status::DE::HAFAS->new_p(
service => $opt{service},
journey => { journey => {
id => $opt{trip_id}, id => $opt{trip_id},
}, },
@ -212,6 +216,7 @@ sub get_route_p {
my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $now = DateTime->now( time_zone => 'Europe/Berlin' );
Travel::Status::DE::HAFAS->new_p( Travel::Status::DE::HAFAS->new_p(
service => $opt{service},
journey => { journey => {
id => $opt{trip_id}, id => $opt{trip_id},

View file

@ -9,7 +9,7 @@ use warnings;
use 5.020; use 5.020;
use Encode qw(encode); use Encode qw(encode);
use Email::Sender::Simple qw(try_to_sendmail); use Email::Sender::Simple qw(try_to_sendmail);
use MIME::Entity; use MIME::Entity;

View file

@ -116,6 +116,7 @@ sub get_status_p {
my $category = $status->{train}{category}; my $category = $status->{train}{category};
my $linename = $status->{train}{lineName}; my $linename = $status->{train}{lineName};
my $train_no = $status->{train}{journeyNumber};
my $trip_id = $status->{train}{hafasId}; my $trip_id = $status->{train}{hafasId};
my ( $train_type, $train_line ) = split( qr{ }, $linename ); my ( $train_type, $train_line ) = split( qr{ }, $linename );
$promise->resolve( $promise->resolve(
@ -133,6 +134,7 @@ sub get_status_p {
arr_ds100 => $arr_ds100, arr_ds100 => $arr_ds100,
arr_name => $arr_name, arr_name => $arr_name,
trip_id => $trip_id, trip_id => $trip_id,
train_no => $train_no,
train_type => $train_type, train_type => $train_type,
line => $linename, line => $linename,
line_no => $train_line, line_no => $train_line,

View file

@ -93,6 +93,7 @@ sub add {
my $uid = $opt{uid}; my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db; my $db = $opt{db} // $self->{pg}->db;
my $backend_id = $opt{backend_id};
my $train = $opt{train}; my $train = $opt{train};
my $journey = $opt{journey}; my $journey = $opt{journey};
my $stop = $opt{stop}; my $stop = $opt{stop};
@ -103,8 +104,6 @@ sub add {
my $json = JSON->new; my $json = JSON->new;
if ($train) { if ($train) {
my $backend_id
= $db->select( 'backends', ['id'], { iris => 1 } )->hash->{id};
$db->insert( $db->insert(
'in_transit', 'in_transit',
{ {
@ -136,14 +135,6 @@ sub add {
); );
} }
elsif ( $journey and $stop ) { elsif ( $journey and $stop ) {
my $backend_id = $db->select(
'backends',
['id'],
{
hafas => 1,
name => 'DB'
}
)->hash->{id};
my @route; my @route;
my $product = $journey->product_at( $stop->loc->eva ) my $product = $journey->product_at( $stop->loc->eva )
// $journey->product; // $journey->product;
@ -440,17 +431,20 @@ sub get_all_active {
->hashes->each; ->hashes->each;
} }
sub get_checkout_station_id { sub get_checkout_ids {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid}; my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db; my $db = $opt{db} // $self->{pg}->db;
my $status = $db->select( 'in_transit', ['checkout_station_id'], my $status = $db->select(
{ user_id => $uid } )->hash; 'in_transit',
[ 'checkout_station_id', 'backend_id' ],
{ user_id => $uid }
)->hash;
if ($status) { if ($status) {
return $status->{checkout_station_id}; return $status->{checkout_station_id}, $status->{backend_id};
} }
return; return;
} }
@ -819,7 +813,6 @@ sub update_arrival_hafas {
my $stop = $opt{stop}; my $stop = $opt{stop};
my $json = JSON->new; my $json = JSON->new;
# TODO use old rt data if available
my @route; my @route;
for my $j_stop ( $journey->route ) { for my $j_stop ( $journey->route ) {
push( push(

View file

@ -545,7 +545,7 @@ sub get {
my @select my @select
= ( = (
qw(journey_id is_iris is_hafas backend_name train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) qw(journey_id is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility)
); );
my %where = ( my %where = (
user_id => $uid, user_id => $uid,
@ -606,6 +606,7 @@ sub get {
is_iris => $entry->{is_iris}, is_iris => $entry->{is_iris},
is_hafas => $entry->{is_hafas}, is_hafas => $entry->{is_hafas},
backend_name => $entry->{backend_name}, backend_name => $entry->{backend_name},
backend_id => $entry->{backend_id},
type => $entry->{train_type}, type => $entry->{train_type},
line => $entry->{train_line}, line => $entry->{train_line},
no => $entry->{train_no}, no => $entry->{train_no},
@ -665,7 +666,10 @@ sub get {
my $rename = $self->{renamed_station}; my $rename = $self->{renamed_station};
for my $stop ( @{ $ref->{route} } ) { for my $stop ( @{ $ref->{route} } ) {
if ( $stop->[0] =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) { if ( $stop->[0] =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) {
if ( my $s = $self->{stations}->get_by_eva($1) ) { if ( my $s
= $self->{stations}
->get_by_eva( $1, backend_id => $ref->{backend_id} ) )
{
$stop->[0] = $s->{name}; $stop->[0] = $s->{name};
} }
} }
@ -800,14 +804,14 @@ sub get_oldest_ts {
return undef; return undef;
} }
sub get_latest_checkout_station_id { sub get_latest_checkout_ids {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid}; my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db; my $db = $opt{db} // $self->{pg}->db;
my $res_h = $db->select( my $res_h = $db->select(
'journeys', 'journeys',
['checkout_station_id'], [ 'checkout_station_id', 'backend_id', ],
{ {
user_id => $uid, user_id => $uid,
cancelled => 0 cancelled => 0
@ -822,7 +826,7 @@ sub get_latest_checkout_station_id {
return; return;
} }
return $res_h->{checkout_station_id}; return $res_h->{checkout_station_id}, $res_h->{backend_id};
} }
sub get_latest_checkout_stations { sub get_latest_checkout_stations {
@ -833,7 +837,7 @@ sub get_latest_checkout_stations {
my $res = $db->select( my $res = $db->select(
'journeys_str', 'journeys_str',
[ 'arr_name', 'arr_eva', 'train_id' ], [ 'arr_name', 'arr_eva', 'train_id', 'backend_id', 'backend_name', 'is_hafas' ],
{ {
user_id => $uid, user_id => $uid,
cancelled => 0 cancelled => 0
@ -856,7 +860,8 @@ sub get_latest_checkout_stations {
{ {
name => $row->{arr_name}, name => $row->{arr_name},
eva => $row->{arr_eva}, eva => $row->{arr_eva},
hafas => ( $row->{train_id} =~ m{[|]} ? 'DB' : 0 ), hafas => $row->{is_hafas} ? $row->{backend_name} : 0,
backend_id => $row->{backend_id},
} }
); );
} }
@ -1726,28 +1731,29 @@ sub get_stats {
return $stats; return $stats;
} }
sub get_latest_dest_id { sub get_latest_dest_ids {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $uid = $opt{uid}; my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db; my $db = $opt{db} // $self->{pg}->db;
if ( if (
my $id = $self->{in_transit}->get_checkout_station_id( my ( $id, $backend_id ) = $self->{in_transit}->get_checkout_ids(
uid => $uid, uid => $uid,
db => $db db => $db
) )
) )
{ {
return $id; return ( $id, $backend_id );
} }
return $self->get_latest_checkout_station_id( return $self->get_latest_checkout_ids(
uid => $uid, uid => $uid,
db => $db db => $db
); );
} }
# Returns a listref of {eva, name} hashrefs for the specified backend.
sub get_connection_targets { sub get_connection_targets {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
@ -1755,22 +1761,30 @@ sub get_connection_targets {
my $threshold = $opt{threshold} my $threshold = $opt{threshold}
// DateTime->now( time_zone => 'Europe/Berlin' )->subtract( months => 4 ); // DateTime->now( time_zone => 'Europe/Berlin' )->subtract( months => 4 );
my $db = $opt{db} //= $self->{pg}->db; my $db = $opt{db} //= $self->{pg}->db;
my $min_count = $opt{min_count} // 3; my $min_count = $opt{min_count} // 1;
my $dest_id = $opt{eva};
if ( $opt{destination_name} ) { if ( $opt{destination_name} ) {
return ( return [ { eva => $opt{eva}, name => $opt{destination_name} } ];
[],
[ { eva => $opt{eva}, name => $opt{destination_name} } ]
);
} }
my $dest_id = $opt{eva} // $self->get_latest_dest_id(%opt); my $backend_id = $opt{backend_id};
if ( not $dest_id ) { if ( not $dest_id ) {
return ( [], [] ); ( $dest_id, $backend_id ) = $self->get_latest_dest_ids(%opt);
} }
my $dest_ids = [ $dest_id, $self->{stations}->get_meta( eva => $dest_id ) ]; if ( not $dest_id ) {
return [];
}
my $dest_ids = [
$dest_id,
$self->{stations}->get_meta(
eva => $dest_id,
backend_id => $backend_id,
)
];
my $res = $db->select( my $res = $db->select(
'journeys', 'journeys',
@ -1778,7 +1792,8 @@ sub get_connection_targets {
{ {
user_id => $uid, user_id => $uid,
checkin_station_id => $dest_ids, checkin_station_id => $dest_ids,
real_departure => { '>', $threshold } real_departure => { '>', $threshold },
backend_id => $opt{backend_id},
}, },
{ {
group_by => ['checkout_station_id'], group_by => ['checkout_station_id'],
@ -1788,8 +1803,11 @@ sub get_connection_targets {
my @destinations my @destinations
= $res->hashes->grep( sub { shift->{count} >= $min_count } ) = $res->hashes->grep( sub { shift->{count} >= $min_count } )
->map( sub { shift->{dest} } )->each; ->map( sub { shift->{dest} } )->each;
@destinations = $self->{stations}->get_by_evas(@destinations); @destinations = $self->{stations}->get_by_evas(
return ( $dest_ids, \@destinations ); backend_id => $opt{backend_id},
evas => [@destinations]
);
return \@destinations;
} }
sub update_visibility { sub update_visibility {

View file

@ -14,38 +14,126 @@ sub new {
return bless( \%opt, $class ); return bless( \%opt, $class );
} }
sub get_backend_id {
my ( $self, %opt ) = @_;
if ( $opt{iris} ) {
# special case
return 0;
}
if ( $opt{hafas} and $self->{backend_id}{hafas}{ $opt{hafas} } ) {
return $self->{backend_id}{hafas}{ $opt{hafas} };
}
my $db = $opt{db} // $self->{pg}->db;
my $backend_id = 0;
if ( $opt{hafas} ) {
$backend_id = $db->select(
'backends',
['id'],
{
hafas => 1,
name => $opt{hafas}
}
)->hash->{id};
$self->{backend_id}{hafas}{ $opt{hafas} } = $backend_id;
}
return $backend_id;
}
sub get_hafas_name {
my ( $self, %opt ) = @_;
if ( exists $self->{hafas_name}{ $opt{backend_id} } ) {
return $self->{hafas_name}{ $opt{backend_id} };
}
my $db = $opt{db} // $self->{pg}->db;
my $hafas_name;
my $ret = $db->select(
'backends',
['name'],
{
hafas => 1,
id => $opt{backend_id},
}
)->hash;
if ($ret) {
$hafas_name = $ret->{name};
}
$self->{hafas_name}{ $opt{backend_id} } = $hafas_name;
return $hafas_name;
}
sub get_backends {
my ( $self, %opt ) = @_;
$opt{db} //= $self->{pg}->db;
my $res = $opt{db}->select( 'backends', [ 'id', 'name', 'iris', 'hafas' ] );
my @ret;
while ( my $row = $res->hash ) {
push(
@ret,
{
id => $row->{id},
name => $row->{name},
iris => $row->{iris},
hafas => $row->{hafas},
}
);
}
return @ret;
}
sub add_or_update { sub add_or_update {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $stop = $opt{stop}; my $stop = $opt{stop};
my $loc = $stop->loc; my $loc = $stop->loc;
my $source = 1; $opt{db} //= $self->{pg}->db;
my $db = $opt{db} // $self->{pg}->db;
if ( my $s = $self->get_by_eva( $loc->eva, db => $db ) ) { $opt{backend_id} //= $self->get_backend_id(%opt);
if ( $source == 1 and $s->{source} == 0 and not $s->{archived} ) {
return; if (
} my $s = $self->get_by_eva(
$db->update( $loc->eva,
db => $opt{db},
backend_id => $opt{backend_id}
)
)
{
$opt{db}->update(
'stations', 'stations',
{ {
name => $loc->name, name => $loc->name,
lat => $loc->lat, lat => $loc->lat,
lon => $loc->lon, lon => $loc->lon,
source => $source, source => $opt{backend_id},
archived => 0 archived => 0
}, },
{ eva => $loc->eva } {
eva => $loc->eva,
source => $opt{backend_id}
}
); );
return; return;
} }
$db->insert( $opt{db}->insert(
'stations', 'stations',
{ {
eva => $loc->eva, eva => $loc->eva,
name => $loc->name, name => $loc->name,
lat => $loc->lat, lat => $loc->lat,
lon => $loc->lon, lon => $loc->lon,
source => $source, source => $opt{backend_id},
archived => 0 archived => 0
} }
); );
@ -53,17 +141,20 @@ sub add_or_update {
sub add_meta { sub add_meta {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $eva = $opt{eva}; my $eva = $opt{eva};
my @meta = @{ $opt{meta} }; my @meta = @{ $opt{meta} };
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
for my $meta (@meta) { for my $meta (@meta) {
if ( $meta != $eva ) { if ( $meta != $eva ) {
$db->insert( $opt{db}->insert(
'related_stations', 'related_stations',
{ {
eva => $eva, eva => $eva,
meta => $meta meta => $meta,
backend_id => $opt{backend_id},
}, },
{ on_conflict => undef } { on_conflict => undef }
); );
@ -82,7 +173,16 @@ sub get_meta {
my $db = $opt{db} // $self->{pg}->db; my $db = $opt{db} // $self->{pg}->db;
my $eva = $opt{eva}; my $eva = $opt{eva};
my $res = $db->select( 'related_stations', ['meta'], { eva => $eva } ); $opt{backend_id} //= $self->get_backend_id( %opt, db => $db );
my $res = $db->select(
'related_stations',
['meta'],
{
eva => $eva,
backend_id => $opt{backend_id}
}
);
my @ret; my @ret;
while ( my $row = $res->hash ) { while ( my $row = $res->hash ) {
@ -93,9 +193,12 @@ sub get_meta {
} }
sub get_for_autocomplete { sub get_for_autocomplete {
my ($self) = @_; my ( $self, %opt ) = @_;
my $res = $self->{pg}->db->select( 'stations', ['name'] ); $opt{backend_id} //= $self->get_backend_id(%opt);
my $res = $self->{pg}
->db->select( 'stations', ['name'], { source => $opt{backend_id} } );
my %ret; my %ret;
while ( my $row = $res->hash ) { while ( my $row = $res->hash ) {
@ -113,18 +216,34 @@ sub get_by_eva {
return; return;
} }
my $db = $opt{db} // $self->{pg}->db; $opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
return $db->select( 'stations', '*', { eva => $eva } )->hash; return $opt{db}->select(
'stations',
'*',
{
eva => $eva,
source => $opt{backend_id}
}
)->hash;
} }
# Fast # Fast
sub get_by_evas { sub get_by_evas {
my ( $self, @evas ) = @_; my ( $self, %opt ) = @_;
my @ret $opt{db} //= $self->{pg}->db;
= $self->{pg}->db->select( 'stations', '*', { eva => { '=', \@evas } } ) $opt{backend_id} //= $self->get_backend_id(%opt);
->hashes->each;
my @ret = $self->{pg}->db->select(
'stations',
'*',
{
eva => { '=', $opt{evas} },
source => $opt{backend_id}
}
)->hashes->each;
return @ret; return @ret;
} }
@ -132,10 +251,18 @@ sub get_by_evas {
sub get_by_name { sub get_by_name {
my ( $self, $name, %opt ) = @_; my ( $self, $name, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db; $opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
return $db->select( 'stations', '*', { name => $name }, { limit => 1 } ) return $opt{db}->select(
->hash; 'stations',
'*',
{
name => $name,
source => $opt{backend_id}
},
{ limit => 1 }
)->hash;
} }
# Slow # Slow
@ -152,16 +279,27 @@ sub get_by_names {
sub get_by_ds100 { sub get_by_ds100 {
my ( $self, $ds100, %opt ) = @_; my ( $self, $ds100, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db; $opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
return $db->select( 'stations', '*', { ds100 => $ds100 }, { limit => 1 } ) return $opt{db}->select(
->hash; 'stations',
'*',
{
ds100 => $ds100,
source => $opt{backend_id}
},
{ limit => 1 }
)->hash;
} }
# Can be slow # Can be slow
sub search { sub search {
my ( $self, $identifier, %opt ) = @_; my ( $self, $identifier, %opt ) = @_;
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
if ( $identifier =~ m{ ^ \d+ $ }x ) { if ( $identifier =~ m{ ^ \d+ $ }x ) {
return $self->get_by_eva( $identifier, %opt ) return $self->get_by_eva( $identifier, %opt )
// $self->get_by_ds100( $identifier, %opt ) // $self->get_by_ds100( $identifier, %opt )

View file

@ -224,6 +224,7 @@ sub get_pushable_accounts {
join in_transit_str as i on t.user_id = i.user_id join in_transit_str as i on t.user_id = i.user_id
where t.push_sync = True where t.push_sync = True
and i.arr_eva is not null and i.arr_eva is not null
and i.backend_id <= 1
and i.cancelled = False and i.cancelled = False
} }
); );

View file

@ -205,6 +205,13 @@ sub get_privacy_by {
return; return;
} }
sub set_backend {
my ( $self, %opt ) = @_;
$opt{db} //= $self->{pg}->db;
$opt{db}->update('users', {backend_id => $opt{backend_id}}, {id => $opt{uid}});
}
sub set_privacy { sub set_privacy {
my ( $self, %opt ) = @_; my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db; my $db = $opt{db} // $self->{pg}->db;
@ -401,12 +408,13 @@ sub get {
my $uid = $opt{uid}; my $uid = $opt{uid};
my $user = $db->select( my $user = $db->select(
'users', 'users_with_backend',
'id, name, status, public_level, email, ' 'id, name, status, public_level, email, '
. 'accept_follows, notifications, ' . 'accept_follows, notifications, '
. 'extract(epoch from registered_at) as registered_at_ts, ' . 'extract(epoch from registered_at) as registered_at_ts, '
. 'extract(epoch from last_seen) as last_seen_ts, ' . 'extract(epoch from last_seen) as last_seen_ts, '
. 'extract(epoch from deletion_requested) as deletion_requested_ts', . 'extract(epoch from deletion_requested) as deletion_requested_ts, '
. 'backend_id, backend_name, hafas',
{ id => $uid } { id => $uid }
)->hash; )->hash;
if ($user) { if ($user) {
@ -443,6 +451,9 @@ sub get {
time_zone => 'Europe/Berlin' time_zone => 'Europe/Berlin'
) )
: undef, : undef,
backend_id => $user->{backend_id},
backend_name => $user->{backend_name},
backend_hafas => $user->{hafas},
}; };
} }
return undef; return undef;

View file

@ -62,7 +62,8 @@ $(document).ready(function() {
}; };
const processLocation = function(loc) { const processLocation = function(loc) {
$.post('/geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude}, processResult); const backend = $('div.geolocation > button').data('backend');
$.post('/geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude, backend: backend}, processResult);
}; };
const processError = function(error) { const processError = function(error) {

View file

@ -191,6 +191,7 @@ function tvly_reg_handlers() {
var link = $(this); var link = $(this);
var req = { var req = {
action: 'checkin', action: 'checkin',
hafas: link.data('hafas'),
station: link.data('station'), station: link.data('station'),
train: link.data('train'), train: link.data('train'),
dest: link.data('dest'), dest: link.data('dest'),
@ -202,6 +203,7 @@ function tvly_reg_handlers() {
var link = $(this); var link = $(this);
var req = { var req = {
action: 'checkout', action: 'checkout',
hafas: link.data('hafas'),
station: link.data('station'), station: link.data('station'),
force: link.data('force'), force: link.data('force'),
}; };
@ -232,6 +234,7 @@ function tvly_reg_handlers() {
var link = $(this); var link = $(this);
var req = { var req = {
action: 'cancelled_from', action: 'cancelled_from',
hafas: link.data('hafas'),
station: link.data('station'), station: link.data('station'),
ts: link.data('ts'), ts: link.data('ts'),
train: link.data('train'), train: link.data('train'),
@ -242,6 +245,7 @@ function tvly_reg_handlers() {
var link = $(this); var link = $(this);
var req = { var req = {
action: 'cancelled_to', action: 'cancelled_to',
hafas: link.data('hafas'),
station: link.data('station'), station: link.data('station'),
force: true, force: true,
}; };

View file

@ -341,6 +341,7 @@ $t->app->in_transit->add(
departure_eva => 8000001, departure_eva => 8000001,
train => $train_dep, train => $train_dep,
route => [], route => [],
backend_id => $t->app->stations->get_backend_id( iris => 1 ),
); );
$t->app->in_transit->set_arrival_eva( $t->app->in_transit->set_arrival_eva(
uid => $uid1, uid => $uid1,

View file

@ -303,6 +303,7 @@ $t->app->in_transit->add(
departure_eva => 8000001, departure_eva => 8000001,
train => $train_dep, train => $train_dep,
route => [], route => [],
backend_id => $t->app->stations->get_backend_id( iris => 1 ),
); );
$t->app->in_transit->set_arrival_eva( $t->app->in_transit->set_arrival_eva(
uid => $uid1, uid => $uid1,

View file

@ -266,6 +266,7 @@ $t->app->in_transit->add(
departure_eva => 8000001, departure_eva => 8000001,
train => $train_dep, train => $train_dep,
route => [], route => [],
backend_id => $t->app->stations->get_backend_id( iris => 1 ),
); );
$t->app->in_transit->set_arrival_eva( $t->app->in_transit->set_arrival_eva(
uid => $uid1, uid => $uid1,

View file

@ -345,7 +345,7 @@
% } % }
<a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a> <a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a>
% if ($journey->{extra_data}{trip_id}) { % if ($journey->{extra_data}{trip_id}) {
<a class="right" style="margin-right: 0;" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?from=<%= $journey->{dep_name} %>&amp;to=<%= $journey->{arr_name} %>&amp;dark=<%= (session('theme') and session('theme') eq 'dark') ? 1 : 0 %>"><i class="material-icons left" aria-hidden="true">map</i> Karte</a> <a class="right" style="margin-right: 0;" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?hafas=<%= $journey->{backend_name} // 'DB' %>&amp;from=<%= $journey->{dep_name} %>&amp;to=<%= $journey->{arr_name} %>&amp;dark=<%= (session('theme') and session('theme') eq 'dark') ? 1 : 0 %>"><i class="material-icons left" aria-hidden="true">map</i> Karte</a>
% } % }
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@
<span class="card-title">Ausgecheckt</span> <span class="card-title">Ausgecheckt</span>
<p>Aus <p>Aus
%= include '_format_train', journey => $journey %= include '_format_train', journey => $journey
bis <a href="/s/<%= $journey->{arr_eva} %>?hafas=<%= $journey->{train_id} =~ m{[|]} ? 1 : 0 %>"><%= $journey->{arr_name} %></a></p> bis <a href="/s/<%= $journey->{arr_eva} %>?hafas=<%= $journey->{is_hafas} ? $journey->{backend_name} : q{} %>"><%= $journey->{arr_name} %></a></p>
% if (@{stash('connections_iris') // [] } or @{stash('connections_hafas') // []}) { % if (@{stash('connections_iris') // [] } or @{stash('connections_hafas') // []}) {
<span class="card-title" style="margin-top: 2ex;">Verbindungen</span> <span class="card-title" style="margin-top: 2ex;">Verbindungen</span>
<p>Fahrt auswählen zum Einchecken mit Zielwahl.</p> <p>Fahrt auswählen zum Einchecken mit Zielwahl.</p>

View file

@ -1,6 +1,6 @@
<ul class="collection departures connections"> <ul class="collection departures connections">
% for my $res (@{$connections}) { % for my $res (@{$connections}) {
% my ($train, $via, $via_arr) = @{$res}; % my ($train, $via, $via_arr, $hafas_service) = @{$res};
% $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{}; % $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{};
% my $row_class = ''; % my $row_class = '';
% my $link_class = 'action-checkin'; % my $link_class = 'action-checkin';
@ -10,6 +10,7 @@
% } % }
% if ($checkin_from) { % if ($checkin_from) {
<li class="collection-item <%= $row_class %> <%= $link_class %>" <li class="collection-item <%= $row_class %> <%= $link_class %>"
data-hafas="<%= $hafas_service %>"
data-station="<%= $train->station_eva %>" data-station="<%= $train->station_eva %>"
data-train="<%= $train->id %>" data-train="<%= $train->id %>"
data-ts="<%= ($train->sched_datetime // $train->datetime)->epoch %>" data-ts="<%= ($train->sched_datetime // $train->datetime)->epoch %>"

View file

@ -18,6 +18,7 @@
</li> </li>
% } % }
<li class="collection-item <%= $link_class %> <%= $row_class %>" <li class="collection-item <%= $link_class %> <%= $row_class %>"
data-hafas="<%= $hafas %>"
data-station="<%= $result->station_eva %>" data-station="<%= $result->station_eva %>"
data-train="<%= $result->id %>" data-train="<%= $result->id %>"
data-ts="<%= ($result->sched_datetime // $result->datetime)->epoch %>" data-ts="<%= ($result->sched_datetime // $result->datetime)->epoch %>"

View file

@ -1,26 +1,27 @@
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s8">
<h2> <strong style="font-size: 120%;">
<i class="material-icons " aria-hidden="true"><%= param('hafas') ? 'directions' : 'train' %></i>
<%= $station %> <%= $station %>
</h2> </strong>
% for my $related_station (sort { $a->{name} cmp $b->{name} } @{$related_stations}) { % for my $related_station (sort { $a->{name} cmp $b->{name} } @{$related_stations}) {
+ <%= $related_station->{name} %> <br/> + <%= $related_station->{name} %> <br/>
% } % }
</div> </div>
</div> <div class="col s4 center-align">
% if ($api_link) { % my $self_link = url_for('sstation', station => param('station'));
<div class="row"> % if (param('hafas')) {
<div class="col s12 center-align"> <span class="btn-small disabled"><i class="material-icons left" aria-hidden="true">directions</i> <%= param('hafas') %> (HAFAS)</span>
% if (param('hafas')) { % }
<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">train</i>zum Schienenverkehr</a> % else {
% } % if ($user->{backend_id}) {
% else { <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %> (<%= $user->{backend_hafas} ? 'HAFAS' : q{} %>)</a>
<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">directions</i>zum Nahverkehr</a> % }
% } % else {
<a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">train</i>DB (IRIS-TTS)</a>
% }
% }
</div> </div>
</div> </div>
% }
% my $have_connections = 0; % my $have_connections = 0;
% if ($user_status->{checked_in}) { % if ($user_status->{checked_in}) {
@ -40,10 +41,10 @@
</div> </div>
<div class="card-action"> <div class="card-action">
% if ($can_check_out) { % if ($can_check_out) {
<a class="action-undo" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>" style="margin-right: 0;"> <a class="action-undo" data-hafas="<%= param('hafas') // q{} %>" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>" style="margin-right: 0;">
<i class="material-icons left" aria-hidden="true">undo</i> Rückgängig <i class="material-icons left" aria-hidden="true">undo</i> Rückgängig
</a> </a>
<a class="action-checkout right" data-station="<%= $eva %>" data-force="1"> <a class="action-checkout right" data-hafas="<%= param('hafas') // q{} %>" data-station="<%= $eva %>" data-force="1">
Hier auschecken Hier auschecken
</a> </a>
% } % }
@ -51,7 +52,7 @@
<a class="action-undo" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>" style="margin-right: 0;"> <a class="action-undo" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>" style="margin-right: 0;">
<i class="material-icons left" aria-hidden="true">undo</i> Rückgängig <i class="material-icons left" aria-hidden="true">undo</i> Rückgängig
</a> </a>
<a class="action-checkout right" data-station="<%= $eva %>" data-force="1"> <a class="action-checkout right" data-hafas="<%= param('hafas') // q{} %>" data-station="<%= $eva %>" data-force="1">
<i class="material-icons left" aria-hidden="true">gps_off</i> <i class="material-icons left" aria-hidden="true">gps_off</i>
Hier auschecken Hier auschecken
</a> </a>
@ -139,7 +140,7 @@
</p> </p>
% if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) { % if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) {
% if ($hafas) { % if ($hafas) {
%= include '_departures_hafas', results => $results; %= include '_departures_hafas', results => $results, hafas => $hafas;
% } % }
% else { % else {
%= include '_departures_iris', results => $results; %= include '_departures_iris', results => $results;

View file

@ -20,8 +20,15 @@
Timestamp: Timestamp:
%= DateTime->now(time_zone => 'Europe/Berlin')->strftime("%d/%b/%Y:%H:%M:%S %z") %= DateTime->now(time_zone => 'Europe/Berlin')->strftime("%d/%b/%Y:%H:%M:%S %z")
<br/><br/> <br/><br/>
Message: % if (ref($exception)) {
%= ref($exception) ? (split(qr{\n}, $exception->message))[0] : $exception Trace:<br/>
% for my $line (split(qr{\n}, $exception->message)) {
<%= $line %><br/>
% }
% }
% else {
Message: <%= $exception %>
% }
</p> </p>
</div> </div>
</div> </div>

View file

@ -1,5 +1,6 @@
% if (is_user_authenticated()) { % if (is_user_authenticated()) {
% my $status = stash('user_status'); % my $status = stash('user_status');
% my $user = stash('user');
% if (stash('error')) { % if (stash('error')) {
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
@ -51,32 +52,40 @@
% if ( @{stash('timeline') // [] } ) { % if ( @{stash('timeline') // [] } ) {
%= include '_timeline_link', timeline => stash('timeline') %= include '_timeline_link', timeline => stash('timeline')
% } % }
<div class="card"> %= form_for 'list_departures' => begin
<div class="card-content"> <div class="card">
<span class="card-title">Hallo, <%= current_user->{name} %>!</span> <div class="card-content">
<p>Du bist gerade nicht eingecheckt.</p> <span class="card-title">Hallo, <%= $user->{name} %>!</span>
<div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>"> <p>Du bist gerade nicht eingecheckt.</p>
<button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button> <p>
</div> <div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>" data-backend=<%= $user->{backend_id} %>">
%= form_for 'list_departures' => begin <button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button>
</div>
<div class="input-field"> <div class="input-field">
%= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef %= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef
<label for="station">Manuelle Eingabe</label> <label for="station">Manuelle Eingabe</label>
</div> </div>
<div class="center-align"> </p>
<button class="btn waves-effect waves-light btn-flat" type="submit" name="action" value="departures"> </div>
<i class="material-icons left" aria-hidden="true">send</i> <div class="card-action">
Abfahrten % if ($user->{backend_id}) {
</button> <a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %> (<%= $user->{backend_hafas} ? 'HAFAS' : q{} %>)</a>
</div> % }
%= end % else {
<a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true">train</i>DB (IRIS-TTS)</a>
% }
<button class="btn right waves-effect waves-light btn-flat" type="submit" name="action" value="departures">
<i class="material-icons left" aria-hidden="true">send</i>
Abfahrten
</button>
</div>
</div> </div>
</div> %= end
% } % }
</div> </div>
</div> </div>
<h2 style="margin-left: 0.75rem;">Letzte Fahrten</h2> <h2 style="margin-left: 0.75rem;">Letzte Fahrten</h2>
%= include '_history_trains', date_format => '%d.%m.%Y', journeys => [journeys->get(uid => current_user->{id}, limit => 5, with_datetime => 1)]; %= include '_history_trains', date_format => '%d.%m.%Y', journeys => [journeys->get(uid => $user->{id}, limit => 5, with_datetime => 1)];
% } % }
% else { % else {
<div class="row"> <div class="row">

View file

@ -62,7 +62,7 @@
%= javascript "/static/${av}/js/geolocation${min}.js" %= javascript "/static/${av}/js/geolocation${min}.js"
% } % }
% if (stash('with_autocomplete')) { % if (stash('with_autocomplete')) {
%= javascript "/dyn/${av}/autocomplete.js", defer => undef %= javascript "/dyn/${av}/autocomplete.js?backend_id=" . (stash('backend_id') // 1), defer => undef
% } % }
% if (stash('with_map')) { % if (stash('with_map')) {
%= javascript "/static/${av}/leaflet/leaflet.js" %= javascript "/static/${av}/leaflet/leaflet.js"

View file

@ -0,0 +1,34 @@
<div class="row">
<div class="col s12">
<h2>Backend auswählen</h2>
<p style="text-align: justify;">
Das ausgewählte Backend bestimmt die Datenquelle für Fahrten in travelynx.
<strong>DB (HAFAS)</strong> ist eine gute Wahl für Nah-, Regional- und Fernverkehr in Deutschland und (teilweise) Nachbarländern.
<strong>IRIS-TTS</strong> unterstützt ausschließlich Schienenverkehr; im Gegensatz zum HAFAS sind hier detaillierte Verspätungsgründe verfügbar.
Die restlichen Backends können sich für Fahrten außerhalb Deutschlands sowie in einzelnen innerdeutschen Verkehrsverbünden lohnen.
Fahrten im Ausland fehlen im DB-HAFAS oft; Fahrten im Inland können mit spezifischen Backends genauere Daten haben z.B. liefert das AVV-HAFAS im Gegensatz zum DB-HAFAS Kartendaten für dortige Buslinien.
</p>
</div>
</div>
%= form_for '/account/select_backend' => (method => 'POST') => begin
% if (stash('redirect_to')) {
%= hidden_field 'redirect_to' => stash('redirect_to')
% }
% for my $backend (@{ stash('backends') // [] }) {
<div class="row">
<div class="col s12 m4 l4 center-align">
<button class="btn waves-effect waves-light <%= $backend->{id} == $user->{backend_id} ? 'disabled' : q{} %>" type="submit" name="backend" value="<%= $backend->{id} %>">
<%= $backend->{name} %> (<%= $backend->{type} %>)
</button>
</div>
<div class="col s12 m8 l8">
% if ($backend->{longname}) {
<%= $backend->{longname} %>
% }
% if ($backend->{id} == $user->{backend_id}) {
(aktuell ausgewählt)
% }
</div>
</div>
% }
%= end

View file

@ -157,26 +157,21 @@
<div> <div>
<label> <label>
%= check_box toot => 1 %= check_box toot => 1
<span>… Checkin auf Mastodon veröffentlichen</span> <span>… Checkin im Fediverse veröffentlichen</span>
</label>
</div>
<div>
<label>
%= check_box tweet => 1
<span>… Checkin auf Twitter veröffentlichen</span>
</label> </label>
</div> </div>
<p>Die Synchronisierung erfolgt spätestens drei Minuten nach der <p>Die Synchronisierung erfolgt spätestens drei Minuten nach der
Zielwahl. Beachte, dass die Synchronisierung travelynx Zielwahl. Es werden ausschließlich Checkins mittels
→ Träwelling unabhängig von der eingestellten Sichtbarkeit DB (IRIS-TTS) und DB (HAFAS) synchornisiert. Beachte, dass
des Checkins erfolgt. travelynx reicht die Sichtbarkeit die Synchronisierung travelynx → Träwelling unabhängig von
aber an Träwelling weiter. der eingestellten Sichtbarkeit des Checkins erfolgt.
Träwelling-Checkins können von travelynx aktuell nicht travelynx reicht die Sichtbarkeit aber an Träwelling
rückgängig gemacht werden. Eine nachträgliche Änderung der weiter. Träwelling-Checkins können von travelynx aktuell
Zielstation wird nicht übernommen. Mastodon und Twitter beziehen nicht rückgängig gemacht werden. Eine nachträgliche
sich auf die in den <a Änderung der Zielstation wird nicht übernommen. Fediverse
bezieht sich auf den in den <a
href="https://traewelling.de/settings">Träwelling-Einstellungen</a> href="https://traewelling.de/settings">Träwelling-Einstellungen</a>
verknüpften Accounts.</p> verknüpften Account.</p>
</div> </div>
<div class="input-field col s12"> <div class="input-field col s12">
<div> <div>