summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorBirte Kristina Friesel <derf@finalrewind.org>2025-03-23 18:07:50 +0100
committerBirte Kristina Friesel <derf@finalrewind.org>2025-03-23 18:07:50 +0100
commita9b5a18943c3e2070703e745cd1131a02fd20365 (patch)
treeb65552c1a4a28e66811be19c5ec03e53fadc5600 /lib
parent5ef9ef68529bb77d79aa4529c48c8de328232186 (diff)
Preliminary DBRIS support (not user-accessible yet)
working: * checkin * checkout * realtime data * polylines * carriage formation (long-distance only) to do: * geolocation * redirects after checkout / undo * traewelling sync * use dbris by default
Diffstat (limited to 'lib')
-rwxr-xr-xlib/Travelynx.pm170
-rw-r--r--lib/Travelynx/Command/database.pm153
-rw-r--r--lib/Travelynx/Command/work.pm105
-rw-r--r--lib/Travelynx/Controller/Account.pm19
-rwxr-xr-xlib/Travelynx/Controller/Traveling.pm103
-rw-r--r--lib/Travelynx/Helper/DBRIS.pm138
-rw-r--r--lib/Travelynx/Model/InTransit.pm132
-rwxr-xr-xlib/Travelynx/Model/Journeys.pm11
-rw-r--r--lib/Travelynx/Model/Stations.pm82
-rw-r--r--lib/Travelynx/Model/Users.pm9
10 files changed, 874 insertions, 48 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index 2b7fdf5..98ba4aa 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -21,6 +21,7 @@ use List::UtilsBy qw(uniq_by);
use List::MoreUtils qw(first_index);
use Travel::Status::DE::DBRIS::Formation;
use Travelynx::Helper::DBDB;
+use Travelynx::Helper::DBRIS;
use Travelynx::Helper::HAFAS;
use Travelynx::Helper::IRIS;
use Travelynx::Helper::Sendmail;
@@ -217,6 +218,19 @@ sub startup {
);
$self->helper(
+ dbris => sub {
+ my ($self) = @_;
+ state $dbris = Travelynx::Helper::DBRIS->new(
+ log => $self->app->log,
+ cache => $self->app->cache_iris_rt,
+ root_url => $self->base_url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->app->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
hafas => sub {
my ($self) = @_;
state $hafas = Travelynx::Helper::HAFAS->new(
@@ -452,6 +466,9 @@ sub startup {
return Mojo::Promise->reject('You are already checked in');
}
+ if ( $opt{dbris} ) {
+ return $self->_checkin_dbris_p(%opt);
+ }
if ( $opt{hafas} ) {
return $self->_checkin_hafas_p(%opt);
}
@@ -531,6 +548,146 @@ sub startup {
);
$self->helper(
+ '_checkin_dbris_p' => sub {
+ my ( $self, %opt ) = @_;
+
+ my $station = $opt{station};
+ my $train_id = $opt{train_id};
+ my $ts = $opt{ts};
+ my $uid = $opt{uid} // $self->current_user->{id};
+ my $db = $opt{db} // $self->pg->db;
+ my $hafas;
+
+ my $promise = Mojo::Promise->new;
+
+ $self->dbris->get_journey_p(
+ trip_id => $train_id,
+ with_polyline => 1
+ )->then(
+ sub {
+ my ($journey) = @_;
+ my $found;
+ for my $stop ( $journey->route ) {
+ if ( $stop->eva eq $station ) {
+ $found = $stop;
+
+ # Lines may serve the same stop several times.
+ # Keep looking until the scheduled departure
+ # matches the one passed while checking in.
+ if ( $ts and $stop->sched_dep->epoch == $ts ) {
+ last;
+ }
+ }
+ }
+ if ( not $found ) {
+ $promise->reject(
+"Did not find stop '$station' within journey '$train_id'"
+ );
+ return;
+ }
+ for my $stop ( $journey->route ) {
+ $self->stations->add_or_update(
+ stop => $stop,
+ db => $db,
+ dbris => 'bahn.de',
+ );
+ }
+ eval {
+ $self->in_transit->add(
+ uid => $uid,
+ db => $db,
+ journey => $journey,
+ stop => $found,
+ data => { trip_id => $train_id },
+ backend_id => $self->stations->get_backend_id(
+ dbris => 'bahn.de'
+ ),
+ );
+ };
+ if ($@) {
+ $self->app->log->error(
+ "Checkin($uid): INSERT failed: $@");
+ $promise->reject( 'INSERT failed: ' . $@ );
+ return;
+ }
+
+ my $polyline;
+ if ( $journey->polyline ) {
+ my @station_list;
+ my @coordinate_list;
+ for my $coord ( $journey->polyline ) {
+ if ( $coord->{stop} ) {
+ push(
+ @coordinate_list,
+ [
+ $coord->{lon}, $coord->{lat},
+ $coord->{stop}->eva
+ ]
+ );
+ push( @station_list, $coord->{stop}->name );
+ }
+ else {
+ push( @coordinate_list,
+ [ $coord->{lon}, $coord->{lat} ] );
+ }
+ }
+
+ # equal length → polyline only consists of straight
+ # lines between stops. that's not helpful.
+ if ( @station_list == @coordinate_list ) {
+ $self->log->debug( 'Ignoring polyline for '
+ . $journey->train
+ . ' as it only consists of straight lines between stops.'
+ );
+ }
+ else {
+ $polyline = {
+ from_eva => ( $journey->route )[0]->eva,
+ to_eva => ( $journey->route )[-1]->eva,
+ coords => \@coordinate_list,
+ };
+ }
+ }
+
+ if ($polyline) {
+ $self->in_transit->set_polyline(
+ uid => $uid,
+ db => $db,
+ polyline => $polyline,
+ );
+ }
+
+ # mustn't be called during a transaction
+ if ( not $opt{in_transaction} ) {
+ $self->run_hook( $uid, 'checkin' );
+ $self->add_wagonorder(
+ uid => $uid,
+ train_id => $train_id,
+ is_departure => 1,
+ eva => $found->eva,
+ datetime => $found->sched_dep,
+ train_type => $journey->type,
+ train_no => $journey->number
+ );
+ $self->add_stationinfo( $uid, 1, $train_id,
+ $found->eva );
+ }
+
+ $promise->resolve($journey);
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+ }
+ );
+
+ $self->helper(
'_checkin_hafas_p' => sub {
my ( $self, %opt ) = @_;
@@ -799,8 +956,8 @@ sub startup {
return $promise->resolve( 0, 'race condition' );
}
- if ( $user->{is_hafas} ) {
- return $self->_checkout_hafas_p(%opt);
+ if ( $user->{is_dbris} or $user->{is_hafas} ) {
+ return $self->_checkout_journey_p(%opt);
}
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
@@ -1049,7 +1206,7 @@ sub startup {
);
$self->helper(
- '_checkout_hafas_p' => sub {
+ '_checkout_journey_p' => sub {
my ( $self, %opt ) = @_;
my $station = $opt{station};
@@ -1840,6 +1997,7 @@ sub startup {
cancellation => $latest_cancellation,
backend_id => $latest->{backend_id},
backend_name => $latest->{backend_name},
+ is_dbris => $latest->{is_dbris},
is_iris => $latest->{is_iris},
is_hafas => $latest->{is_hafas},
journey_id => $latest->{journey_id},
@@ -1901,8 +2059,10 @@ sub startup {
) ? \1 : \0,
comment => $status->{comment},
backend => {
- id => $status->{backend_id},
- type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS',
+ id => $status->{backend_id},
+ type => $status->{ds_dbris} ? 'DBRIS'
+ : $status->{is_hafas} ? 'HAFAS'
+ : 'IRIS-TTS',
name => $status->{backend_name},
},
fromStation => {
diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm
index 6ba80a3..f72a38c 100644
--- a/lib/Travelynx/Command/database.pm
+++ b/lib/Travelynx/Command/database.pm
@@ -2701,6 +2701,159 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;}
}
);
},
+
+ # v59 -> v60
+ # Add bahn.de / DBRIS backend
+ sub {
+ my ($db) = @_;
+ $db->insert(
+ 'backends',
+ {
+ iris => 0,
+ hafas => 0,
+ efa => 0,
+ ris => 1,
+ name => 'bahn.de',
+ },
+ );
+ $db->query(
+ qq{
+ update schema_version set version = 60;
+ }
+ );
+ },
+
+ # v60 -> v61
+ # Rename "ris" / "is_ris" to "dbris" / "is_dbris", as it is DB-specific
+ sub {
+ my ($db) = @_;
+ $db->query(
+ qq{
+ drop view in_transit_str;
+ drop view journeys_str;
+ drop view users_with_backend;
+ drop view follows_in_transit;
+ alter table backends rename column ris to dbris;
+ 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.dbris as is_dbris,
+ 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.dbris as is_dbris,
+ 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 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, dbris, backend.name as backend_name
+ from users
+ left join backends as backend on users.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,
+ backend.iris as is_iris, backend.hafas as is_hafas,
+ backend.efa as is_efa, backend.dbris as is_dbris,
+ backend.name as backend_name, 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
+ left join backends as backend on in_transit.backend_id = backend.id
+ order by checkin_time desc
+ ;
+ update schema_version set version = 61;
+ }
+ );
+ },
);
sub sync_stations {
diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm
index 5385e9b..55c2005 100644
--- a/lib/Travelynx/Command/work.pm
+++ b/lib/Travelynx/Command/work.pm
@@ -49,6 +49,111 @@ sub run {
my $arr = $entry->{arr_eva};
my $train_id = $entry->{train_id};
+ if ( $entry->{is_dbris} ) {
+
+ eval {
+
+ $self->app->dbris->get_journey_p( trip_id => $train_id )->then(
+ sub {
+ my ($journey) = @_;
+
+ my $found_dep;
+ my $found_arr;
+ for my $stop ( $journey->route ) {
+ if ( $stop->eva == $dep ) {
+ $found_dep = $stop;
+ }
+ if ( $arr and $stop->eva == $arr ) {
+ $found_arr = $stop;
+ last;
+ }
+ }
+ if ( not $found_dep ) {
+ $self->app->log->debug(
+ "Did not find $dep within journey $train_id");
+ return;
+ }
+
+ if ( $found_dep->rt_dep ) {
+ $self->app->in_transit->update_departure_dbris(
+ uid => $uid,
+ journey => $journey,
+ stop => $found_dep,
+ dep_eva => $dep,
+ arr_eva => $arr,
+ train_id => $train_id,
+ );
+ }
+ if ( $found_dep->sched_dep
+ and $found_dep->dep->epoch > $now->epoch )
+ {
+ $self->app->add_wagonorder(
+ uid => $uid,
+ train_id => $train_id,
+ is_departure => 1,
+ eva => $dep,
+ datetime => $found_dep->sched_dep,
+ train_type => $journey->type,
+ train_no => $journey->number,
+ );
+ $self->app->add_stationinfo( $uid, 1,
+ $train_id, $found_dep->eva );
+ }
+
+ if ( $found_arr and $found_arr->rt_arr ) {
+ $self->app->in_transit->update_arrival_dbris(
+ uid => $uid,
+ journey => $journey,
+ stop => $found_arr,
+ dep_eva => $dep,
+ arr_eva => $arr
+ );
+ if ( $found_arr->arr->epoch - $now->epoch < 600 ) {
+ $self->app->add_wagonorder(
+ uid => $uid,
+ train_id => $train_id,
+ is_arrival => 1,
+ eva => $arr,
+ datetime => $found_arr->sched_dep,
+ train_type => $journey->type,
+ train_no => $journey->number,
+ );
+ $self->app->add_stationinfo( $uid, 0,
+ $train_id, $found_dep->eva,
+ $found_arr->eva );
+ }
+ }
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->app->log->error(
+"work($uid) @ DBRIS $entry->{backend_name}: journey: $err"
+ );
+ }
+ )->wait;
+
+ if ( $arr
+ and $entry->{real_arr_ts}
+ and $now->epoch - $entry->{real_arr_ts} > 600 )
+ {
+ $self->app->checkout_p(
+ station => $arr,
+ force => 2,
+ dep_eva => $dep,
+ arr_eva => $arr,
+ uid => $uid
+ )->wait;
+ }
+ };
+ if ($@) {
+ $errors += 1;
+ $self->app->log->error(
+ "work($uid) @ DBRIS $entry->{backend_name}: $@");
+ }
+ next;
+ }
+
if ( $entry->{is_hafas} ) {
eval {
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm
index 9cd0edb..43b0683 100644
--- a/lib/Travelynx/Controller/Account.pm
+++ b/lib/Travelynx/Controller/Account.pm
@@ -1066,9 +1066,17 @@ sub backend_form {
if ( $backend->{iris} ) {
$type = 'IRIS-TTS';
$backend->{name} = 'IRIS';
- $backend->{longname} = 'Deutsche Bahn (IRIS-TTS)';
+ $backend->{longname} = 'Deutsche Bahn: IRIS-TTS';
$backend->{homepage} = 'https://www.bahn.de';
}
+ elsif ( $backend->{dbris} ) {
+ $type = 'DBRIS';
+ $backend->{longname} = 'Deutsche Bahn: bahn.de';
+ $backend->{homepage} = 'https://www.bahn.de';
+
+ # not ready for production yet
+ $type = undef;
+ }
elsif ( $backend->{hafas} ) {
# These backends lack a journey endpoint or are no longer
@@ -1135,14 +1143,11 @@ sub backend_form {
$backend->{type} = $type;
}
- # These backends lack a journey endpoint and are useless for travelynx
- @backends
- = grep { $_->{name} ne 'Resrobot' and $_->{name} ne 'TPG' } @backends;
-
my $iris = shift @backends;
- @backends
- = sort { $a->{name} cmp $b->{name} } grep { $_->{type} } @backends;
+ @backends = map { $_->[1] }
+ sort { $a->[0] cmp $b->[0] }
+ map { [ lc( $_->{name} ), $_ ] } grep { $_->{type} } @backends;
unshift( @backends, $iris );
diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm
index 1ca9d4a..a6e56b9 100755
--- a/lib/Travelynx/Controller/Traveling.pm
+++ b/lib/Travelynx/Controller/Traveling.pm
@@ -42,6 +42,13 @@ sub get_connecting_trains_p {
my $promise = Mojo::Promise->new;
+ if ( $user->{backend_dbris} ) {
+
+ # We do get a little bit of via information, so this might work in some
+ # cases. But not reliably. Probably best to leave it out entirely then.
+ return $promise->reject;
+ }
+
if ( $opt{eva} ) {
if ( $use_history & 0x01 ) {
$eva = $opt{eva};
@@ -106,6 +113,8 @@ sub get_connecting_trains_p {
my $iris_promise = Mojo::Promise->new;
my %via_count = map { $_->{name} => 0 } @destinations;
+ my $backend
+ = $self->stations->get_backend( backend_id => $opt{backend_id} );
if ( $opt{backend_id} == 0 ) {
$self->iris->get_departures_p(
station => $eva,
@@ -260,9 +269,11 @@ sub get_connecting_trains_p {
}
)->wait;
}
- else {
- my $hafas_service
- = $self->stations->get_hafas_name( backend_id => $opt{backend_id} );
+ elsif ( $backend->{dbris} ) {
+ ...;
+ }
+ elsif ( $backend->{hafas} ) {
+ my $hafas_service = $backend->{name};
$self->hafas->get_departures_p(
service => $hafas_service,
eva => $eva,
@@ -524,10 +535,19 @@ sub geolocation {
return;
}
- my $hafas_service
- = $self->stations->get_hafas_name( backend_id => $backend_id );
+ my ( $dbris_service, $hafas_service );
+ my $backend = $self->stations->get_backend( backend_id => $backend_id );
+ if ( $backend->{dbris} ) {
+ $dbris_service = $backend->{name};
+ }
+ elsif ( $backend->{hafas} ) {
+ $hafas_service = $backend->{name};
+ }
- if ($hafas_service) {
+ if ($dbris_service) {
+ ...;
+ }
+ elsif ($hafas_service) {
$self->render_later;
my $agent = $self->ua;
@@ -588,6 +608,7 @@ sub geolocation {
lon => $_->[0][3],
lat => $_->[0][4],
distance => $_->[1],
+ dbris => 0,
hafas => 0,
}
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
@@ -656,6 +677,7 @@ sub travel_action {
$promise->then(
sub {
return $self->checkin_p(
+ dbris => $params->{dbris},
hafas => $params->{hafas},
station => $params->{station},
train_id => $params->{train},
@@ -687,7 +709,10 @@ sub travel_action {
my ( $still_checked_in, undef ) = @_;
if ( my $destination = $params->{dest} ) {
my $station_link = '/s/' . $destination;
- if ( $status->{is_hafas} ) {
+ if ( $status->{is_dbris} ) {
+ $station_link .= '?dbris=' . $status->{backend_name};
+ }
+ elsif ( $status->{is_hafas} ) {
$station_link .= '?hafas=' . $status->{backend_name};
}
$self->render(
@@ -723,7 +748,10 @@ sub travel_action {
sub {
my ( $still_checked_in, $error ) = @_;
my $station_link = '/s/' . $params->{station};
- if ( $status->{is_hafas} ) {
+ if ( $status->{is_dbris} ) {
+ $station_link .= '?dbris=' . $status->{backend_name};
+ }
+ elsif ( $status->{is_hafas} ) {
$station_link .= '?hafas=' . $status->{backend_name};
}
@@ -774,7 +802,14 @@ sub travel_action {
else {
my $redir = '/';
if ( $status->{checked_in} or $status->{cancelled} ) {
- if ( $status->{is_hafas} ) {
+ if ( $status->{is_dbris} ) {
+ $redir
+ = '/s/'
+ . $status->{dep_eva}
+ . '?dbris='
+ . $status->{backend_name};
+ }
+ elsif ( $status->{is_hafas} ) {
$redir
= '/s/'
. $status->{dep_eva}
@@ -796,6 +831,7 @@ sub travel_action {
elsif ( $params->{action} eq 'cancelled_from' ) {
$self->render_later;
$self->checkin_p(
+ dbris => $params->{dbris},
hafas => $params->{hafas},
station => $params->{station},
train_id => $params->{train},
@@ -925,10 +961,19 @@ sub station {
$timestamp = DateTime->now( time_zone => 'Europe/Berlin' );
}
+ my $dbris_service = $self->param('dbris')
+ // ( $user->{backend_dbris} ? $user->{backend_name} : undef );
my $hafas_service = $self->param('hafas')
// ( $user->{backend_hafas} ? $user->{backend_name} : undef );
my $promise;
- if ($hafas_service) {
+ if ($dbris_service) {
+ $promise = $self->dbris->get_departures_p(
+ station => $station,
+ timestamp => $timestamp,
+ lookbehind => 30,
+ );
+ }
+ elsif ($hafas_service) {
$promise = $self->hafas->get_departures_p(
service => $hafas_service,
eva => $station,
@@ -954,7 +999,22 @@ sub station {
my $now_within_range
= abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0;
- if ($hafas_service) {
+ if ($dbris_service) {
+
+ @results = map { $_->[0] }
+ sort { $b->[1] <=> $a->[1] }
+ map { [ $_, $_->dep->epoch ] } $status->results;
+
+ $status = {
+ station_eva => $station,
+ related_stations => [],
+ };
+
+ if ( $station =~ m{ [@] O = (?<name> [^@]+ ) [@] }x ) {
+ $status->{station_name} = $+{name};
+ }
+ }
+ elsif ($hafas_service) {
@results = map { $_->[0] }
sort { $b->[1] <=> $a->[1] }
@@ -1039,6 +1099,7 @@ sub station {
$self->render(
'departures',
user => $user,
+ dbris => $dbris_service,
hafas => $hafas_service,
eva => $status->{station_eva},
datetime => $timestamp,
@@ -1058,6 +1119,7 @@ sub station {
$self->render(
'departures',
user => $user,
+ dbris => $dbris_service,
hafas => $hafas_service,
eva => $status->{station_eva},
datetime => $timestamp,
@@ -1076,6 +1138,7 @@ sub station {
$self->render(
'departures',
user => $user,
+ dbris => $dbris_service,
hafas => $hafas_service,
eva => $status->{station_eva},
datetime => $timestamp,
@@ -1174,7 +1237,23 @@ sub redirect_to_station {
my ($self) = @_;
my $station = $self->param('station');
- $self->redirect_to("/s/${station}");
+ if ( $self->param('backend_dbris') ) {
+ $self->render_later;
+ $self->dbris->get_station_id_p($station)->then(
+ sub {
+ my ($dbris_station) = @_;
+ $self->redirect_to( '/s/' . $dbris_station->{id} );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->redirect_to('/');
+ }
+ )->wait;
+ }
+ else {
+ $self->redirect_to("/s/${station}");
+ }
}
sub cancelled {
diff --git a/lib/Travelynx/Helper/DBRIS.pm b/lib/Travelynx/Helper/DBRIS.pm
new file mode 100644
index 0000000..e647cc5
--- /dev/null
+++ b/lib/Travelynx/Helper/DBRIS.pm
@@ -0,0 +1,138 @@
+package Travelynx::Helper::DBRIS;
+
+# Copyright (C) 2025 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use strict;
+use warnings;
+use 5.020;
+use utf8;
+
+use DateTime;
+use Encode qw(decode);
+use JSON;
+use Mojo::Promise;
+use Mojo::UserAgent;
+use Travel::Status::DE::DBRIS;
+
+sub new {
+ my ( $class, %opt ) = @_;
+
+ my $version = $opt{version};
+
+ $opt{header}
+ = { 'User-Agent' =>
+"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx"
+ };
+
+ return bless( \%opt, $class );
+}
+
+sub get_station_id_p {
+ my ( $self, $station_name ) = @_;
+ my $promise = Mojo::Promise->new;
+ Travel::Status::DE::DBRIS->new_p(
+ locationSearch => $station_name,
+ cache => $self->{cache},
+ lwp_options => {
+ timeout => 10,
+ agent => $self->{header}{'User-Agent'},
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ )->then(
+ sub {
+ my ($dbris) = @_;
+ my $found;
+ for my $result ( $dbris->results ) {
+ if ( defined $result->eva ) {
+ $promise->resolve($result);
+ return;
+ }
+ }
+ $promise->reject("Unable to find station '$station_name'");
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $promise->reject("'$err' while trying to look up '$station_name'");
+ return;
+ }
+ )->wait;
+ return $promise;
+}
+
+sub get_departures_p {
+ my ( $self, %opt ) = @_;
+
+ my $agent = $self->{user_agent};
+
+ if ( $opt{station} =~ m{ [@] L = (?<eva> \d+ ) [@] }x ) {
+ $opt{station} = {
+ eva => $+{eva},
+ id => $opt{station},
+ };
+ }
+
+ my $when = (
+ $opt{timestamp}
+ ? $opt{timestamp}->clone
+ : DateTime->now( time_zone => 'Europe/Berlin' )
+ )->subtract( minutes => $opt{lookbehind} );
+ return Travel::Status::DE::DBRIS->new_p(
+ station => $opt{station},
+ datetime => $when,
+ cache => $self->{cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10),
+ );
+}
+
+sub get_journey_p {
+ my ( $self, %opt ) = @_;
+
+ my $promise = Mojo::Promise->new;
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ my $agent = $self->{user_agent};
+ if ( my $proxy = $self->{service_config}{dbris}{proxy} ) {
+ $agent = Mojo::UserAgent->new;
+ $agent->proxy->http($proxy);
+ $agent->proxy->https($proxy);
+ }
+
+ Travel::Status::DE::DBRIS->new_p(
+ journey => $opt{trip_id},
+ with_polyline => $opt{with_polyline},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10),
+ )->then(
+ sub {
+ my ($dbris) = @_;
+ my $journey = $dbris->result;
+
+ if ($journey) {
+ $self->{log}->debug("get_journey_p($opt{trip_id}): success");
+ $promise->resolve($journey);
+ return;
+ }
+ $self->{log}->debug("get_journey_p($opt{trip_id}): no journey");
+ $promise->reject('no journey');
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->debug("get_journey_p($opt{trip_id}): error $err");
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+1;
diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm
index 43ecb90..2b9832c 100644
--- a/lib/Travelynx/Model/InTransit.pm
+++ b/lib/Travelynx/Model/InTransit.pm
@@ -104,6 +104,8 @@ sub add {
my $json = JSON->new;
if ($train) {
+
+ # IRIS
$db->insert(
'in_transit',
{
@@ -134,7 +136,9 @@ sub add {
}
);
}
- elsif ( $journey and $stop ) {
+ elsif ( $journey and $stop and $journey->can('id') ) {
+
+ # HAFAS
my @route;
my $product = $journey->product_at( $stop->loc->eva )
// $journey->product;
@@ -188,6 +192,56 @@ sub add {
}
);
}
+ elsif ( $journey and $stop ) {
+
+ # DBRIS
+ my @route;
+ for my $j_stop ( $journey->route ) {
+ push(
+ @route,
+ [
+ $j_stop->name,
+ $j_stop->eva,
+ {
+ sched_arr => _epoch( $j_stop->sched_arr ),
+ sched_dep => _epoch( $j_stop->sched_dep ),
+ rt_arr => _epoch( $j_stop->rt_arr ),
+ rt_dep => _epoch( $j_stop->rt_dep ),
+ arr_delay => $j_stop->arr_delay,
+ dep_delay => $j_stop->dep_delay,
+ load => undef,
+ lat => $j_stop->lat,
+ lon => $j_stop->lon,
+ }
+ ]
+ );
+ }
+ $db->insert(
+ 'in_transit',
+ {
+ user_id => $uid,
+ cancelled => $stop->{dep_cancelled}
+ ? 1
+ : 0,
+ checkin_station_id => $stop->eva,
+ checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ),
+ dep_platform => $stop->platform,
+ train_type => $journey->type,
+ train_no => $journey->number,
+ train_id => $data->{trip_id},
+ sched_departure => $stop->sched_dep,
+ real_departure => $stop->rt_dep // $stop->sched_dep,
+ route => $json->encode( \@route ),
+ data => JSON->new->encode(
+ {
+ rt => $stop->{rt_dep} ? 1 : 0,
+ %{ $data // {} }
+ }
+ ),
+ backend_id => $backend_id,
+ }
+ );
+ }
else {
die('neither train nor journey specified');
}
@@ -731,6 +785,33 @@ sub update_departure_cancelled {
return $rows;
}
+sub update_departure_dbris {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $journey = $opt{journey};
+ my $stop = $opt{stop};
+ my $json = JSON->new;
+
+ # selecting on user_id and train_no avoids a race condition if a user checks
+ # into a new train while we are fetching data for their previous journey. In
+ # this case, the new train would receive data from the previous journey.
+ $db->update(
+ 'in_transit',
+ {
+ real_departure => $stop->{rt_dep},
+ },
+ {
+ user_id => $uid,
+ train_id => $opt{train_id},
+ checkin_station_id => $dep_eva,
+ checkout_station_id => $arr_eva,
+ }
+ );
+}
+
sub update_departure_hafas {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
@@ -800,6 +881,55 @@ sub update_arrival {
return $rows;
}
+sub update_arrival_dbris {
+ my ( $self, %opt ) = @_;
+ my $uid = $opt{uid};
+ my $db = $opt{db} // $self->{pg}->db;
+ my $dep_eva = $opt{dep_eva};
+ my $arr_eva = $opt{arr_eva};
+ my $journey = $opt{journey};
+ my $stop = $opt{stop};
+ my $json = JSON->new;
+
+ my @route;
+ for my $j_stop ( $journey->route ) {
+ push(
+ @route,
+ [
+ $j_stop->name,
+ $j_stop->eva,
+ {
+ sched_arr => _epoch( $j_stop->sched_arr ),
+ sched_dep => _epoch( $j_stop->sched_dep ),
+ rt_arr => _epoch( $j_stop->rt_arr ),
+ rt_dep => _epoch( $j_stop->rt_dep ),
+ arr_delay => $j_stop->arr_delay,
+ dep_delay => $j_stop->dep_delay,
+ lat => $j_stop->lat,
+ lon => $j_stop->lon,
+ }
+ ]
+ );
+ }
+
+ # selecting on user_id and train_no avoids a race condition if a user checks
+ # into a new train while we are fetching data for their previous journey. In
+ # this case, the new train would receive data from the previous journey.
+ $db->update(
+ 'in_transit',
+ {
+ real_arrival => $stop->{rt_arr},
+ route => $json->encode( [@route] ),
+ },
+ {
+ user_id => $uid,
+ train_id => $opt{train_id},
+ checkin_station_id => $dep_eva,
+ checkout_station_id => $arr_eva,
+ }
+ );
+}
+
sub update_arrival_hafas {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm
index 905c426..f5bc9f1 100755
--- a/lib/Travelynx/Model/Journeys.pm
+++ b/lib/Travelynx/Model/Journeys.pm
@@ -549,7 +549,7 @@ sub get {
my @select
= (
- 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)
+ qw(journey_id is_dbris 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,
@@ -607,6 +607,7 @@ sub get {
my $ref = {
id => $entry->{journey_id},
+ is_dbris => $entry->{is_dbris},
is_iris => $entry->{is_iris},
is_hafas => $entry->{is_hafas},
backend_name => $entry->{backend_name},
@@ -870,8 +871,8 @@ sub get_latest_checkout_stations {
my $res = $db->select(
'journeys_str',
[
- 'arr_name', 'arr_eva', 'train_id', 'backend_id',
- 'backend_name', 'is_hafas'
+ 'arr_name', 'arr_eva', 'train_id', 'backend_id',
+ 'backend_name', 'is_dbris', 'is_hafas'
],
{
user_id => $uid,
@@ -895,6 +896,7 @@ sub get_latest_checkout_stations {
{
name => $row->{arr_name},
eva => $row->{arr_eva},
+ dbris => $row->{is_dbris} ? $row->{backend_name} : 0,
hafas => $row->{is_hafas} ? $row->{backend_name} : 0,
backend_id => $row->{backend_id},
}
@@ -1883,7 +1885,8 @@ sub get_connection_targets {
);
my @destinations
= $res->hashes->grep( sub { shift->{count} >= $min_count } )
- ->map( sub { shift->{dest} } )->each;
+ ->map( sub { shift->{dest} } )
+ ->each;
@destinations = $self->{stations}->get_by_evas(
backend_id => $opt{backend_id},
evas => [@destinations]
diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm
index 76fd452..3d6549f 100644
--- a/lib/Travelynx/Model/Stations.pm
+++ b/lib/Travelynx/Model/Stations.pm
@@ -25,11 +25,25 @@ sub get_backend_id {
if ( $opt{hafas} and $self->{backend_id}{hafas}{ $opt{hafas} } ) {
return $self->{backend_id}{hafas}{ $opt{hafas} };
}
+ if ( $opt{dbris} and $self->{backend_id}{dbris}{ $opt{dbris} } ) {
+ return $self->{backend_id}{dbris}{ $opt{dbris} };
+ }
my $db = $opt{db} // $self->{pg}->db;
my $backend_id = 0;
- if ( $opt{hafas} ) {
+ if ( $opt{dbris} ) {
+ $backend_id = $db->select(
+ 'backends',
+ ['id'],
+ {
+ dbris => 1,
+ name => $opt{dbris}
+ }
+ )->hash->{id};
+ $self->{backend_id}{dbris}{ $opt{dbris} } = $backend_id;
+ }
+ elsif ( $opt{hafas} ) {
$backend_id = $db->select(
'backends',
['id'],
@@ -44,31 +58,25 @@ sub get_backend_id {
return $backend_id;
}
-sub get_hafas_name {
+sub get_backend {
my ( $self, %opt ) = @_;
- if ( exists $self->{hafas_name}{ $opt{backend_id} } ) {
- return $self->{hafas_name}{ $opt{backend_id} };
+ if ( $self->{backend_cache}{ $opt{backend_id} } ) {
+ return $self->{backend_cache}{ $opt{backend_id} };
}
- my $db = $opt{db} // $self->{pg}->db;
- my $hafas_name;
+ my $db = $opt{db} // $self->{pg}->db;
my $ret = $db->select(
'backends',
- ['name'],
+ '*',
{
- hafas => 1,
- id => $opt{backend_id},
+ id => $opt{backend_id},
}
)->hash;
- if ($ret) {
- $hafas_name = $ret->{name};
- }
-
- $self->{hafas_name}{ $opt{backend_id} } = $hafas_name;
+ $self->{backend_cache}{ $opt{backend_id} } = $ret;
- return $hafas_name;
+ return $ret;
}
sub get_backends {
@@ -76,7 +84,8 @@ sub get_backends {
$opt{db} //= $self->{pg}->db;
- my $res = $opt{db}->select( 'backends', [ 'id', 'name', 'iris', 'hafas' ] );
+ my $res = $opt{db}
+ ->select( 'backends', [ 'id', 'name', 'iris', 'hafas', 'dbris' ] );
my @ret;
while ( my $row = $res->hash ) {
@@ -86,6 +95,7 @@ sub get_backends {
id => $row->{id},
name => $row->{name},
iris => $row->{iris},
+ dbris => $row->{dbris},
hafas => $row->{hafas},
}
);
@@ -97,11 +107,49 @@ sub get_backends {
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 ( $opt{dbris} ) {
+ if (
+ my $s = $self->get_by_eva(
+ $stop->eva,
+ db => $opt{db},
+ backend_id => $opt{backend_id}
+ )
+ )
+ {
+ $opt{db}->update(
+ 'stations',
+ {
+ name => $stop->name,
+ lat => $stop->lat,
+ lon => $stop->lon,
+ archived => 0
+ },
+ {
+ eva => $stop->eva,
+ source => $opt{backend_id}
+ }
+ );
+ return;
+ }
+ $opt{db}->insert(
+ 'stations',
+ {
+ eva => $stop->eva,
+ name => $stop->name,
+ lat => $stop->lat,
+ lon => $stop->lon,
+ source => $opt{backend_id},
+ archived => 0
+ }
+ );
+ return;
+ }
+
+ my $loc = $stop->loc;
if (
my $s = $self->get_by_eva(
$loc->eva,
diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm
index 7d3777b..e3d6f7a 100644
--- a/lib/Travelynx/Model/Users.pm
+++ b/lib/Travelynx/Model/Users.pm
@@ -209,7 +209,11 @@ sub set_backend {
my ( $self, %opt ) = @_;
$opt{db} //= $self->{pg}->db;
- $opt{db}->update('users', {backend_id => $opt{backend_id}}, {id => $opt{uid}});
+ $opt{db}->update(
+ 'users',
+ { backend_id => $opt{backend_id} },
+ { id => $opt{uid} }
+ );
}
sub set_privacy {
@@ -414,7 +418,7 @@ sub get {
. '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, '
- . 'backend_id, backend_name, hafas',
+ . 'backend_id, backend_name, hafas, dbris',
{ id => $uid }
)->hash;
if ($user) {
@@ -453,6 +457,7 @@ sub get {
: undef,
backend_id => $user->{backend_id},
backend_name => $user->{backend_name},
+ backend_dbris => $user->{dbris},
backend_hafas => $user->{hafas},
};
}