diff options
Diffstat (limited to 'lib/Travelynx/Controller/Traveling.pm')
| -rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 803 |
1 files changed, 720 insertions, 83 deletions
diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index bb45378..154938d 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -8,13 +8,15 @@ use Mojo::Base 'Mojolicious::Controller'; use DateTime; use DateTime::Format::Strptime; +use GIS::Distance; use List::Util qw(uniq min max); use List::UtilsBy qw(max_by uniq_by); -use List::MoreUtils qw(first_index); +use List::MoreUtils qw(first_index last_index); use Mojo::UserAgent; use Mojo::Promise; use Text::CSV; use Travel::Status::DE::IRIS::Stations; +use XML::LibXML; # Internal Helpers @@ -370,7 +372,9 @@ sub homepage { my $map_data = {}; if ( $status->{arr_name} ) { $map_data = $self->journeys_to_map_data( - journeys => [$status], + journeys => [$status], + show_full_route => 1, + with_now_markers => 1, ); } my $journey_visibility @@ -460,7 +464,9 @@ sub status_card { my $map_data = {}; if ( $status->{arr_name} ) { $map_data = $self->journeys_to_map_data( - journeys => [$status], + journeys => [$status], + show_full_route => 1, + with_now_markers => 1, ); } my $journey_visibility @@ -588,13 +594,9 @@ sub geolocation { if ($dbris_service) { $self->render_later; - Travel::Status::DE::DBRIS->new_p( - promise => 'Mojo::Promise', - user_agent => Mojo::UserAgent->new, - geoSearch => { - latitude => $lat, - longitude => $lon - } + $self->dbris->geosearch_p( + latitude => $lat, + longitude => $lon )->then( sub { my ($dbris) = @_; @@ -605,7 +607,7 @@ sub geolocation { distance => 0, dbris => $dbris_service, } - } $dbris->results; + } uniq_by { $_->name } $dbris->results; if ( @results > 10 ) { @results = @results[ 0 .. 9 ]; } @@ -621,8 +623,13 @@ sub geolocation { $self->render( json => { candidates => [], - warning => $err, - } + error => $err, + }, + + # The frontend JavaScript does not have an XHR error handler yet + # (and if it did, I do not know whether it would have access to our JSON body). + # So, for now, we do the bad thing™ and return HTTP 200 even though the request to the backend was not successful. + # status => 502, ); } )->wait; @@ -665,8 +672,11 @@ sub geolocation { $self->render( json => { candidates => [], - warning => $err, - } + error => $err, + }, + + # See above + # status => 502 ); } )->wait; @@ -716,8 +726,11 @@ sub geolocation { $self->render( json => { candidates => [], - warning => $err, - } + error => $err, + }, + + # See above + #status => 502 ); } )->wait; @@ -730,6 +743,7 @@ sub geolocation { Travel::Status::MOTIS->new_p( promise => 'Mojo::Promise', user_agent => $self->ua, + time_zone => 'Europe/Berlin', service => $motis_service, stops_by_coordinate => { @@ -764,8 +778,11 @@ sub geolocation { $self->render( json => { candidates => [], - warning => $err, - } + error => $err, + }, + + # See above + #status => 502 ); } )->wait; @@ -1157,14 +1174,37 @@ sub station { $timestamp = DateTime->now( time_zone => 'Europe/Berlin' ); } - my $dbris_service = $self->param('dbris') - // ( $user->{backend_dbris} ? $user->{backend_name} : undef ); - my $efa_service = $self->param('efa') - // ( $user->{backend_efa} ? $user->{backend_name} : undef ); - my $hafas_service = $self->param('hafas') - // ( $user->{backend_hafas} ? $user->{backend_name} : undef ); - my $motis_service = $self->param('motis') - // ( $user->{backend_motis} ? $user->{backend_name} : undef ); + my ( $dbris_service, $efa_service, $hafas_service, $motis_service ); + + if ( $self->param('dbris') ) { + $dbris_service = $self->param('dbris'); + } + elsif ( $self->param('efa') ) { + $efa_service = $self->param('efa'); + } + elsif ( $self->param('hafas') ) { + $hafas_service = $self->param('hafas'); + } + elsif ( $self->param('motis') ) { + $motis_service = $self->param('motis'); + } + else { + if ( $user->{backend_dbris} ) { + $dbris_service = $user->{backend_name}; + } + elsif ( $user->{backend_efa} ) { + $efa_service = $user->{backend_name}; + } + elsif ( $user->{backend_hafas} ) { + $hafas_service = $user->{backend_name}; + } + elsif ( $user->{backend_motis} ) { + $motis_service = $user->{backend_name}; + } + } + + my @suggestions; + my $promise; if ($dbris_service) { if ( $station !~ m{ [@] L = \d+ }x ) { @@ -1266,6 +1306,37 @@ sub station { if ( $station =~ m{ [@] O = (?<name> [^@]+ ) [@] }x ) { $status->{station_name} = $+{name}; } + + my ($eva) = ( $station =~ m{ [@] L = (\d+) }x ); + my $backend_id + = $self->stations->get_backend_id( dbris => $dbris_service ); + my @destinations = $self->journeys->get_connection_targets( + uid => $uid, + backend_id => $backend_id, + eva => $eva + ); + + for my $dep (@results) { + destination: for my $dest (@destinations) { + if ( $dep->destination + and $dep->destination eq $dest->{name} ) + { + push( @suggestions, [ $dep, $dest ] ); + next destination; + } + for my $via_name ( $dep->via ) { + if ( $via_name eq $dest->{name} ) { + push( @suggestions, [ $dep, $dest ] ); + next destination; + } + } + } + } + + @suggestions = map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + grep { $_->[1] >= $now - 300 } + map { [ $_, $_->[0]->dep->epoch ] } @suggestions; } elsif ($hafas_service) { @@ -1297,6 +1368,28 @@ sub station { station_name => $status->stop->full_name, related_stations => [], }; + my $backend_id + = $self->stations->get_backend_id( efa => $efa_service ); + my @destinations = $self->journeys->get_connection_targets( + uid => $uid, + backend_id => $backend_id, + eva => $status->{station_eva}, + ); + for my $dep (@results) { + destination: for my $dest (@destinations) { + for my $stop ( $dep->route_post ) { + if ( $stop->full_name eq $dest->{name} ) { + push( @suggestions, [ $dep, $dest ] ); + next destination; + } + } + } + } + + @suggestions = map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + grep { $_->[1] >= $now - 300 and $_->[1] <= $now + 1800 } + map { [ $_, $_->[0]->datetime->epoch ] } @suggestions; } elsif ($motis_service) { @results = map { $_->[0] } @@ -1411,6 +1504,7 @@ sub station { related_stations => $status->{related_stations}, user_status => $user_status, can_check_out => $can_check_out, + suggestions => \@suggestions, title => "travelynx: $status->{station_name}", ); } @@ -1432,6 +1526,7 @@ sub station { related_stations => $status->{related_stations}, user_status => $user_status, can_check_out => $can_check_out, + suggestions => \@suggestions, title => "travelynx: $status->{station_name}", ); } @@ -1500,7 +1595,7 @@ sub station { )->wait; } elsif ( $err - =~ m{svcRes|connection close|Service Temporarily Unavailable|Forbidden} + =~ m{svcRes|connection close|Service Temporarily Unavailable|Forbidden|HTTP 500 Internal Server Error|HTTP 429 Too Many Requests} ) { $self->render( @@ -1723,23 +1818,19 @@ sub map_history { my $with_polyline = $route_type eq 'beeline' ? 0 : 1; my $parser = DateTime::Format::Strptime->new( - pattern => '%d.%m.%Y', + pattern => '%F', locale => 'de_DE', time_zone => 'Europe/Berlin' ); - if ( $filter_from - and $filter_from =~ m{ ^ (\d+) [.] (\d+) [.] (\d+) $ }x ) - { + if ($filter_from) { $filter_from = $parser->parse_datetime($filter_from); } else { $filter_from = undef; } - if ( $filter_until - and $filter_until =~ m{ ^ (\d+) [.] (\d+) [.] (\d+) $ }x ) - { + if ($filter_until) { $filter_until = $parser->parse_datetime($filter_until)->set( hour => 23, minute => 59, @@ -1817,15 +1908,19 @@ sub csv_history { my $buf = q{}; $csv->combine( - qw(Zugtyp Linie Nummer Start Ziel), - 'Start (DS100)', - 'Ziel (DS100)', - 'Abfahrt (soll)', - 'Abfahrt (ist)', - 'Ankunft (soll)', - 'Ankunft (ist)', - 'Kommentar', - 'ID' + qw(type line number), + 'departure stop name', + 'departure stop id', + 'arrival stop name', + 'arrival stop id', + 'scheduled departure', + 'real-time departure', + 'scheduled arrival', + 'real-time arrival', + 'operator', + 'carriage type', + 'comment', + 'id' ); $buf .= $csv->string; @@ -1842,13 +1937,17 @@ sub csv_history { $journey->{line}, $journey->{no}, $journey->{from_name}, + $journey->{from_eva}, $journey->{to_name}, - $journey->{from_ds100}, - $journey->{to_ds100}, - $journey->{sched_departure}->strftime('%Y-%m-%d %H:%M'), - $journey->{rt_departure}->strftime('%Y-%m-%d %H:%M'), - $journey->{sched_arrival}->strftime('%Y-%m-%d %H:%M'), - $journey->{rt_arrival}->strftime('%Y-%m-%d %H:%M'), + $journey->{to_eva}, + $journey->{sched_departure}->strftime('%Y-%m-%d %H:%M:%S'), + $journey->{rt_departure}->strftime('%Y-%m-%d %H:%M:%S'), + $journey->{sched_arrival}->strftime('%Y-%m-%d %H:%M:%S'), + $journey->{rt_arrival}->strftime('%Y-%m-%d %H:%M:%S'), + $journey->{user_data}{operator} // q{}, + join( q{ + }, + map { $_->{desc} // $_->{name} } + @{ $journey->{user_data}{wagongroups} // [] } ), $journey->{user_data}{comment} // q{}, $journey->{id} ) @@ -2093,11 +2192,17 @@ sub journey_details { $self->param( journey_id => $journey_id ); if ( not( $journey_id and $journey_id =~ m{ ^ \d+ $ }x ) ) { - $self->render( - 'journey', - status => 404, - error => 'notfound', - journey => {} + $self->respond_to( + json => { + json => { error => 'not found' }, + status => 404 + }, + any => { + template => 'journey', + status => 404, + error => 'notfound', + journey => {} + } ); return; } @@ -2113,6 +2218,40 @@ sub journey_details { ); if ($journey) { + + if ( $self->stash('polyline_export') ) { + + if ( not( $journey->{polyline} and @{ $journey->{polyline} } ) ) { + $journey->{polyline} + = [ map { [ $_->[2]{lon}, $_->[2]{lat}, $_->[1] ] } + @{ $journey->{route} } ]; + } + + delete $self->stash->{layout}; + + my $xml = $self->render_to_string( + template => 'polyline', + name => sprintf( '%s %s: %s → %s', + $journey->{type}, $journey->{no}, + $journey->{from_name}, $journey->{to_name} ), + polyline => $journey->{polyline} + ); + $self->respond_to( + gpx => { + text => $xml, + format => 'gpx' + }, + json => { + json => [ + map { + $_->[2] ? [ $_->[0], $_->[1], int( $_->[2] ) ] : $_ + } @{ $journey->{polyline} } + ] + }, + ); + return; + } + my $map_data = $self->journeys_to_map_data( journeys => [$journey], include_manual => 1, @@ -2150,29 +2289,39 @@ sub journey_details { $delay, $journey->{rt_arrival}->strftime('%H:%M') ); } - $self->render( - 'journey', - title => sprintf( - 'travelynx: Fahrt %s %s %s am %s', - $journey->{type}, $journey->{line} // '', - $journey->{no}, - $journey->{sched_departure}->strftime('%d.%m.%Y um %H:%M') - ), - error => undef, - journey => $journey, - journey_visibility => $visibility, - with_map => 1, - with_share => $with_share, - share_text => $share_text, - %{$map_data}, + $self->respond_to( + json => { json => $journey }, + any => { + template => 'journey', + title => sprintf( + 'travelynx: Fahrt %s %s %s am %s', + $journey->{type}, + $journey->{line} // '', + $journey->{no}, + $journey->{sched_departure}->strftime('%d.%m.%Y um %H:%M') + ), + error => undef, + journey => $journey, + journey_visibility => $visibility, + with_map => 1, + with_share => $with_share, + share_text => $share_text, + %{$map_data}, + } ); } else { - $self->render( - 'journey', - status => 404, - error => 'notfound', - journey => {} + $self->respond_to( + json => { + json => { error => 'not found' }, + status => 404 + }, + any => { + template => 'journey', + status => 404, + error => 'notfound', + journey => {} + } ); } @@ -2352,7 +2501,12 @@ sub edit_journey { my $error = undef; if ( $self->param('action') and $self->param('action') eq 'save' ) { - my $parser = DateTime::Format::Strptime->new( + my $parser_sec = DateTime::Format::Strptime->new( + pattern => '%d.%m.%Y %H:%M:%S', + locale => 'de_DE', + time_zone => 'Europe/Berlin' + ); + my $parser_min = DateTime::Format::Strptime->new( pattern => '%d.%m.%Y %H:%M', locale => 'de_DE', time_zone => 'Europe/Berlin' @@ -2363,7 +2517,8 @@ sub edit_journey { for my $key (qw(sched_departure rt_departure sched_arrival rt_arrival)) { - my $datetime = $parser->parse_datetime( $self->param($key) ); + my $datetime = $parser_sec->parse_datetime( $self->param($key) ) + // $parser_min->parse_datetime( $self->param($key) ); if ( $datetime and $datetime->epoch ne $journey->{$key}->epoch ) { $error = $self->journeys->update( uid => $uid, @@ -2384,7 +2539,7 @@ sub edit_journey { uid => $uid, db => $db, id => $journey->{id}, - $key => $self->param($key) + $key => $self->param($key), ); if ($error) { last; @@ -2455,8 +2610,14 @@ sub edit_journey { for my $key (qw(sched_departure rt_departure sched_arrival rt_arrival)) { if ( $journey->{$key} and $journey->{$key}->epoch ) { - $self->param( - $key => $journey->{$key}->strftime('%d.%m.%Y %H:%M') ); + if ( $journey->{$key}->second ) { + $self->param( + $key => $journey->{$key}->strftime('%d.%m.%Y %H:%M:%S') ); + } + else { + $self->param( + $key => $journey->{$key}->strftime('%d.%m.%Y %H:%M') ); + } } } @@ -2476,17 +2637,228 @@ sub edit_journey { $self->render( 'edit_journey', with_autocomplete => 1, + backend_id => $journey->{backend_id}, error => $error, journey => $journey ); } +# Taken from Travel::Status::DE::EFA::Trip#polyline +sub polyline_add_stops { + my ( $self, %opt ) = @_; + + my $polyline = $opt{polyline}; + my $route = $opt{route}; + + my $distance = GIS::Distance->new; + + my %min_dist; + my $route_i = 0; + for my $stop ( @{$route} ) { + for my $polyline_index ( 0 .. $#{$polyline} ) { + my $pl = $polyline->[$polyline_index]; + if ( not( defined $stop->[2]{lat} and defined $stop->[2]{lon} ) ) { + my $err + = sprintf( +"Cannot match uploaded polyline with the journey's route: route stop %s (ID %s) has no lat/lon\n", + $stop->[0], $stop->[1] // 'unknown' ); + die($err); + } + my $dist + = $distance->distance_metal( $stop->[2]{lat}, $stop->[2]{lon}, + $pl->[1], $pl->[0] ); + my $key = $route_i . ';' . $stop->[1]; + if ( not $min_dist{$key} + or $min_dist{$key}{dist} > $dist ) + { + $min_dist{$key} = { + dist => $dist, + index => $polyline_index, + }; + } + } + $route_i += 1; + } + $route_i = 0; + for my $stop ( @{$route} ) { + my $key = $route_i . ';' . $stop->[1]; + if ( $min_dist{$key} ) { + if ( defined $polyline->[ $min_dist{$key}{index} ][2] ) { + return sprintf( +'Error: Route stops %d and %d both map to polyline lon/lat %f/%f. ' + . 'The uploaded polyline must cover the following route stops: %s', + $polyline->[ $min_dist{$key}{index} ][2], + $stop->[1], + $polyline->[ $min_dist{$key}{index} ][0], + $polyline->[ $min_dist{$key}{index} ][1], + join( + q{ · }, + map { + sprintf( + '%s (ID %s) @ %f/%f', + $_->[0], $_->[1] // 'unknown', + $_->[2]{lon}, $_->[2]{lat} + ) + } @{$route} + ), + ); + } + $polyline->[ $min_dist{$key}{index} ][2] + = $stop->[1]; + } + $route_i += 1; + } + return; +} + +sub set_polyline { + my ($self) = @_; + + if ( $self->validation->csrf_protect->has_error('csrf_token') ) { + $self->render( + 'bad_request', + csrf => 1, + status => 400 + ); + return; + } + + my $journey_id = $self->param('id'); + my $uid = $self->current_user->{id}; + + # Ensure that the journey exists and belongs to the user + my $journey = $self->journeys->get_single( + uid => $uid, + journey_id => $journey_id, + ); + + if ( not $journey ) { + $self->render( + 'bad_request', + message => 'Invalid journey ID', + status => 400, + ); + return; + } + + if ( my $upload = $self->req->upload('file') ) { + my $root; + eval { + $root = XML::LibXML->load_xml( string => $upload->asset->slurp ); + }; + + if ($@) { + $self->render( + 'bad_request', + message => "Invalid GPX file: Invalid XML: $@", + status => 400, + ); + return; + } + + my $context = XML::LibXML::XPathContext->new($root); + $context->registerNs( 'gpx', 'http://www.topografix.com/GPX/1/1' ); + + use Data::Dumper; + + my @polyline; + for my $point ( + $context->findnodes('/gpx:gpx/gpx:trk/gpx:trkseg/gpx:trkpt') ) + { + push( + @polyline, + [ + 0.0 + $point->getAttribute('lon'), + 0.0 + $point->getAttribute('lat') + ] + ); + } + + if ( not @polyline ) { + $self->render( + 'bad_request', + message => 'Invalid GPX file: found no track points', + status => 400, + ); + return; + } + + my @route = @{ $journey->{route} }; + + if ( $self->param('upload-partial') ) { + my $route_start = first_index { + ( + ( + $_->[1] and $_->[1] == $journey->{from_eva} + or $_->[0] eq $journey->{from_name} + ) + and ( + not( defined $_->[2]{sched_dep} + or defined $_->[2]{rt_dep} ) + or ( $_->[2]{sched_dep} // $_->[2]{rt_dep} ) + == $journey->{sched_dep_ts} + ) + ) + } + @route; + + my $route_end = last_index { + ( + ( + $_->[1] and $_->[1] == $journey->{to_eva} + or $_->[0] eq $journey->{to_name} + ) + and ( + not( defined $_->[2]{sched_arr} + or defined $_->[2]{rt_arr} ) + or ( $_->[2]{sched_arr} // $_->[2]{rt_arr} ) + == $journey->{sched_arr_ts} + ) + ) + } + @route; + + if ( $route_start > -1 and $route_end > -1 ) { + @route = @route[ $route_start .. $route_end ]; + } + } + + my $err = $self->polyline_add_stops( + polyline => \@polyline, + route => \@route, + ); + + if ($err) { + $self->render( + 'bad_request', + message => $err, + status => 400, + ); + return; + } + + $self->journeys->set_polyline( + uid => $uid, + journey_id => $journey_id, + edited => $journey->{edited}, + polyline => \@polyline, + from_eva => $route[0][1], + to_eva => $route[-1][1], + stats_ts => $journey->{rt_dep_ts}, + ); + } + + $self->redirect_to("/journey/${journey_id}"); +} + sub add_journey_form { my ($self) = @_; + $self->stash( backend_id => $self->current_user->{backend_id} ); + if ( $self->param('action') and $self->param('action') eq 'save' ) { my $parser = DateTime::Format::Strptime->new( - pattern => '%d.%m.%Y %H:%M', + pattern => '%FT%H:%M', locale => 'de_DE', time_zone => 'Europe/Berlin' ); @@ -2506,7 +2878,7 @@ sub add_journey_form { with_autocomplete => 1, status => 400, error => -'Zug muss als „Typ Nummer“ oder „Typ Linie Nummer“ eingegeben werden.' +'Fahrt muss als „Typ Nummer“ oder „Typ Linie Nummer“ eingegeben werden.' ); return; } @@ -2544,7 +2916,7 @@ sub add_journey_form { $opt{db} = $db; $opt{uid} = $self->current_user->{id}; - $opt{backend_id} = 1; + $opt{backend_id} = $self->current_user->{backend_id}; my ( $journey_id, $error ) = $self->journeys->add(%opt); @@ -2580,4 +2952,269 @@ sub add_journey_form { } } +sub add_intransit_form { + my ($self) = @_; + + $self->stash( backend_id => $self->current_user->{backend_id} ); + + if ( $self->param('action') and $self->param('action') eq 'save' ) { + my $parser = DateTime::Format::Strptime->new( + pattern => '%FT%H:%M', + locale => 'de_DE', + time_zone => 'Europe/Berlin' + ); + my $time_parser = DateTime::Format::Strptime->new( + pattern => '%H:%M', + locale => 'de_DE', + time_zone => 'Europe/Berlin' + ); + my %opt; + my %trip; + + my @parts = split( qr{\s+}, $self->param('train') ); + + if ( @parts == 2 ) { + @trip{ 'train_type', 'train_no' } = @parts; + } + elsif ( @parts == 3 ) { + @trip{ 'train_type', 'train_line', 'train_no' } = @parts; + } + else { + $self->render( + 'add_intransit', + with_autocomplete => 1, + status => 400, + error => +'Fahrt muss als „Typ Nummer“ oder „Typ Linie Nummer“ eingegeben werden.' + ); + return; + } + + for my $key (qw(sched_departure sched_arrival)) { + if ( $self->param($key) ) { + my $datetime = $parser->parse_datetime( $self->param($key) ); + if ( not $datetime ) { + $self->render( + 'add_intransit', + with_autocomplete => 1, + status => 400, + error => "${key}: Ungültiges Datums-/Zeitformat" + ); + return; + } + $trip{$key} = $datetime; + } + } + + for my $key (qw(dep_station arr_station route comment)) { + $trip{$key} = $self->param($key); + } + + $opt{backend_id} = $self->current_user->{backend_id}; + + my $dep_stop = $self->stations->search( $trip{dep_station}, + backend_id => $opt{backend_id} ); + my $arr_stop = $self->stations->search( $trip{arr_station}, + backend_id => $opt{backend_id} ); + + if ( defined $trip{route} ) { + $trip{route} = [ split( qr{\r?\n\r?}, $trip{route} ) ]; + } + + my $route_has_start = 0; + my $route_has_stop = 0; + + for my $station ( @{ $trip{route} || [] } ) { + if ( $station eq $dep_stop->{name} + or $station eq $dep_stop->{eva} ) + { + $route_has_start = 1; + } + if ( $station eq $arr_stop->{name} + or $station eq $arr_stop->{eva} ) + { + $route_has_stop = 1; + } + } + + my @route; + + if ( not $route_has_start ) { + push( + @route, + [ + $dep_stop->{name}, + $dep_stop->{eva}, + { + lat => $dep_stop->{lat}, + lon => $dep_stop->{lon}, + } + ] + ); + } + + if ( $trip{route} ) { + my @unknown_stations; + my $prev_ts = $trip{sched_departure}; + for my $station ( @{ $trip{route} } ) { + my $ts; + my %station_data; + if ( $station + =~ m{ ^ (?<stop> [^@]+? ) \s* [@] \s* (?<timestamp> .+ ) $ }x + ) + { + $station = $+{stop}; + + # attempt to parse "07:08" short timestamp first + $ts = $time_parser->parse_datetime( $+{timestamp} ); + if ($ts) { + + # fill in last stop's (or at the first stop, our departure's) + # date to complete the datetime + $ts = $ts->set( + year => $prev_ts->year, + month => $prev_ts->month, + day => $prev_ts->day + ); + + # if we go back in time with this, assume we went + # over midnight and add a day, e.g. in case of a stop + # at 23:00 followed by one at 01:30 + if ( $ts < $prev_ts ) { + $ts = $ts->add( days => 1 ); + } + } + else { + # do a full datetime parse + $ts = $parser->parse_datetime( $+{timestamp} ); + } + if ( $ts and $ts >= $prev_ts ) { + $station_data{sched_arr} = $ts->epoch; + $station_data{sched_dep} = $ts->epoch; + $prev_ts = $ts; + } + else { + $self->render( + 'add_intransit', + with_autocomplete => 1, + status => 400, + error => "Ungültige Zeitangabe: $+{timestamp}" + ); + return; + } + } + my $station_info = $self->stations->search( $station, + backend_id => $opt{backend_id} ); + if ($station_info) { + $station_data{lat} = $station_info->{lat}; + $station_data{lon} = $station_info->{lon}; + push( + @route, + [ + $station_info->{name}, $station_info->{eva}, + \%station_data, + ] + ); + } + else { + push( @route, [ $station, undef, {} ] ); + push( @unknown_stations, $station ); + } + } + + if ( @unknown_stations == 1 ) { + $self->render( + 'add_intransit', + with_autocomplete => 1, + status => 400, + error => "Unbekannter Unterwegshalt: $unknown_stations[0]" + ); + return; + } + elsif (@unknown_stations) { + $self->render( + 'add_intransit', + with_autocomplete => 1, + status => 400, + error => 'Unbekannte Unterwegshalte: ' + . join( ', ', @unknown_stations ) + ); + return; + } + } + + if ( not $route_has_stop ) { + push( + @route, + [ + $arr_stop->{name}, + $arr_stop->{eva}, + { + lat => $arr_stop->{lat}, + lon => $arr_stop->{lon}, + } + ] + ); + } + + for my $station (@route) { + if ( $station->[0] eq $dep_stop->{name} + or $station->[1] eq $dep_stop->{eva} ) + { + $station->[2]{sched_dep} = $trip{sched_departure}->epoch; + } + if ( $station->[0] eq $arr_stop->{name} + or $station->[1] eq $arr_stop->{eva} ) + { + $station->[2]{sched_arr} = $trip{sched_arrival}->epoch; + } + } + + my $error; + my $db = $self->pg->db; + my $tx = $db->begin; + + $trip{dep_id} = $dep_stop->{eva}; + $trip{arr_id} = $arr_stop->{eva}; + $trip{route} = \@route; + + $opt{db} = $db; + $opt{manual} = \%trip; + $opt{uid} = $self->current_user->{id}; + + if ( not defined $trip{dep_id} ) { + $error = "Unknown departure stop '$trip{dep_station}'"; + } + elsif ( not defined $trip{arr_id} ) { + $error = "Unknown arrival stop '$trip{arr_station}'"; + } + elsif ( $trip{sched_arrival} <= $trip{sched_departure} ) { + $error = 'Ankunftszeit muss nach Abfahrtszeit liegen'; + } + else { + $error = $self->in_transit->add(%opt); + } + + if ($error) { + $self->render( + 'add_intransit', + with_autocomplete => 1, + status => 400, + error => $error, + ); + } + else { + $tx->commit; + $self->redirect_to('/'); + } + } + else { + $self->render( + 'add_intransit', + with_autocomplete => 1, + error => undef + ); + } +} + 1; |
