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');
}
if ( $train_id =~ m{[|]} ) {
if ( $opt{hafas} ) {
return $self->_checkin_hafas_p(%opt);
}
@ -483,6 +483,8 @@ sub startup {
departure_eva => $eva,
train => $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(
@ -553,6 +556,7 @@ sub startup {
$self->stations->add_or_update(
stop => $stop,
db => $db,
hafas => $opt{hafas},
);
}
eval {
@ -561,7 +565,10 @@ sub startup {
db => $db,
journey => $journey,
stop => $found,
data => { trip_id => $journey->id }
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,
@ -1834,6 +1854,11 @@ sub startup {
or $status->{cancelled}
) ? \1 : \0,
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;
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) = @_;
$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 ) {
my @all_hafas_trains = $status->results;
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 $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 ] );
}
}
}
}
}
};
if ($@) {
$self->app->log->error(
"get_connecting_trains_p($uid): IRIS/HAFAS merge failed: $@"
push(
@hafas_trains,
[
$hafas_train, $dest,
$arrival, $hafas_service
]
);
}
$promise->resolve( \@iris_trains, \@hafas_trains );
}
}
}
}
$promise->resolve( [], \@hafas_trains );
return;
}
)->catch(
sub {
my ($err) = @_;
$promise->reject($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(
@ -565,33 +509,23 @@ sub geolocation {
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 @iris = map {
{
ds100 => $_->[0][0],
name => $_->[0][1],
eva => $_->[0][2],
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 ];
}
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
@ -604,18 +538,15 @@ sub geolocation {
name => $_->name,
eva => $_->eva,
distance => $_->distance_m / 1000,
hafas => 'DB'
hafas => $hafas_service
}
} $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],
candidates => [@hafas],
}
);
}
@ -624,12 +555,37 @@ sub geolocation {
my ($err) = @_;
$self->render(
json => {
candidates => [@iris],
candidates => [],
warning => $err,
}
);
}
)->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 {
@ -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} // []
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");
}
}
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

@ -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
@ -856,7 +863,8 @@ sub get_latest_checkout_stations {
{
name => $row->{arr_name},
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;
}
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;
$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
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};
% }
% 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">
<div class="col s4 center-align">
% my $self_link = url_for('sstation', station => param('station'));
% 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 {
<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>
% }
% 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')
% }
%= form_for 'list_departures' => begin
<div class="card">
<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>
<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>
</div>
%= form_for 'list_departures' => begin
<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">
</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>
%= end
</div>
</div>
% }
</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

@ -8,6 +8,10 @@
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,