From 47f76da4f8cc31146d2834dfdf9731d330288c9d Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Fri, 26 Jul 2024 18:55:58 +0200 Subject: Multi-backend support Squashed commit of the following: commit 92518024ba295456358618c0e8180bd8e996fdf1 Author: Birte Kristina Friesel 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 Date: Fri Jul 26 18:35:51 2024 +0200 revert connection targets min_count to 3 commit be335cef07d0b42874f5fc1de4a1d13396e8e807 Author: Birte Kristina Friesel Date: Fri Jul 26 18:20:05 2024 +0200 mention backend selection in API documentation commit 9f41828fb4f18fd707e0087def3032e8d4c8d7d8 Author: Birte Kristina Friesel 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 Date: Thu Jul 25 20:11:44 2024 +0200 disambiguation: pass correct hafas parameter commit 8cdf1120fc32155dc6525be64601b7c10a9c7f52 Author: Birte Kristina Friesel Date: Thu Jul 25 20:11:28 2024 +0200 _checked_in: hide Zuglauf link for non-db checkins commit 7455653f541198e0e0a6d11aed421487ffdb6285 Author: Birte Kristina Friesel Date: Thu Jul 25 20:01:47 2024 +0200 debug output commit b9cda07f85601a58ea32dbdacdd5399f302db52b Author: Birte Kristina Friesel 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 Date: Wed Jul 24 20:50:12 2024 +0200 support non-DB HAFAS backends (WiP) --- lib/Travelynx.pm | 66 ++++-- lib/Travelynx/Command/database.pm | 270 ++++++++++++++++++++++- lib/Travelynx/Command/dumpconfig.pm | 1 + lib/Travelynx/Command/work.pm | 10 +- lib/Travelynx/Controller/Account.pm | 46 ++++ lib/Travelynx/Controller/Api.pm | 23 +- lib/Travelynx/Controller/Traveling.pm | 390 +++++++++++++++------------------- lib/Travelynx/Helper/HAFAS.pm | 5 + lib/Travelynx/Helper/Sendmail.pm | 2 +- lib/Travelynx/Helper/Traewelling.pm | 2 + lib/Travelynx/Model/InTransit.pm | 23 +- lib/Travelynx/Model/Journeys.pm | 67 ++++-- lib/Travelynx/Model/Stations.pm | 203 +++++++++++++++--- lib/Travelynx/Model/Traewelling.pm | 1 + lib/Travelynx/Model/Users.pm | 15 +- 15 files changed, 803 insertions(+), 321 deletions(-) (limited to 'lib') diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 0c5a5ae..e416be3 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -448,7 +448,7 @@ sub startup { return Mojo::Promise->reject('You are already checked in'); } - if ( $train_id =~ m{[|]} ) { + if ( $opt{hafas} ) { return $self->_checkin_hafas_p(%opt); } @@ -482,7 +482,9 @@ sub startup { db => $db, departure_eva => $eva, train => $train, - route => [ $self->iris->route_diff($train) ], + route => [ $self->iris->route_diff($train) ], + backend_id => + $self->stations->get_backend_id( iris => 1 ), ); }; if ($@) { @@ -530,6 +532,7 @@ sub startup { my $promise = Mojo::Promise->new; $self->hafas->get_journey_p( + service => $opt{hafas}, trip_id => $train_id, with_polyline => 1 )->then( @@ -551,17 +554,21 @@ sub startup { } for my $stop ( $journey->route ) { $self->stations->add_or_update( - stop => $stop, - db => $db, + stop => $stop, + db => $db, + hafas => $opt{hafas}, ); } eval { $self->in_transit->add( - uid => $uid, - db => $db, - journey => $journey, - stop => $found, - data => { trip_id => $journey->id } + uid => $uid, + db => $db, + journey => $journey, + stop => $found, + data => { trip_id => $journey->id }, + backend_id => $self->stations->get_backend_id( + hafas => $opt{hafas} + ), ); }; if ($@) { @@ -620,8 +627,8 @@ sub startup { # mustn't be called during a transaction if ( not $opt{in_transaction} ) { $self->run_hook( $uid, 'checkin' ); - if ( $journey->class <= 16 ) { - $self->app->add_wagonorder( $uid, 1, $journey->id, + if ( $opt{hafas} eq 'DB' and $journey->class <= 16 ) { + $self->add_wagonorder( $uid, 1, $journey->id, $found->sched_dep, $journey->number ); $self->add_stationinfo( $uid, 1, $journey->id, $found->loc->eva ); @@ -744,6 +751,7 @@ sub startup { my $db = $opt{db} // $self->pg->db; my $user = $self->get_user_status( $uid, $db ); my $train_id = $user->{train_id}; + my $hafas = $opt{hafas}; my $promise = Mojo::Promise->new; @@ -765,7 +773,7 @@ sub startup { return $promise->resolve( 0, 'race condition' ); } - if ( $train_id =~ m{[|]} ) { + if ( $user->{is_hafas} ) { return $self->_checkout_hafas_p(%opt); } @@ -1736,7 +1744,8 @@ sub startup { if ( $latest_cancellation and $latest_cancellation->{cancelled} ) { if ( my $station = $self->stations->get_by_eva( - $latest_cancellation->{dep_eva} + $latest_cancellation->{dep_eva}, + backend_id => $latest_cancellation->{backend_id}, ) ) { @@ -1745,7 +1754,8 @@ sub startup { } if ( my $station = $self->stations->get_by_eva( - $latest_cancellation->{arr_eva} + $latest_cancellation->{arr_eva}, + backend_id => $latest_cancellation->{backend_id}, ) ) { @@ -1760,14 +1770,20 @@ sub startup { if ($latest) { my $ts = $latest->{checkout_ts}; my $action_time = epoch_to_dt($ts); - if ( my $station - = $self->stations->get_by_eva( $latest->{dep_eva} ) ) + if ( + my $station = $self->stations->get_by_eva( + $latest->{dep_eva}, backend_id => $latest->{backend_id} + ) + ) { $latest->{dep_ds100} = $station->{ds100}; $latest->{dep_name} = $station->{name}; } - if ( my $station - = $self->stations->get_by_eva( $latest->{arr_eva} ) ) + if ( + my $station = $self->stations->get_by_eva( + $latest->{arr_eva}, backend_id => $latest->{backend_id} + ) + ) { $latest->{arr_ds100} = $station->{ds100}; $latest->{arr_name} = $station->{name}; @@ -1776,6 +1792,10 @@ sub startup { checked_in => 0, cancelled => 0, cancellation => $latest_cancellation, + backend_id => $latest->{backend_id}, + backend_name => $latest->{backend_name}, + is_iris => $latest->{is_iris}, + is_hafas => $latest->{is_hafas}, journey_id => $latest->{journey_id}, timestamp => $action_time, timestamp_delta => $now->epoch - $action_time->epoch, @@ -1833,7 +1853,12 @@ sub startup { $status->{checked_in} or $status->{cancelled} ) ? \1 : \0, - comment => $status->{comment}, + comment => $status->{comment}, + backend => { + id => $status->{backend_id}, + type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS', + name => $status->{backend_name}, + }, fromStation => { ds100 => $status->{dep_ds100}, name => $status->{dep_name}, @@ -1992,6 +2017,7 @@ sub startup { "Eingecheckt in $traewelling->{line} nach $traewelling->{arr_name}", status_id => $traewelling->{status_id}, ); + $self->traewelling->set_latest_pull_status_id( uid => $uid, status_id => $traewelling->{status_id}, @@ -2324,6 +2350,7 @@ sub startup { $authed_r->get('/account/password')->to('account#password_form'); $authed_r->get('/account/mail')->to('account#change_mail'); $authed_r->get('/account/name')->to('account#change_name'); + $authed_r->get('/account/select_backend')->to('account#backend_form'); $authed_r->get('/export.json')->to('account#json_export'); $authed_r->get('/history.json')->to('traveling#json_history'); $authed_r->get('/history.csv')->to('traveling#csv_history'); @@ -2345,6 +2372,7 @@ sub startup { $authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/account/traewelling')->to('traewelling#settings'); $authed_r->post('/account/insight')->to('account#insight'); + $authed_r->post('/account/select_backend')->to('account#change_backend'); $authed_r->post('/journey/add')->to('traveling#add_journey_form'); $authed_r->post('/journey/comment')->to('traveling#comment_form'); $authed_r->post('/journey/visibility')->to('traveling#visibility_form'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index ae17f64..880dfb4 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -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} } ); diff --git a/lib/Travelynx/Command/dumpconfig.pm b/lib/Travelynx/Command/dumpconfig.pm index 600ffb0..2c308c9 100644 --- a/lib/Travelynx/Command/dumpconfig.pm +++ b/lib/Travelynx/Command/dumpconfig.pm @@ -1,4 +1,5 @@ package Travelynx::Command::dumpconfig; + # Copyright (C) 2020-2023 Birte Kristina Friesel # # SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 129ff45..44d780a 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -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 diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 5c101b1..b7b2b78 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -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'); diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index 687243d..5fbfb3e 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -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( diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 2b86688..6b421d1 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -24,10 +24,15 @@ sub has_str_in_list { return; } +# when called with "eva" provided: look up connections from eva, either +# for provided backend_id / hafas or (if not provided) for user backend id. +# When calld without "eva": look up connections from current/latest arrival +# eva, using the checkin's backend id. sub get_connecting_trains_p { my ( $self, %opt ) = @_; - my $uid = $opt{uid} //= $self->current_user->{id}; + my $user = $self->current_user; + my $uid = $opt{uid} //= $user->{id}; my $use_history = $self->users->use_history( uid => $uid ); my ( $eva, $exclude_via, $exclude_train_id, $exclude_before ); @@ -43,10 +48,20 @@ sub get_connecting_trains_p { elsif ( $opt{destination_name} ) { $eva = $opt{eva}; } + if ( not defined $opt{backend_id} ) { + if ( $opt{hafas} ) { + $opt{backend_id} + = $self->stations->get_backend_id( hafas => $opt{hafas} ); + } + else { + $opt{backend_id} = $user->{backend_id}; + } + } } else { if ( $use_history & 0x02 ) { my $status = $self->get_user_status; + $opt{backend_id} = $status->{backend_id}; $eva = $status->{arr_eva}; $exclude_via = $status->{dep_name}; $exclude_train_id = $status->{train_id}; @@ -65,10 +80,12 @@ sub get_connecting_trains_p { return $promise->reject; } - my ( $dest_ids, $destinations ) - = $self->journeys->get_connection_targets(%opt); + $self->log->debug( + "get_connecting_trains_p(backend_id => $opt{backend_id}, eva => $eva)"); - my @destinations = uniq_by { $_->{name} } @{$destinations}; + my @destinations = $self->journeys->get_connection_targets(%opt); + + @destinations = uniq_by { $_->{name} } @destinations; if ($exclude_via) { @destinations = grep { $_->{name} ne $exclude_via } @destinations; @@ -78,11 +95,8 @@ sub get_connecting_trains_p { return $promise->reject; } - my $iris_eva = $eva; - if ( $eva < 8000000 ) { - $iris_eva = ( List::Util::first { $_ >= 8000000 } @{$dest_ids} ) - // $eva; - } + $self->log->debug( 'get_connection_targets returned ' + . join( q{, }, map { $_->{name} } @destinations ) ); my $can_check_in = not $arr_epoch || ( $arr_countdown // 1 ) < 0; my $lookahead @@ -91,11 +105,9 @@ sub get_connecting_trains_p { my $iris_promise = Mojo::Promise->new; my %via_count = map { $_->{name} => 0 } @destinations; - if ( $iris_eva >= 8000000 - and List::Util::any { $_->{eva} >= 8000000 } @destinations ) - { + if ( $opt{backend_id} == 0 ) { $self->iris->get_departures_p( - station => $iris_eva, + station => $eva, lookbehind => 10, lookahead => $lookahead, with_related => 1 @@ -103,7 +115,7 @@ sub get_connecting_trains_p { sub { my ($stationboard) = @_; if ( $stationboard->{errstr} ) { - $iris_promise->resolve( [] ); + $promise->resolve( [], [] ); return; } @@ -237,105 +249,30 @@ sub get_connecting_trains_p { } } - $iris_promise->resolve( [ @results, @cancellations ] ); + $promise->resolve( [ @results, @cancellations ], [] ); return; } )->catch( sub { - $iris_promise->resolve( [] ); + $promise->resolve( [], [] ); return; } )->wait; } else { - $iris_promise->resolve( [] ); - } - - my $hafas_promise = Mojo::Promise->new; - $self->hafas->get_departures_p( - eva => $eva, - lookbehind => 10, - lookahead => $lookahead - )->then( - sub { - my ($status) = @_; - $hafas_promise->resolve( [ $status->results ] ); - return; - } - )->catch( - sub { - # HAFAS data is optional. - # Errors are logged by get_json_p and can be silently ignored here. - $hafas_promise->resolve( [] ); - return; - } - )->wait; - - Mojo::Promise->all( $iris_promise, $hafas_promise )->then( - sub { - my ( $iris, $hafas ) = @_; - my @iris_trains = @{ $iris->[0] }; - my @all_hafas_trains = @{ $hafas->[0] }; - my @hafas_trains; - - # We've already got a list of connecting trains; this function - # only adds further information to them. We ignore errors, as - # partial data is better than no data. - eval { - for my $iris_train (@iris_trains) { - if ( $iris_train->[0]->departure_is_cancelled ) { - for my $hafas_train (@all_hafas_trains) { - if ( $hafas_train->number - and $hafas_train->number - == $iris_train->[0]->train_no ) - { - $hafas_train->{iris_seen} = 1; - next; - } - } - next; - } - for my $hafas_train (@all_hafas_trains) { - if ( $hafas_train->number - and $hafas_train->number - == $iris_train->[0]->train_no ) - { - $hafas_train->{iris_seen} = 1; - if ( $hafas_train->load - and $hafas_train->load->{SECOND} ) - { - $iris_train->[3] = $hafas_train->load; - } - for my $stop ( $hafas_train->route ) { - if ( $stop->loc->name - and $stop->loc->name eq - $iris_train->[1]->{name} - and $stop->arr ) - { - $iris_train->[2] = $stop->arr; - if ( $iris_train->[0]->departure_delay - and not $stop->arr_delay ) - { - $iris_train->[2] - ->add( minutes => $iris_train->[0] - ->departure_delay ); - } - last; - } - } - last; - } - } - } + my $hafas_service + = $self->stations->get_hafas_name( backend_id => $opt{backend_id} ); + $self->hafas->get_departures_p( + service => $hafas_service, + eva => $eva, + lookbehind => 10, + lookahead => $lookahead + )->then( + sub { + my ($status) = @_; + my @hafas_trains; + my @all_hafas_trains = $status->results; for my $hafas_train (@all_hafas_trains) { - if ( $hafas_train->{iris_seen} ) { - next; - } - if ( $hafas_train->station_eva >= 8000000 ) { - - # better safe than sorry, for now - next; - } for my $stop ( $hafas_train->route ) { for my $dest (@destinations) { if ( $stop->loc->name @@ -353,30 +290,30 @@ sub get_connecting_trains_p { } if ( $departure->epoch >= $exclude_before ) { $via_count{ $dest->{name} }++; - push( @hafas_trains, - [ $hafas_train, $dest, $arrival ] ); + push( + @hafas_trains, + [ + $hafas_train, $dest, + $arrival, $hafas_service + ] + ); } } } } } - }; - if ($@) { - $self->app->log->error( - "get_connecting_trains_p($uid): IRIS/HAFAS merge failed: $@" - ); + $promise->resolve( [], \@hafas_trains ); + return; } - - $promise->resolve( \@iris_trains, \@hafas_trains ); - return; - } - )->catch( - sub { - my ($err) = @_; - $promise->reject($err); - return; - } - )->wait; + )->catch( + sub { + my ($err) = @_; + $self->log->debug("get_connection_trains: hafas: $err"); + $promise->resolve( [], [] ); + return; + } + )->wait; + } return $promise; } @@ -394,7 +331,8 @@ sub compute_effective_visibility { sub homepage { my ($self) = @_; if ( $self->is_user_authenticated ) { - my $uid = $self->current_user->{id}; + my $user = $self->current_user; + my $uid = $user->{id}; my $status = $self->get_user_status; my @timeline = $self->in_transit->get_timeline( uid => $uid, @@ -405,7 +343,7 @@ sub homepage { if ( $status->{checked_in} ) { my $journey_visibility = $self->compute_effective_visibility( - $self->current_user->{default_visibility_str}, + $user->{default_visibility_str}, $status->{visibility_str} ); if ( defined $status->{arrival_countdown} and $status->{arrival_countdown} < ( 40 * 60 ) ) @@ -416,6 +354,7 @@ sub homepage { my ( $connections_iris, $connections_hafas ) = @_; $self->render( 'landingpage', + user => $user, user_status => $status, journey_visibility => $journey_visibility, connections_iris => $connections_iris, @@ -427,6 +366,7 @@ sub homepage { sub { $self->render( 'landingpage', + user => $user, user_status => $status, journey_visibility => $journey_visibility, ); @@ -438,6 +378,7 @@ sub homepage { else { $self->render( 'landingpage', + user => $user, user_status => $status, journey_visibility => $journey_visibility, ); @@ -451,10 +392,12 @@ sub homepage { } $self->render( 'landingpage', + user => $user, user_status => $status, recent_targets => \@recent_targets, with_autocomplete => 1, - with_geolocation => 1 + with_geolocation => 1, + backend_id => $user->{backend_id}, ); $self->users->mark_seen( uid => $uid ); } @@ -515,6 +458,7 @@ sub status_card { elsif ( $status->{cancellation} ) { $self->render_later; $self->get_connecting_trains_p( + backend_id => $status->{backend_id}, eva => $status->{cancellation}{dep_eva}, destination_name => $status->{cancellation}{arr_name} )->then( @@ -563,14 +507,63 @@ sub status_card { sub geolocation { my ($self) = @_; - my $lon = $self->param('lon'); - my $lat = $self->param('lat'); + my $lon = $self->param('lon'); + my $lat = $self->param('lat'); + my $backend_id = $self->param('backend') // 0; - if ( not $lon or not $lat ) { + if ( not $lon or not $lat or $backend_id !~ m{ ^ \d+ $ }x ) { $self->render( json => { error => 'Invalid lon/lat received' } ); return; } - $self->render_later; + + my $hafas_service + = $self->stations->get_hafas_name( backend_id => $backend_id ); + + if ($hafas_service) { + $self->render_later; + + Travel::Status::DE::HAFAS->new_p( + promise => 'Mojo::Promise', + user_agent => $self->ua, + service => $hafas_service, + geoSearch => { + lat => $lat, + lon => $lon + } + )->then( + sub { + my ($hafas) = @_; + my @hafas = map { + { + name => $_->name, + eva => $_->eva, + distance => $_->distance_m / 1000, + hafas => $hafas_service + } + } $hafas->results; + if ( @hafas > 10 ) { + @hafas = @hafas[ 0 .. 9 ]; + } + $self->render( + json => { + candidates => [@hafas], + } + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + json => { + candidates => [], + warning => $err, + } + ); + } + )->wait; + + return; + } my @iris = map { { @@ -580,7 +573,6 @@ sub geolocation { lon => $_->[0][3], lat => $_->[0][4], distance => $_->[1], - hafas => 0, } } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon, $lat, 10 ); @@ -588,48 +580,12 @@ sub geolocation { if ( @iris > 5 ) { @iris = @iris[ 0 .. 4 ]; } - - Travel::Status::DE::HAFAS->new_p( - promise => 'Mojo::Promise', - user_agent => $self->ua, - geoSearch => { - lat => $lat, - lon => $lon - } - )->then( - sub { - my ($hafas) = @_; - my @hafas = map { - { - name => $_->name, - eva => $_->eva, - distance => $_->distance_m / 1000, - hafas => 'DB' - } - } $hafas->results; - if ( @hafas > 10 ) { - @hafas = @hafas[ 0 .. 9 ]; - } - my @results = map { $_->[0] } - sort { $a->[1] <=> $b->[1] } - map { [ $_, $_->{distance} ] } ( @iris, @hafas ); - $self->render( - json => { - candidates => [@results], - } - ); - } - )->catch( - sub { - my ($err) = @_; - $self->render( - json => { - candidates => [@iris], - warning => $err, - } - ); + $self->render( + json => { + candidates => [@iris], } - )->wait; + ); + } sub travel_action { @@ -684,6 +640,7 @@ sub travel_action { $promise->then( sub { return $self->checkin_p( + hafas => $params->{hafas}, station => $params->{station}, train_id => $params->{train} ); @@ -713,8 +670,8 @@ sub travel_action { my ( $still_checked_in, undef ) = @_; if ( my $destination = $params->{dest} ) { my $station_link = '/s/' . $destination; - if ( $status->{train_id} =~ m{[|]} ) { - $station_link .= '?hafas=DB'; + if ( $status->{is_hafas} ) { + $station_link .= '?hafas=' . $status->{backend_name}; } $self->render( json => { @@ -749,8 +706,8 @@ sub travel_action { sub { my ( $still_checked_in, $error ) = @_; my $station_link = '/s/' . $params->{station}; - if ( $status->{train_id} =~ m{[|]} ) { - $station_link .= '?hafas=DB'; + if ( $status->{is_hafas} ) { + $station_link .= '?hafas=' . $status->{backend_name}; } if ($error) { @@ -800,8 +757,12 @@ sub travel_action { else { my $redir = '/'; if ( $status->{checked_in} or $status->{cancelled} ) { - if ( $status->{train_id} =~ m{[|]} ) { - $redir = '/s/' . $status->{dep_eva} . '?hafas=DB'; + if ( $status->{is_hafas} ) { + $redir + = '/s/' + . $status->{dep_eva} + . '?hafas=' + . $status->{backend_name}; } else { $redir = '/s/' . $status->{dep_ds100}; @@ -818,6 +779,7 @@ sub travel_action { elsif ( $params->{action} eq 'cancelled_from' ) { $self->render_later; $self->checkin_p( + hafas => $params->{hafas}, station => $params->{station}, train_id => $params->{train} )->then( @@ -920,7 +882,8 @@ sub station { my $train = $self->param('train'); my $trip_id = $self->param('trip_id'); my $timestamp = $self->param('timestamp'); - my $uid = $self->current_user->{id}; + my $user = $self->current_user; + my $uid = $user->{id}; my @timeline = $self->in_transit->get_timeline( uid => $uid, @@ -928,7 +891,6 @@ sub station { ); my %checkin_by_train; for my $checkin (@timeline) { - say $checkin->{train_id}; push( @{ $checkin_by_train{ $checkin->{train_id} } }, $checkin ); } $self->stash( checkin_by_train => \%checkin_by_train ); @@ -945,10 +907,12 @@ sub station { $timestamp = DateTime->now( time_zone => 'Europe/Berlin' ); } - my $use_hafas = $self->param('hafas'); + my $hafas_service = $self->param('hafas') + // ( $user->{backend_hafas} ? $user->{backend_name} : undef ); my $promise; - if ($use_hafas) { + if ($hafas_service) { $promise = $self->hafas->get_departures_p( + service => $hafas_service, eva => $station, timestamp => $timestamp, lookbehind => 30, @@ -966,27 +930,21 @@ sub station { $promise->then( sub { my ($status) = @_; - my $api_link; my @results; my $now = $self->now->epoch; my $now_within_range = abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0; - if ($use_hafas) { - - my $iris_eva = List::Util::min grep { $_ >= 1000000 } - @{ $status->station->{evas} // [] }; - if ($iris_eva) { - $api_link = '/s/' . $iris_eva; - } + if ($hafas_service) { @results = map { $_->[0] } sort { $b->[1] <=> $a->[1] } map { [ $_, $_->datetime->epoch ] } $status->results; $self->stations->add_meta( - eva => $status->station->{eva}, - meta => $status->station->{evas} // [] + eva => $status->station->{eva}, + meta => $status->station->{evas} // [], + hafas => $hafas_service, ); $status = { station_eva => $status->station->{eva}, @@ -999,8 +957,6 @@ sub station { } else { - $api_link = '/s/' . $status->{station_eva} . '?hafas=DB'; - # You can't check into a train which terminates here @results = grep { $_->departure } @{ $status->{results} }; @@ -1029,10 +985,10 @@ sub station { } my $connections_p; - if ( $trip_id and $use_hafas ) { + if ( $trip_id and $hafas_service ) { @results = grep { $_->id eq $trip_id } @results; } - elsif ( $train and not $use_hafas ) { + elsif ( $train and not $hafas_service ) { @results = grep { $_->type . ' ' . $_->train_no eq $train } @results; } @@ -1044,12 +1000,15 @@ sub station { $connections_p = $self->get_connecting_trains_p( eva => $user_status->{cancellation}{dep_eva}, destination_name => - $user_status->{cancellation}{arr_name} + $user_status->{cancellation}{arr_name}, + hafas => $hafas_service, ); } else { $connections_p = $self->get_connecting_trains_p( - eva => $status->{station_eva} ); + eva => $status->{station_eva}, + hafas => $hafas_service + ); } } @@ -1059,18 +1018,18 @@ sub station { my ( $connections_iris, $connections_hafas ) = @_; $self->render( 'departures', + user => $user, + hafas => $hafas_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, results => \@results, - hafas => $use_hafas, station => $status->{station_name}, related_stations => $status->{related_stations}, user_status => $user_status, can_check_out => $can_check_out, connections_iris => $connections_iris, connections_hafas => $connections_hafas, - api_link => $api_link, title => "travelynx: $status->{station_name}", ); } @@ -1078,16 +1037,16 @@ sub station { sub { $self->render( 'departures', + user => $user, + hafas => $hafas_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, results => \@results, - hafas => $use_hafas, station => $status->{station_name}, related_stations => $status->{related_stations}, user_status => $user_status, can_check_out => $can_check_out, - api_link => $api_link, title => "travelynx: $status->{station_name}", ); } @@ -1096,16 +1055,16 @@ sub station { else { $self->render( 'departures', + user => $user, + hafas => $hafas_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, results => \@results, - hafas => $use_hafas, station => $status->{station_name}, related_stations => $status->{related_stations}, user_status => $user_status, can_check_out => $can_check_out, - api_link => $api_link, title => "travelynx: $status->{station_name}", ); } @@ -1120,15 +1079,22 @@ sub station { status => 300, ); } - elsif ( $use_hafas and $status and $status->errcode eq 'LOCATION' ) + elsif ( $hafas_service + and $status + and $status->errcode eq 'LOCATION' ) { - $self->hafas->search_location_p( query => $station )->then( + $self->hafas->search_location_p( + service => $hafas_service, + query => $station + )->then( sub { my ($hafas2) = @_; my @suggestions = $hafas2->results; if ( @suggestions == 1 ) { - $self->redirect_to( - '/s/' . $suggestions[0]->eva . '?hafas=DB' ); + $self->redirect_to( '/s/' + . $suggestions[0]->eva + . '?hafas=' + . $hafas_service ); } else { $self->render( @@ -1169,17 +1135,7 @@ sub redirect_to_station { my ($self) = @_; my $station = $self->param('station'); - if ( my $s = $self->app->stations->search($station) ) { - if ( $s->{source} == 1 ) { - $self->redirect_to("/s/${station}?hafas=DB"); - } - else { - $self->redirect_to("/s/${station}"); - } - } - else { - $self->redirect_to("/s/${station}?hafas=DB"); - } + $self->redirect_to("/s/${station}"); } sub cancelled { diff --git a/lib/Travelynx/Helper/HAFAS.pm b/lib/Travelynx/Helper/HAFAS.pm index 1569208..40bc636 100644 --- a/lib/Travelynx/Helper/HAFAS.pm +++ b/lib/Travelynx/Helper/HAFAS.pm @@ -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}, diff --git a/lib/Travelynx/Helper/Sendmail.pm b/lib/Travelynx/Helper/Sendmail.pm index baa1156..54829c8 100644 --- a/lib/Travelynx/Helper/Sendmail.pm +++ b/lib/Travelynx/Helper/Sendmail.pm @@ -9,7 +9,7 @@ use warnings; use 5.020; -use Encode qw(encode); +use Encode qw(encode); use Email::Sender::Simple qw(try_to_sendmail); use MIME::Entity; diff --git a/lib/Travelynx/Helper/Traewelling.pm b/lib/Travelynx/Helper/Traewelling.pm index d688004..100a799 100644 --- a/lib/Travelynx/Helper/Traewelling.pm +++ b/lib/Travelynx/Helper/Traewelling.pm @@ -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, diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index f5a1f6d..b9fbabc 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -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( diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index b067d78..e47df58 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -545,7 +545,7 @@ sub get { my @select = ( - qw(journey_id is_iris is_hafas backend_name train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) + qw(journey_id is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) ); my %where = ( user_id => $uid, @@ -606,6 +606,7 @@ sub get { is_iris => $entry->{is_iris}, is_hafas => $entry->{is_hafas}, backend_name => $entry->{backend_name}, + backend_id => $entry->{backend_id}, type => $entry->{train_type}, line => $entry->{train_line}, no => $entry->{train_no}, @@ -665,7 +666,10 @@ sub get { my $rename = $self->{renamed_station}; for my $stop ( @{ $ref->{route} } ) { if ( $stop->[0] =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) { - if ( my $s = $self->{stations}->get_by_eva($1) ) { + if ( my $s + = $self->{stations} + ->get_by_eva( $1, backend_id => $ref->{backend_id} ) ) + { $stop->[0] = $s->{name}; } } @@ -800,14 +804,14 @@ sub get_oldest_ts { return undef; } -sub get_latest_checkout_station_id { +sub get_latest_checkout_ids { my ( $self, %opt ) = @_; my $uid = $opt{uid}; my $db = $opt{db} // $self->{pg}->db; my $res_h = $db->select( 'journeys', - ['checkout_station_id'], + [ 'checkout_station_id', 'backend_id', ], { user_id => $uid, cancelled => 0 @@ -822,7 +826,7 @@ sub get_latest_checkout_station_id { return; } - return $res_h->{checkout_station_id}; + return $res_h->{checkout_station_id}, $res_h->{backend_id}; } sub get_latest_checkout_stations { @@ -833,7 +837,10 @@ sub get_latest_checkout_stations { my $res = $db->select( 'journeys_str', - [ 'arr_name', 'arr_eva', 'train_id' ], + [ + 'arr_name', 'arr_eva', 'train_id', 'backend_id', + 'backend_name', 'is_hafas' + ], { user_id => $uid, cancelled => 0 @@ -854,9 +861,10 @@ sub get_latest_checkout_stations { push( @ret, { - name => $row->{arr_name}, - eva => $row->{arr_eva}, - hafas => ( $row->{train_id} =~ m{[|]} ? 'DB' : 0 ), + name => $row->{arr_name}, + eva => $row->{arr_eva}, + hafas => $row->{is_hafas} ? $row->{backend_name} : 0, + backend_id => $row->{backend_id}, } ); } @@ -1726,28 +1734,29 @@ sub get_stats { return $stats; } -sub get_latest_dest_id { +sub get_latest_dest_ids { my ( $self, %opt ) = @_; my $uid = $opt{uid}; my $db = $opt{db} // $self->{pg}->db; if ( - my $id = $self->{in_transit}->get_checkout_station_id( + my ( $id, $backend_id ) = $self->{in_transit}->get_checkout_ids( uid => $uid, db => $db ) ) { - return $id; + return ( $id, $backend_id ); } - return $self->get_latest_checkout_station_id( + return $self->get_latest_checkout_ids( uid => $uid, db => $db ); } +# Returns a listref of {eva, name} hashrefs for the specified backend. sub get_connection_targets { my ( $self, %opt ) = @_; @@ -1756,21 +1765,29 @@ sub get_connection_targets { // DateTime->now( time_zone => 'Europe/Berlin' )->subtract( months => 4 ); my $db = $opt{db} //= $self->{pg}->db; my $min_count = $opt{min_count} // 3; + my $dest_id = $opt{eva}; if ( $opt{destination_name} ) { - return ( - [], - [ { eva => $opt{eva}, name => $opt{destination_name} } ] - ); + return [ { eva => $opt{eva}, name => $opt{destination_name} } ]; } - my $dest_id = $opt{eva} // $self->get_latest_dest_id(%opt); + my $backend_id = $opt{backend_id}; + + if ( not $dest_id ) { + ( $dest_id, $backend_id ) = $self->get_latest_dest_ids(%opt); + } if ( not $dest_id ) { - return ( [], [] ); + return []; } - my $dest_ids = [ $dest_id, $self->{stations}->get_meta( eva => $dest_id ) ]; + 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 { diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index ad219e0..7215aa5 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -14,38 +14,125 @@ sub new { return bless( \%opt, $class ); } -sub add_or_update { +sub get_backend_id { my ( $self, %opt ) = @_; - my $stop = $opt{stop}; - my $loc = $stop->loc; - my $source = 1; - my $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; + + 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}, } - $db->update( + )->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; + $opt{db} //= $self->{pg}->db; + + $opt{backend_id} //= $self->get_backend_id(%opt); + + if ( + my $s = $self->get_by_eva( + $loc->eva, + db => $opt{db}, + backend_id => $opt{backend_id} + ) + ) + { + $opt{db}->update( 'stations', { name => $loc->name, lat => $loc->lat, lon => $loc->lon, - source => $source, archived => 0 }, - { eva => $loc->eva } + { + eva => $loc->eva, + source => $opt{backend_id} + } ); return; } - $db->insert( + $opt{db}->insert( 'stations', { eva => $loc->eva, name => $loc->name, lat => $loc->lat, lon => $loc->lon, - source => $source, + source => $opt{backend_id}, archived => 0 } ); @@ -53,17 +140,20 @@ sub add_or_update { sub add_meta { my ( $self, %opt ) = @_; - my $db = $opt{db} // $self->{pg}->db; my $eva = $opt{eva}; my @meta = @{ $opt{meta} }; + $opt{db} //= $self->{pg}->db; + $opt{backend_id} //= $self->get_backend_id(%opt); + for my $meta (@meta) { if ( $meta != $eva ) { - $db->insert( + $opt{db}->insert( 'related_stations', { - eva => $eva, - meta => $meta + eva => $eva, + meta => $meta, + backend_id => $opt{backend_id}, }, { on_conflict => undef } ); @@ -82,7 +172,16 @@ sub get_meta { my $db = $opt{db} // $self->{pg}->db; my $eva = $opt{eva}; - my $res = $db->select( 'related_stations', ['meta'], { eva => $eva } ); + $opt{backend_id} //= $self->get_backend_id( %opt, db => $db ); + + my $res = $db->select( + 'related_stations', + ['meta'], + { + eva => $eva, + backend_id => $opt{backend_id} + } + ); my @ret; while ( my $row = $res->hash ) { @@ -93,9 +192,12 @@ sub get_meta { } sub get_for_autocomplete { - my ($self) = @_; + my ( $self, %opt ) = @_; + + $opt{backend_id} //= $self->get_backend_id(%opt); - my $res = $self->{pg}->db->select( 'stations', ['name'] ); + 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 ) diff --git a/lib/Travelynx/Model/Traewelling.pm b/lib/Travelynx/Model/Traewelling.pm index 25648cc..c460b1a 100644 --- a/lib/Travelynx/Model/Traewelling.pm +++ b/lib/Travelynx/Model/Traewelling.pm @@ -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 } ); diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 4d90d92..7d3777b 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -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; -- cgit v1.2.3