Multi-backend support

Squashed commit of the following:

commit 92518024ba
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:39:46 2024 +0200

    add_or_update station: remove superfluos 'new backend id := old backend id'

commit df21c20c6e
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:35:51 2024 +0200

    revert connection targets min_count to 3

commit be335cef07
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:20:05 2024 +0200

    mention backend selection in API documentation

commit 9f41828fb4
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:19:23 2024 +0200

    use_history: not all backends provide route data in departure monitor

commit 09714b4d89
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:11:44 2024 +0200

    disambiguation: pass correct hafas parameter

commit 8cdf1120fc
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:11:28 2024 +0200

    _checked_in: hide Zuglauf link for non-db checkins

commit 7455653f54
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:01:47 2024 +0200

    debug output

commit b9cda07f85
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 19:09:07 2024 +0200

    fix remaining get_connection_targets / get_connecting_trains_p invocations

commit 2759d7258c
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Wed Jul 24 20:50:12 2024 +0200

    support non-DB HAFAS backends (WiP)
This commit is contained in:
Birte Kristina Friesel 2024-07-26 18:55:58 +02:00
parent 7811520a30
commit 47f76da4f8
No known key found for this signature in database
GPG key ID: B63118F7196EA660
33 changed files with 935 additions and 382 deletions

View file

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

View file

@ -8,6 +8,7 @@ use Mojo::Base 'Mojolicious::Command';
use DateTime;
use File::Slurp qw(read_file);
use JSON;
use Travel::Status::DE::HAFAS;
use Travel::Status::DE::IRIS::Stations;
has description => 'Initialize or upgrade database layout';
@ -1918,7 +1919,7 @@ my @migrations = (
# v49 -> v50
# travelynx 2.0 introduced proper HAFAS support, so there is no need for
# the 'FYI, here is some hAFAS data' kludge anymore.
# the 'FYI, here is some HAFAS data' kludge anymore.
sub {
my ($db) = @_;
$db->query(
@ -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 {
@ -2341,7 +2571,7 @@ sub sync_stations {
},
{
on_conflict => \
'(eva) do update set archived = false, source = 0, ds100 = EXCLUDED.ds100, name=EXCLUDED.name, lat=EXCLUDED.lat, lon=EXCLUDED.lon'
'(eva, source) do update set archived = false, source = 0, ds100 = EXCLUDED.ds100, name=EXCLUDED.name, lat=EXCLUDED.lat, lon=EXCLUDED.lon'
}
);
if ( $count++ % 1000 == 0 ) {
@ -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 {
my ($db) = @_;
my $tx = $db->begin;
@ -2566,9 +2816,9 @@ sub migrate_db {
}
my $iris_version = get_schema_version( $db, 'iris' );
say "Found IRIS station database v${iris_version}";
say "Found IRIS station table v${iris_version}";
if ( $iris_version eq $Travel::Status::DE::IRIS::Stations::VERSION ) {
say 'Station database is up-to-date';
say 'Station table is up-to-date';
}
else {
eval {
@ -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',
{ travelynx => $self->app->config->{version} } );

View file

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

View file

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

View file

@ -999,6 +999,52 @@ sub password_form {
$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 {
my ($self) = @_;
my $old_password = $self->req->param('oldpw');

View file

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

View file

@ -24,10 +24,15 @@ sub has_str_in_list {
return;
}
# when called with "eva" provided: look up connections from eva, either
# for provided backend_id / hafas or (if not provided) for user backend id.
# When calld without "eva": look up connections from current/latest arrival
# eva, using the checkin's backend id.
sub get_connecting_trains_p {
my ( $self, %opt ) = @_;
my $uid = $opt{uid} //= $self->current_user->{id};
my $user = $self->current_user;
my $uid = $opt{uid} //= $user->{id};
my $use_history = $self->users->use_history( uid => $uid );
my ( $eva, $exclude_via, $exclude_train_id, $exclude_before );
@ -43,10 +48,20 @@ sub get_connecting_trains_p {
elsif ( $opt{destination_name} ) {
$eva = $opt{eva};
}
if ( not defined $opt{backend_id} ) {
if ( $opt{hafas} ) {
$opt{backend_id}
= $self->stations->get_backend_id( hafas => $opt{hafas} );
}
else {
$opt{backend_id} = $user->{backend_id};
}
}
}
else {
if ( $use_history & 0x02 ) {
my $status = $self->get_user_status;
$opt{backend_id} = $status->{backend_id};
$eva = $status->{arr_eva};
$exclude_via = $status->{dep_name};
$exclude_train_id = $status->{train_id};
@ -65,10 +80,12 @@ sub get_connecting_trains_p {
return $promise->reject;
}
my ( $dest_ids, $destinations )
= $self->journeys->get_connection_targets(%opt);
$self->log->debug(
"get_connecting_trains_p(backend_id => $opt{backend_id}, eva => $eva)");
my @destinations = uniq_by { $_->{name} } @{$destinations};
my @destinations = $self->journeys->get_connection_targets(%opt);
@destinations = uniq_by { $_->{name} } @destinations;
if ($exclude_via) {
@destinations = grep { $_->{name} ne $exclude_via } @destinations;
@ -78,11 +95,8 @@ sub get_connecting_trains_p {
return $promise->reject;
}
my $iris_eva = $eva;
if ( $eva < 8000000 ) {
$iris_eva = ( List::Util::first { $_ >= 8000000 } @{$dest_ids} )
// $eva;
}
$self->log->debug( 'get_connection_targets returned '
. join( q{, }, map { $_->{name} } @destinations ) );
my $can_check_in = not $arr_epoch || ( $arr_countdown // 1 ) < 0;
my $lookahead
@ -91,11 +105,9 @@ sub get_connecting_trains_p {
my $iris_promise = Mojo::Promise->new;
my %via_count = map { $_->{name} => 0 } @destinations;
if ( $iris_eva >= 8000000
and List::Util::any { $_->{eva} >= 8000000 } @destinations )
{
if ( $opt{backend_id} == 0 ) {
$self->iris->get_departures_p(
station => $iris_eva,
station => $eva,
lookbehind => 10,
lookahead => $lookahead,
with_related => 1
@ -103,7 +115,7 @@ sub get_connecting_trains_p {
sub {
my ($stationboard) = @_;
if ( $stationboard->{errstr} ) {
$iris_promise->resolve( [] );
$promise->resolve( [], [] );
return;
}
@ -237,105 +249,30 @@ sub get_connecting_trains_p {
}
}
$iris_promise->resolve( [ @results, @cancellations ] );
$promise->resolve( [ @results, @cancellations ], [] );
return;
}
)->catch(
sub {
$iris_promise->resolve( [] );
$promise->resolve( [], [] );
return;
}
)->wait;
}
else {
$iris_promise->resolve( [] );
}
my $hafas_promise = Mojo::Promise->new;
$self->hafas->get_departures_p(
eva => $eva,
lookbehind => 10,
lookahead => $lookahead
)->then(
sub {
my ($status) = @_;
$hafas_promise->resolve( [ $status->results ] );
return;
}
)->catch(
sub {
# HAFAS data is optional.
# Errors are logged by get_json_p and can be silently ignored here.
$hafas_promise->resolve( [] );
return;
}
)->wait;
Mojo::Promise->all( $iris_promise, $hafas_promise )->then(
sub {
my ( $iris, $hafas ) = @_;
my @iris_trains = @{ $iris->[0] };
my @all_hafas_trains = @{ $hafas->[0] };
my @hafas_trains;
# We've already got a list of connecting trains; this function
# only adds further information to them. We ignore errors, as
# partial data is better than no data.
eval {
for my $iris_train (@iris_trains) {
if ( $iris_train->[0]->departure_is_cancelled ) {
for my $hafas_train (@all_hafas_trains) {
if ( $hafas_train->number
and $hafas_train->number
== $iris_train->[0]->train_no )
{
$hafas_train->{iris_seen} = 1;
next;
}
}
next;
}
for my $hafas_train (@all_hafas_trains) {
if ( $hafas_train->number
and $hafas_train->number
== $iris_train->[0]->train_no )
{
$hafas_train->{iris_seen} = 1;
if ( $hafas_train->load
and $hafas_train->load->{SECOND} )
{
$iris_train->[3] = $hafas_train->load;
}
for my $stop ( $hafas_train->route ) {
if ( $stop->loc->name
and $stop->loc->name eq
$iris_train->[1]->{name}
and $stop->arr )
{
$iris_train->[2] = $stop->arr;
if ( $iris_train->[0]->departure_delay
and not $stop->arr_delay )
{
$iris_train->[2]
->add( minutes => $iris_train->[0]
->departure_delay );
}
last;
}
}
last;
}
}
}
my $hafas_service
= $self->stations->get_hafas_name( backend_id => $opt{backend_id} );
$self->hafas->get_departures_p(
service => $hafas_service,
eva => $eva,
lookbehind => 10,
lookahead => $lookahead
)->then(
sub {
my ($status) = @_;
my @hafas_trains;
my @all_hafas_trains = $status->results;
for my $hafas_train (@all_hafas_trains) {
if ( $hafas_train->{iris_seen} ) {
next;
}
if ( $hafas_train->station_eva >= 8000000 ) {
# better safe than sorry, for now
next;
}
for my $stop ( $hafas_train->route ) {
for my $dest (@destinations) {
if ( $stop->loc->name
@ -353,30 +290,30 @@ sub get_connecting_trains_p {
}
if ( $departure->epoch >= $exclude_before ) {
$via_count{ $dest->{name} }++;
push( @hafas_trains,
[ $hafas_train, $dest, $arrival ] );
push(
@hafas_trains,
[
$hafas_train, $dest,
$arrival, $hafas_service
]
);
}
}
}
}
}
};
if ($@) {
$self->app->log->error(
"get_connecting_trains_p($uid): IRIS/HAFAS merge failed: $@"
);
$promise->resolve( [], \@hafas_trains );
return;
}
$promise->resolve( \@iris_trains, \@hafas_trains );
return;
}
)->catch(
sub {
my ($err) = @_;
$promise->reject($err);
return;
}
)->wait;
)->catch(
sub {
my ($err) = @_;
$self->log->debug("get_connection_trains: hafas: $err");
$promise->resolve( [], [] );
return;
}
)->wait;
}
return $promise;
}
@ -394,7 +331,8 @@ sub compute_effective_visibility {
sub homepage {
my ($self) = @_;
if ( $self->is_user_authenticated ) {
my $uid = $self->current_user->{id};
my $user = $self->current_user;
my $uid = $user->{id};
my $status = $self->get_user_status;
my @timeline = $self->in_transit->get_timeline(
uid => $uid,
@ -405,7 +343,7 @@ sub homepage {
if ( $status->{checked_in} ) {
my $journey_visibility
= $self->compute_effective_visibility(
$self->current_user->{default_visibility_str},
$user->{default_visibility_str},
$status->{visibility_str} );
if ( defined $status->{arrival_countdown}
and $status->{arrival_countdown} < ( 40 * 60 ) )
@ -416,6 +354,7 @@ sub homepage {
my ( $connections_iris, $connections_hafas ) = @_;
$self->render(
'landingpage',
user => $user,
user_status => $status,
journey_visibility => $journey_visibility,
connections_iris => $connections_iris,
@ -427,6 +366,7 @@ sub homepage {
sub {
$self->render(
'landingpage',
user => $user,
user_status => $status,
journey_visibility => $journey_visibility,
);
@ -438,6 +378,7 @@ sub homepage {
else {
$self->render(
'landingpage',
user => $user,
user_status => $status,
journey_visibility => $journey_visibility,
);
@ -451,10 +392,12 @@ sub homepage {
}
$self->render(
'landingpage',
user => $user,
user_status => $status,
recent_targets => \@recent_targets,
with_autocomplete => 1,
with_geolocation => 1
with_geolocation => 1,
backend_id => $user->{backend_id},
);
$self->users->mark_seen( uid => $uid );
}
@ -515,6 +458,7 @@ sub status_card {
elsif ( $status->{cancellation} ) {
$self->render_later;
$self->get_connecting_trains_p(
backend_id => $status->{backend_id},
eva => $status->{cancellation}{dep_eva},
destination_name => $status->{cancellation}{arr_name}
)->then(
@ -563,14 +507,63 @@ sub status_card {
sub geolocation {
my ($self) = @_;
my $lon = $self->param('lon');
my $lat = $self->param('lat');
my $lon = $self->param('lon');
my $lat = $self->param('lat');
my $backend_id = $self->param('backend') // 0;
if ( not $lon or not $lat ) {
if ( not $lon or not $lat or $backend_id !~ m{ ^ \d+ $ }x ) {
$self->render( json => { error => 'Invalid lon/lat received' } );
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 {
{
@ -580,7 +573,6 @@ sub geolocation {
lon => $_->[0][3],
lat => $_->[0][4],
distance => $_->[1],
hafas => 0,
}
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
$lat, 10 );
@ -588,48 +580,12 @@ sub geolocation {
if ( @iris > 5 ) {
@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 {
@ -684,6 +640,7 @@ sub travel_action {
$promise->then(
sub {
return $self->checkin_p(
hafas => $params->{hafas},
station => $params->{station},
train_id => $params->{train}
);
@ -713,8 +670,8 @@ sub travel_action {
my ( $still_checked_in, undef ) = @_;
if ( my $destination = $params->{dest} ) {
my $station_link = '/s/' . $destination;
if ( $status->{train_id} =~ m{[|]} ) {
$station_link .= '?hafas=DB';
if ( $status->{is_hafas} ) {
$station_link .= '?hafas=' . $status->{backend_name};
}
$self->render(
json => {
@ -749,8 +706,8 @@ sub travel_action {
sub {
my ( $still_checked_in, $error ) = @_;
my $station_link = '/s/' . $params->{station};
if ( $status->{train_id} =~ m{[|]} ) {
$station_link .= '?hafas=DB';
if ( $status->{is_hafas} ) {
$station_link .= '?hafas=' . $status->{backend_name};
}
if ($error) {
@ -800,8 +757,12 @@ sub travel_action {
else {
my $redir = '/';
if ( $status->{checked_in} or $status->{cancelled} ) {
if ( $status->{train_id} =~ m{[|]} ) {
$redir = '/s/' . $status->{dep_eva} . '?hafas=DB';
if ( $status->{is_hafas} ) {
$redir
= '/s/'
. $status->{dep_eva}
. '?hafas='
. $status->{backend_name};
}
else {
$redir = '/s/' . $status->{dep_ds100};
@ -818,6 +779,7 @@ sub travel_action {
elsif ( $params->{action} eq 'cancelled_from' ) {
$self->render_later;
$self->checkin_p(
hafas => $params->{hafas},
station => $params->{station},
train_id => $params->{train}
)->then(
@ -920,7 +882,8 @@ sub station {
my $train = $self->param('train');
my $trip_id = $self->param('trip_id');
my $timestamp = $self->param('timestamp');
my $uid = $self->current_user->{id};
my $user = $self->current_user;
my $uid = $user->{id};
my @timeline = $self->in_transit->get_timeline(
uid => $uid,
@ -928,7 +891,6 @@ sub station {
);
my %checkin_by_train;
for my $checkin (@timeline) {
say $checkin->{train_id};
push( @{ $checkin_by_train{ $checkin->{train_id} } }, $checkin );
}
$self->stash( checkin_by_train => \%checkin_by_train );
@ -945,10 +907,12 @@ sub station {
$timestamp = DateTime->now( time_zone => 'Europe/Berlin' );
}
my $use_hafas = $self->param('hafas');
my $hafas_service = $self->param('hafas')
// ( $user->{backend_hafas} ? $user->{backend_name} : undef );
my $promise;
if ($use_hafas) {
if ($hafas_service) {
$promise = $self->hafas->get_departures_p(
service => $hafas_service,
eva => $station,
timestamp => $timestamp,
lookbehind => 30,
@ -966,27 +930,21 @@ sub station {
$promise->then(
sub {
my ($status) = @_;
my $api_link;
my @results;
my $now = $self->now->epoch;
my $now_within_range
= abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0;
if ($use_hafas) {
my $iris_eva = List::Util::min grep { $_ >= 1000000 }
@{ $status->station->{evas} // [] };
if ($iris_eva) {
$api_link = '/s/' . $iris_eva;
}
if ($hafas_service) {
@results = map { $_->[0] }
sort { $b->[1] <=> $a->[1] }
map { [ $_, $_->datetime->epoch ] } $status->results;
$self->stations->add_meta(
eva => $status->station->{eva},
meta => $status->station->{evas} // []
eva => $status->station->{eva},
meta => $status->station->{evas} // [],
hafas => $hafas_service,
);
$status = {
station_eva => $status->station->{eva},
@ -999,8 +957,6 @@ sub station {
}
else {
$api_link = '/s/' . $status->{station_eva} . '?hafas=DB';
# You can't check into a train which terminates here
@results = grep { $_->departure } @{ $status->{results} };
@ -1029,10 +985,10 @@ sub station {
}
my $connections_p;
if ( $trip_id and $use_hafas ) {
if ( $trip_id and $hafas_service ) {
@results = grep { $_->id eq $trip_id } @results;
}
elsif ( $train and not $use_hafas ) {
elsif ( $train and not $hafas_service ) {
@results
= grep { $_->type . ' ' . $_->train_no eq $train } @results;
}
@ -1044,12 +1000,15 @@ sub station {
$connections_p = $self->get_connecting_trains_p(
eva => $user_status->{cancellation}{dep_eva},
destination_name =>
$user_status->{cancellation}{arr_name}
$user_status->{cancellation}{arr_name},
hafas => $hafas_service,
);
}
else {
$connections_p = $self->get_connecting_trains_p(
eva => $status->{station_eva} );
eva => $status->{station_eva},
hafas => $hafas_service
);
}
}
@ -1059,18 +1018,18 @@ sub station {
my ( $connections_iris, $connections_hafas ) = @_;
$self->render(
'departures',
user => $user,
hafas => $hafas_service,
eva => $status->{station_eva},
datetime => $timestamp,
now_in_range => $now_within_range,
results => \@results,
hafas => $use_hafas,
station => $status->{station_name},
related_stations => $status->{related_stations},
user_status => $user_status,
can_check_out => $can_check_out,
connections_iris => $connections_iris,
connections_hafas => $connections_hafas,
api_link => $api_link,
title => "travelynx: $status->{station_name}",
);
}
@ -1078,16 +1037,16 @@ sub station {
sub {
$self->render(
'departures',
user => $user,
hafas => $hafas_service,
eva => $status->{station_eva},
datetime => $timestamp,
now_in_range => $now_within_range,
results => \@results,
hafas => $use_hafas,
station => $status->{station_name},
related_stations => $status->{related_stations},
user_status => $user_status,
can_check_out => $can_check_out,
api_link => $api_link,
title => "travelynx: $status->{station_name}",
);
}
@ -1096,16 +1055,16 @@ sub station {
else {
$self->render(
'departures',
user => $user,
hafas => $hafas_service,
eva => $status->{station_eva},
datetime => $timestamp,
now_in_range => $now_within_range,
results => \@results,
hafas => $use_hafas,
station => $status->{station_name},
related_stations => $status->{related_stations},
user_status => $user_status,
can_check_out => $can_check_out,
api_link => $api_link,
title => "travelynx: $status->{station_name}",
);
}
@ -1120,15 +1079,22 @@ sub station {
status => 300,
);
}
elsif ( $use_hafas and $status and $status->errcode eq 'LOCATION' )
elsif ( $hafas_service
and $status
and $status->errcode eq 'LOCATION' )
{
$self->hafas->search_location_p( query => $station )->then(
$self->hafas->search_location_p(
service => $hafas_service,
query => $station
)->then(
sub {
my ($hafas2) = @_;
my @suggestions = $hafas2->results;
if ( @suggestions == 1 ) {
$self->redirect_to(
'/s/' . $suggestions[0]->eva . '?hafas=DB' );
$self->redirect_to( '/s/'
. $suggestions[0]->eva
. '?hafas='
. $hafas_service );
}
else {
$self->render(
@ -1169,17 +1135,7 @@ sub redirect_to_station {
my ($self) = @_;
my $station = $self->param('station');
if ( my $s = $self->app->stations->search($station) ) {
if ( $s->{source} == 1 ) {
$self->redirect_to("/s/${station}?hafas=DB");
}
else {
$self->redirect_to("/s/${station}");
}
}
else {
$self->redirect_to("/s/${station}?hafas=DB");
}
$self->redirect_to("/s/${station}");
}
sub cancelled {

View file

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

View file

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

View file

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

View file

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

View file

@ -545,7 +545,7 @@ sub get {
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 = (
user_id => $uid,
@ -606,6 +606,7 @@ sub get {
is_iris => $entry->{is_iris},
is_hafas => $entry->{is_hafas},
backend_name => $entry->{backend_name},
backend_id => $entry->{backend_id},
type => $entry->{train_type},
line => $entry->{train_line},
no => $entry->{train_no},
@ -665,7 +666,10 @@ sub get {
my $rename = $self->{renamed_station};
for my $stop ( @{ $ref->{route} } ) {
if ( $stop->[0] =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) {
if ( my $s = $self->{stations}->get_by_eva($1) ) {
if ( my $s
= $self->{stations}
->get_by_eva( $1, backend_id => $ref->{backend_id} ) )
{
$stop->[0] = $s->{name};
}
}
@ -800,14 +804,14 @@ sub get_oldest_ts {
return undef;
}
sub get_latest_checkout_station_id {
sub get_latest_checkout_ids {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db;
my $res_h = $db->select(
'journeys',
['checkout_station_id'],
[ 'checkout_station_id', 'backend_id', ],
{
user_id => $uid,
cancelled => 0
@ -822,7 +826,7 @@ sub get_latest_checkout_station_id {
return;
}
return $res_h->{checkout_station_id};
return $res_h->{checkout_station_id}, $res_h->{backend_id};
}
sub get_latest_checkout_stations {
@ -833,7 +837,10 @@ sub get_latest_checkout_stations {
my $res = $db->select(
'journeys_str',
[ 'arr_name', 'arr_eva', 'train_id' ],
[
'arr_name', 'arr_eva', 'train_id', 'backend_id',
'backend_name', 'is_hafas'
],
{
user_id => $uid,
cancelled => 0
@ -854,9 +861,10 @@ sub get_latest_checkout_stations {
push(
@ret,
{
name => $row->{arr_name},
eva => $row->{arr_eva},
hafas => ( $row->{train_id} =~ m{[|]} ? 'DB' : 0 ),
name => $row->{arr_name},
eva => $row->{arr_eva},
hafas => $row->{is_hafas} ? $row->{backend_name} : 0,
backend_id => $row->{backend_id},
}
);
}
@ -1726,28 +1734,29 @@ sub get_stats {
return $stats;
}
sub get_latest_dest_id {
sub get_latest_dest_ids {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db;
if (
my $id = $self->{in_transit}->get_checkout_station_id(
my ( $id, $backend_id ) = $self->{in_transit}->get_checkout_ids(
uid => $uid,
db => $db
)
)
{
return $id;
return ( $id, $backend_id );
}
return $self->get_latest_checkout_station_id(
return $self->get_latest_checkout_ids(
uid => $uid,
db => $db
);
}
# Returns a listref of {eva, name} hashrefs for the specified backend.
sub get_connection_targets {
my ( $self, %opt ) = @_;
@ -1756,21 +1765,29 @@ sub get_connection_targets {
// DateTime->now( time_zone => 'Europe/Berlin' )->subtract( months => 4 );
my $db = $opt{db} //= $self->{pg}->db;
my $min_count = $opt{min_count} // 3;
my $dest_id = $opt{eva};
if ( $opt{destination_name} ) {
return (
[],
[ { eva => $opt{eva}, name => $opt{destination_name} } ]
);
return [ { eva => $opt{eva}, name => $opt{destination_name} } ];
}
my $dest_id = $opt{eva} // $self->get_latest_dest_id(%opt);
my $backend_id = $opt{backend_id};
if ( not $dest_id ) {
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(
'journeys',
@ -1778,7 +1795,8 @@ sub get_connection_targets {
{
user_id => $uid,
checkin_station_id => $dest_ids,
real_departure => { '>', $threshold }
real_departure => { '>', $threshold },
backend_id => $opt{backend_id},
},
{
group_by => ['checkout_station_id'],
@ -1788,8 +1806,11 @@ sub get_connection_targets {
my @destinations
= $res->hashes->grep( sub { shift->{count} >= $min_count } )
->map( sub { shift->{dest} } )->each;
@destinations = $self->{stations}->get_by_evas(@destinations);
return ( $dest_ids, \@destinations );
@destinations = $self->{stations}->get_by_evas(
backend_id => $opt{backend_id},
evas => [@destinations]
);
return @destinations;
}
sub update_visibility {

View file

@ -14,38 +14,125 @@ sub new {
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 {
my ( $self, %opt ) = @_;
my $stop = $opt{stop};
my $loc = $stop->loc;
my $source = 1;
my $db = $opt{db} // $self->{pg}->db;
my $stop = $opt{stop};
my $loc = $stop->loc;
$opt{db} //= $self->{pg}->db;
if ( my $s = $self->get_by_eva( $loc->eva, db => $db ) ) {
if ( $source == 1 and $s->{source} == 0 and not $s->{archived} ) {
return;
}
$db->update(
$opt{backend_id} //= $self->get_backend_id(%opt);
if (
my $s = $self->get_by_eva(
$loc->eva,
db => $opt{db},
backend_id => $opt{backend_id}
)
)
{
$opt{db}->update(
'stations',
{
name => $loc->name,
lat => $loc->lat,
lon => $loc->lon,
source => $source,
archived => 0
},
{ eva => $loc->eva }
{
eva => $loc->eva,
source => $opt{backend_id}
}
);
return;
}
$db->insert(
$opt{db}->insert(
'stations',
{
eva => $loc->eva,
name => $loc->name,
lat => $loc->lat,
lon => $loc->lon,
source => $source,
source => $opt{backend_id},
archived => 0
}
);
@ -53,17 +140,20 @@ sub add_or_update {
sub add_meta {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $eva = $opt{eva};
my @meta = @{ $opt{meta} };
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
for my $meta (@meta) {
if ( $meta != $eva ) {
$db->insert(
$opt{db}->insert(
'related_stations',
{
eva => $eva,
meta => $meta
eva => $eva,
meta => $meta,
backend_id => $opt{backend_id},
},
{ on_conflict => undef }
);
@ -82,7 +172,16 @@ sub get_meta {
my $db = $opt{db} // $self->{pg}->db;
my $eva = $opt{eva};
my $res = $db->select( 'related_stations', ['meta'], { eva => $eva } );
$opt{backend_id} //= $self->get_backend_id( %opt, db => $db );
my $res = $db->select(
'related_stations',
['meta'],
{
eva => $eva,
backend_id => $opt{backend_id}
}
);
my @ret;
while ( my $row = $res->hash ) {
@ -93,9 +192,12 @@ sub get_meta {
}
sub get_for_autocomplete {
my ($self) = @_;
my ( $self, %opt ) = @_;
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;
while ( my $row = $res->hash ) {
@ -113,18 +215,34 @@ sub get_by_eva {
return;
}
my $db = $opt{db} // $self->{pg}->db;
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
return $db->select( 'stations', '*', { eva => $eva } )->hash;
return $opt{db}->select(
'stations',
'*',
{
eva => $eva,
source => $opt{backend_id}
}
)->hash;
}
# Fast
sub get_by_evas {
my ( $self, @evas ) = @_;
my ( $self, %opt ) = @_;
my @ret
= $self->{pg}->db->select( 'stations', '*', { eva => { '=', \@evas } } )
->hashes->each;
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
my @ret = $self->{pg}->db->select(
'stations',
'*',
{
eva => { '=', $opt{evas} },
source => $opt{backend_id}
}
)->hashes->each;
return @ret;
}
@ -132,10 +250,18 @@ sub get_by_evas {
sub get_by_name {
my ( $self, $name, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
return $db->select( 'stations', '*', { name => $name }, { limit => 1 } )
->hash;
return $opt{db}->select(
'stations',
'*',
{
name => $name,
source => $opt{backend_id}
},
{ limit => 1 }
)->hash;
}
# Slow
@ -152,16 +278,27 @@ sub get_by_names {
sub get_by_ds100 {
my ( $self, $ds100, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
return $db->select( 'stations', '*', { ds100 => $ds100 }, { limit => 1 } )
->hash;
return $opt{db}->select(
'stations',
'*',
{
ds100 => $ds100,
source => $opt{backend_id}
},
{ limit => 1 }
)->hash;
}
# Can be slow
sub search {
my ( $self, $identifier, %opt ) = @_;
$opt{db} //= $self->{pg}->db;
$opt{backend_id} //= $self->get_backend_id(%opt);
if ( $identifier =~ m{ ^ \d+ $ }x ) {
return $self->get_by_eva( $identifier, %opt )
// $self->get_by_ds100( $identifier, %opt )

View file

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

View file

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

View file

@ -62,7 +62,8 @@ $(document).ready(function() {
};
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) {

View file

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

View file

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

View file

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

View file

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

View file

@ -343,9 +343,14 @@
% else {
% $url = $url . $journey->{train_type} . ' ' . $journey->{train_no} . '/' . $journey->{sched_departure}->epoch . '000?station=' . $journey->{dep_eva};
% }
<a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a>
% if ($journey->{backend_id} <= 1) {
<a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a>
% }
% else {
&nbsp;
% }
% 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>

View file

@ -3,7 +3,7 @@
<span class="card-title">Ausgecheckt</span>
<p>Aus
%= 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') // []}) {
<span class="card-title" style="margin-top: 2ex;">Verbindungen</span>
<p>Fahrt auswählen zum Einchecken mit Zielwahl.</p>

View file

@ -1,6 +1,6 @@
<ul class="collection departures 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{};
% my $row_class = '';
% my $link_class = 'action-checkin';
@ -10,6 +10,7 @@
% }
% if ($checkin_from) {
<li class="collection-item <%= $row_class %> <%= $link_class %>"
data-hafas="<%= $hafas_service %>"
data-station="<%= $train->station_eva %>"
data-train="<%= $train->id %>"
data-ts="<%= ($train->sched_datetime // $train->datetime)->epoch %>"

View file

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

View file

@ -30,6 +30,11 @@
"actionTime" : 1234567, (UNIX-Timestamp des letzten Checkin/Checkout)<br/>
"checkedIn" : true / false,<br/>
"comment": "Kommentar",<br/>
"backend": {<br/>
"id": 1,<br/>
"name": "DB",<br/>
"type": "HAFAS",<br/>
},<br/>
"fromStation" : { (letzter Checkin)<br/>
"name" : "Essen Hbf",<br/>
"ds100" : "EE", (ggf. null)<br/>
@ -122,6 +127,7 @@
{<br/>
"token" : "<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>",<br/>
"action" : "checkin",<br/>
"hafas" : "DB", (HAFAS-Instanz Default: Deutsche Bahn)<br/>
"train" : {<br/>
"journeyID" : "1|1426396|4|80|19082023",<br/>
}<br/>

View file

@ -1,26 +1,27 @@
<div class="row">
<div class="col s12">
<h2>
<i class="material-icons " aria-hidden="true"><%= param('hafas') ? 'directions' : 'train' %></i>
<div class="col s8">
<strong style="font-size: 120%;">
<%= $station %>
</h2>
</strong>
% for my $related_station (sort { $a->{name} cmp $b->{name} } @{$related_stations}) {
+ <%= $related_station->{name} %> <br/>
% }
</div>
</div>
% if ($api_link) {
<div class="row">
<div class="col s12 center-align">
% if (param('hafas')) {
<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">train</i>zum Schienenverkehr</a>
% }
% else {
<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">directions</i>zum Nahverkehr</a>
% }
<div class="col s4 center-align">
% my $self_link = url_for('sstation', station => param('station'));
% if (param('hafas')) {
<span class="btn-small disabled"><i class="material-icons left" aria-hidden="true">directions</i> <%= param('hafas') %> (HAFAS)</span>
% }
% else {
% if ($user->{backend_id}) {
<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>
% }
% 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>
% }
% my $have_connections = 0;
% if ($user_status->{checked_in}) {
@ -40,10 +41,10 @@
</div>
<div class="card-action">
% 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
</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
</a>
% }
@ -51,7 +52,7 @@
<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
</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>
Hier auschecken
</a>
@ -139,7 +140,7 @@
</p>
% if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) {
% if ($hafas) {
%= include '_departures_hafas', results => $results;
%= include '_departures_hafas', results => $results, hafas => $hafas;
% }
% else {
%= include '_departures_iris', results => $results;

View file

@ -13,7 +13,7 @@
<div class="col s12">
<ul class="suggestions">
% for my $suggestion (@{$suggestions // []}) {
<li><a href="<%= url_for('station' => $suggestion->{eva}) . (param('hafas') ? '?hafas=DB' : q{}) %>"><%= $suggestion->{name} %></a></li>
<li><a href="<%= url_for('station' => $suggestion->{eva}) . (param('hafas') ? '?hafas=' . param('hafas') : q{}) %>"><%= $suggestion->{name} %></a></li>
% }
</ul>
</div>

View file

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

View file

@ -1,5 +1,6 @@
% if (is_user_authenticated()) {
% my $status = stash('user_status');
% my $user = stash('user');
% if (stash('error')) {
<div class="row">
<div class="col s12">
@ -51,32 +52,40 @@
% if ( @{stash('timeline') // [] } ) {
%= include '_timeline_link', timeline => stash('timeline')
% }
<div class="card">
<div class="card-content">
<span class="card-title">Hallo, <%= current_user->{name} %>!</span>
<p>Du bist gerade nicht eingecheckt.</p>
<div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>">
<button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button>
</div>
%= form_for 'list_departures' => begin
%= form_for 'list_departures' => begin
<div class="card">
<div class="card-content">
<span class="card-title">Hallo, <%= $user->{name} %>!</span>
<p>Du bist gerade nicht eingecheckt.</p>
<p>
<div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>" data-backend=<%= $user->{backend_id} %>">
<button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button>
</div>
<div class="input-field">
%= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef
<label for="station">Manuelle Eingabe</label>
</div>
<div class="center-align">
<button class="btn 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>
%= end
</p>
</div>
<div class="card-action">
% if ($user->{backend_id}) {
<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>
% }
% 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>
%= end
% }
</div>
</div>
<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 {
<div class="row">

View file

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

View file

@ -5,9 +5,13 @@
Travelynx kann anhand deiner vergangenen Fahrten Verbindungen zum
Einchecken vorschlagen. Fährst zu z.B regelmäßig von Dortmund Hbf
nach Essen Hbf, werden dir in Dortmund bevorzugt Fahrten angezeigt, die
Essen passieren. Bei Auswahl dieser wird nicht nur in die Fahrt eingecheckt,
Essen passieren. Bei Auswahl dieser wird nicht nur in die Fahrt eingecheckt,
sondern auch direkt Essen Hbf als Ziel eingetragen.
<p/>
<p>
Beachte, dass nicht alle von travelynx unterstützten Backends die
für dieses Feature notwendigen Daten bereitstellen.
</p>
<!-- <p>
Falls du das nicht nützlich findest oder nicht möchtest, dass deine
regelmäßigen (Anschluss-)Züge auf deinem Bildschirm sichtbar sind,