Multi-backend support

Squashed commit of the following:

commit 92518024ba295456358618c0e8180bd8e996fdf1
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 df21c20c6e4c86454f8a9ac69121404415217f2a
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 be335cef07d0b42874f5fc1de4a1d13396e8e807
Author: Birte Kristina Friesel <birte.friesel@uos.de>
Date:   Fri Jul 26 18:20:05 2024 +0200

    mention backend selection in API documentation

commit 9f41828fb4f18fd707e0087def3032e8d4c8d7d8
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 09714b4d89684b8331d0e96f564a4c7432318f70
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:11:44 2024 +0200

    disambiguation: pass correct hafas parameter

commit 8cdf1120fc32155dc6525be64601b7c10a9c7f52
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 7455653f541198e0e0a6d11aed421487ffdb6285
Author: Birte Kristina Friesel <derf@finalrewind.org>
Date:   Thu Jul 25 20:01:47 2024 +0200

    debug output

commit b9cda07f85601a58ea32dbdacdd5399f302db52b
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 2759d7258c37c7498905cfe19f6b4c4f6d16bd21
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'); 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);
} }
@ -483,6 +483,8 @@ sub startup {
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(
@ -553,6 +556,7 @@ sub startup {
$self->stations->add_or_update( $self->stations->add_or_update(
stop => $stop, stop => $stop,
db => $db, db => $db,
hafas => $opt{hafas},
); );
} }
eval { eval {
@ -561,7 +565,10 @@ sub startup {
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,
@ -1834,6 +1854,11 @@ sub startup {
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

@ -24,10 +24,15 @@ sub has_str_in_list {
return; 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 { 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 +48,20 @@ 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,10 +80,12 @@ sub get_connecting_trains_p {
return $promise->reject; return $promise->reject;
} }
my ( $dest_ids, $destinations ) $self->log->debug(
= $self->journeys->get_connection_targets(%opt); "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) { if ($exclude_via) {
@destinations = grep { $_->{name} ne $exclude_via } @destinations; @destinations = grep { $_->{name} ne $exclude_via } @destinations;
@ -78,11 +95,8 @@ sub get_connecting_trains_p {
return $promise->reject; return $promise->reject;
} }
my $iris_eva = $eva; $self->log->debug( 'get_connection_targets returned '
if ( $eva < 8000000 ) { . join( q{, }, map { $_->{name} } @destinations ) );
$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 +105,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 +115,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 +249,30 @@ 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( [] ); my $hafas_service
} = $self->stations->get_hafas_name( backend_id => $opt{backend_id} );
my $hafas_promise = Mojo::Promise->new;
$self->hafas->get_departures_p( $self->hafas->get_departures_p(
service => $hafas_service,
eva => $eva, eva => $eva,
lookbehind => 10, lookbehind => 10,
lookahead => $lookahead lookahead => $lookahead
)->then( )->then(
sub { sub {
my ($status) = @_; 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; my @hafas_trains;
my @all_hafas_trains = $status->results;
# 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) { 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) {
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 +290,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, $hafas_service
} ]
}
};
if ($@) {
$self->app->log->error(
"get_connecting_trains_p($uid): IRIS/HAFAS merge failed: $@"
); );
} }
}
$promise->resolve( \@iris_trains, \@hafas_trains ); }
}
}
$promise->resolve( [], \@hafas_trains );
return; return;
} }
)->catch( )->catch(
sub { sub {
my ($err) = @_; my ($err) = @_;
$promise->reject($err); $self->log->debug("get_connection_trains: hafas: $err");
$promise->resolve( [], [] );
return; return;
} }
)->wait; )->wait;
}
return $promise; return $promise;
} }
@ -394,7 +331,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,7 +343,7 @@ 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 ) )
@ -416,6 +354,7 @@ sub homepage {
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 +366,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 +378,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 +392,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 );
} }
@ -515,6 +458,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(
@ -565,33 +509,23 @@ sub geolocation {
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 @iris = map { my $hafas_service
{ = $self->stations->get_hafas_name( backend_id => $backend_id );
ds100 => $_->[0][0],
name => $_->[0][1], if ($hafas_service) {
eva => $_->[0][2], $self->render_later;
lon => $_->[0][3],
lat => $_->[0][4],
distance => $_->[1],
hafas => 0,
}
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
$lat, 10 );
@iris = uniq_by { $_->{name} } @iris;
if ( @iris > 5 ) {
@iris = @iris[ 0 .. 4 ];
}
Travel::Status::DE::HAFAS->new_p( Travel::Status::DE::HAFAS->new_p(
promise => 'Mojo::Promise', promise => 'Mojo::Promise',
user_agent => $self->ua, user_agent => $self->ua,
service => $hafas_service,
geoSearch => { geoSearch => {
lat => $lat, lat => $lat,
lon => $lon lon => $lon
@ -604,18 +538,15 @@ sub geolocation {
name => $_->name, name => $_->name,
eva => $_->eva, eva => $_->eva,
distance => $_->distance_m / 1000, distance => $_->distance_m / 1000,
hafas => 'DB' hafas => $hafas_service
} }
} $hafas->results; } $hafas->results;
if ( @hafas > 10 ) { if ( @hafas > 10 ) {
@hafas = @hafas[ 0 .. 9 ]; @hafas = @hafas[ 0 .. 9 ];
} }
my @results = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [ $_, $_->{distance} ] } ( @iris, @hafas );
$self->render( $self->render(
json => { json => {
candidates => [@results], candidates => [@hafas],
} }
); );
} }
@ -624,12 +555,37 @@ sub geolocation {
my ($err) = @_; my ($err) = @_;
$self->render( $self->render(
json => { json => {
candidates => [@iris], candidates => [],
warning => $err, warning => $err,
} }
); );
} }
)->wait; )->wait;
return;
}
my @iris = map {
{
ds100 => $_->[0][0],
name => $_->[0][1],
eva => $_->[0][2],
lon => $_->[0][3],
lat => $_->[0][4],
distance => $_->[1],
}
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
$lat, 10 );
@iris = uniq_by { $_->{name} } @iris;
if ( @iris > 5 ) {
@iris = @iris[ 0 .. 4 ];
}
$self->render(
json => {
candidates => [@iris],
}
);
} }
sub travel_action { sub travel_action {
@ -684,6 +640,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 +670,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 +706,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 +757,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 +779,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 +882,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 +891,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 +907,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 +930,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 +957,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 +985,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;
} }
@ -1044,12 +1000,15 @@ sub station {
$connections_p = $self->get_connecting_trains_p( $connections_p = $self->get_connecting_trains_p(
eva => $user_status->{cancellation}{dep_eva}, eva => $user_status->{cancellation}{dep_eva},
destination_name => destination_name =>
$user_status->{cancellation}{arr_name} $user_status->{cancellation}{arr_name},
hafas => $hafas_service,
); );
} }
else { else {
$connections_p = $self->get_connecting_trains_p( $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 ) = @_; 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 +1037,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 +1055,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 +1079,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,18 +1135,8 @@ 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) ) {
if ( $s->{source} == 1 ) {
$self->redirect_to("/s/${station}?hafas=DB");
}
else {
$self->redirect_to("/s/${station}"); $self->redirect_to("/s/${station}");
} }
}
else {
$self->redirect_to("/s/${station}?hafas=DB");
}
}
sub cancelled { sub cancelled {
my ($self) = @_; my ($self) = @_;

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

@ -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,10 @@ 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 +863,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 +1734,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 ) = @_;
@ -1756,21 +1765,29 @@ sub get_connection_targets {
// 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} // 3;
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 +1795,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 +1806,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,125 @@ 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,
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 +140,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 +172,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 +192,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 +215,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 +250,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 +278,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

@ -343,9 +343,14 @@
% else { % else {
% $url = $url . $journey->{train_type} . ' ' . $journey->{train_no} . '/' . $journey->{sched_departure}->epoch . '000?station=' . $journey->{dep_eva}; % $url = $url . $journey->{train_type} . ' ' . $journey->{train_no} . '/' . $journey->{sched_departure}->epoch . '000?station=' . $journey->{dep_eva};
% } % }
% if ($journey->{backend_id} <= 1) {
<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>
% }
% else {
&nbsp;
% }
% 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

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

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">
<div class="col s12 center-align">
% if (param('hafas')) { % if (param('hafas')) {
<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">train</i>zum Schienenverkehr</a> <span class="btn-small disabled"><i class="material-icons left" aria-hidden="true">directions</i> <%= param('hafas') %> (HAFAS)</span>
% } % }
% else { % else {
<a href="<%= $api_link %>" class="btn-small"><i class="material-icons left" aria-hidden="true">directions</i>zum Nahverkehr</a> % 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>
</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

@ -13,7 +13,7 @@
<div class="col s12"> <div class="col s12">
<ul class="suggestions"> <ul class="suggestions">
% for my $suggestion (@{$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> </ul>
</div> </div>

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')
% } % }
%= form_for 'list_departures' => begin
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<span class="card-title">Hallo, <%= current_user->{name} %>!</span> <span class="card-title">Hallo, <%= $user->{name} %>!</span>
<p>Du bist gerade nicht eingecheckt.</p> <p>Du bist gerade nicht eingecheckt.</p>
<div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>"> <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> <button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button>
</div> </div>
%= form_for 'list_departures' => begin
<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>
<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> <i class="material-icons left" aria-hidden="true">send</i>
Abfahrten Abfahrten
</button> </button>
</div> </div>
</div>
%= end %= end
</div>
</div>
% } % }
</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>

View file

@ -8,6 +8,10 @@
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. sondern auch direkt Essen Hbf als Ziel eingetragen.
<p/> <p/>
<p>
Beachte, dass nicht alle von travelynx unterstützten Backends die
für dieses Feature notwendigen Daten bereitstellen.
</p>
<!-- <p> <!-- <p>
Falls du das nicht nützlich findest oder nicht möchtest, dass deine Falls du das nicht nützlich findest oder nicht möchtest, dass deine
regelmäßigen (Anschluss-)Züge auf deinem Bildschirm sichtbar sind, regelmäßigen (Anschluss-)Züge auf deinem Bildschirm sichtbar sind,