diff options
Diffstat (limited to 'lib/Travelynx/Controller/Profile.pm')
-rwxr-xr-x | lib/Travelynx/Controller/Profile.pm | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm new file mode 100755 index 0000000..182c90d --- /dev/null +++ b/lib/Travelynx/Controller/Profile.pm @@ -0,0 +1,475 @@ +package Travelynx::Controller::Profile; + +# Copyright (C) 2020-2023 Daniel Friesel +# +# SPDX-License-Identifier: AGPL-3.0-or-later +use Mojo::Base 'Mojolicious::Controller'; + +use DateTime; + +# Internal Helpers + +sub compute_effective_visibility { + my ( $self, $default_visibility, $journey_visibility ) = @_; + if ( $journey_visibility eq 'default' ) { + return $default_visibility; + } + return $journey_visibility; +} + +sub status_token_ok { + my ( $self, $status, $ts2_ext ) = @_; + my $token = $self->param('token') // q{}; + + my ( $eva, $ts, $ts2 ) = split( qr{-}, $token ); + if ( not $ts ) { + return; + } + + $ts2 //= $ts2_ext; + + if ( $eva == $status->{dep_eva} + and $ts == $status->{timestamp}->epoch % 337 + and $ts2 == $status->{sched_departure}->epoch ) + { + return 1; + } + return; +} + +sub journey_token_ok { + my ( $self, $journey, $ts2_ext ) = @_; + my $token = $self->param('token') // q{}; + + my ( $eva, $ts, $ts2 ) = split( qr{-}, $token ); + if ( not $ts ) { + return; + } + + $ts2 //= $ts2_ext; + + if ( $eva == $journey->{from_eva} + and $ts == $journey->{checkin_ts} % 337 + and $ts2 == $journey->{sched_dep_ts} ) + { + return 1; + } + return; +} + +# Controllers + +sub profile { + my ($self) = @_; + + my $name = $self->stash('name'); + my $user = $self->users->get_privacy_by_name( name => $name ); + + if ( not $user ) { + $self->render('not_found'); + return; + } + + my $status = $self->get_user_status( $user->{id} ); + my $visibility; + if ( $status->{checked_in} or $status->{arr_name} ) { + $visibility + = $self->compute_effective_visibility( + $user->{default_visibility_str}, + $status->{visibility_str} ); + if ( + not( + $visibility eq 'public' + or ( $visibility eq 'unlisted' + and $self->status_token_ok($status) ) + or ( + $visibility eq 'travelynx' + and ( $self->is_user_authenticated + or $self->status_token_ok($status) ) + ) + ) + ) + { + $status->{checked_in} = 0; + $status->{arr_name} = undef; + } + } + if ( not $status->{checked_in} + and $status->{arr_name} + and not $user->{past_status} ) + { + $status->{arr_name} = undef; + } + + my @journeys; + + if ( $user->{past_visible} == 2 + or ( $user->{past_visible} == 1 and $self->is_user_authenticated ) ) + { + + my %opt = ( + uid => $user->{id}, + limit => 10, + with_datetime => 1 + ); + + if ( not $user->{past_all} ) { + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + $opt{before} = DateTime->now( time_zone => 'Europe/Berlin' ); + $opt{after} = $now->clone->subtract( weeks => 4 ); + } + + if ( + $user->{default_visibility_str} eq 'public' + or ( $user->{default_visibility_str} eq 'travelynx' + and $self->is_user_authenticated ) + ) + { + $opt{with_default_visibility} = 1; + } + else { + $opt{with_default_visibility} = 0; + } + + if ( $self->is_user_authenticated ) { + $opt{min_visibility} = 'travelynx'; + } + else { + $opt{min_visibility} = 'public'; + } + + @journeys = $self->journeys->get(%opt); + } + + $self->render( + 'profile', + name => $name, + uid => $user->{id}, + public_level => $user->{public_level}, + journey => $status, + journey_visibility => $visibility, + journeys => [@journeys], + version => $self->app->config->{version} // 'UNKNOWN', + ); +} + +sub journey_details { + my ($self) = @_; + my $name = $self->stash('name'); + my $journey_id = $self->stash('id'); + my $user = $self->users->get_privacy_by_name( name => $name ); + + $self->param( journey_id => $journey_id ); + + if ( not( $user and $journey_id and $journey_id =~ m{ ^ \d+ $ }x ) ) { + $self->render( + 'journey', + status => 404, + error => 'notfound', + journey => {} + ); + return; + } + + my $journey = $self->journeys->get_single( + uid => $user->{id}, + journey_id => $journey_id, + verbose => 1, + with_datetime => 1, + with_polyline => 1, + with_visibility => 1, + ); + + if ( not $journey ) { + $self->render( + 'journey', + status => 404, + error => 'notfound', + journey => {} + ); + return; + } + + my $is_past; + if ( not $user->{past_all} ) { + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + if ( $journey->{sched_dep_ts} < $now->subtract( weeks => 4 )->epoch ) { + $is_past = 1; + } + } + + my $visibility + = $self->compute_effective_visibility( $user->{default_visibility_str}, + $journey->{visibility_str} ); + + if ( + not( + ( $visibility eq 'public' and not $is_past ) + or ( $visibility eq 'unlisted' + and $self->journey_token_ok($journey) ) + or ( + $visibility eq 'travelynx' + and ( ( $self->is_user_authenticated and not $is_past ) + or $self->journey_token_ok($journey) ) + ) + ) + ) + { + $self->render( + 'journey', + status => 404, + error => 'notfound', + journey => {} + ); + return; + } + + my $title = sprintf( 'Fahrt von %s nach %s am %s', + $journey->{from_name}, $journey->{to_name}, + $journey->{rt_arrival}->strftime('%d.%m.%Y') ); + my $delay = 'pünktlich '; + if ( $journey->{rt_arrival} != $journey->{sched_arrival} ) { + $delay = sprintf( + 'mit %+d ', + ( + $journey->{rt_arrival}->epoch + - $journey->{sched_arrival}->epoch + ) / 60 + ); + } + my $description = sprintf( 'Ankunft mit %s %s %s', + $journey->{type}, $journey->{no}, + $journey->{rt_arrival}->strftime('um %H:%M') ); + if ( $journey->{km_route} > 0.1 ) { + $description = sprintf( '%.0f km mit %s %s – Ankunft %sum %s', + $journey->{km_route}, $journey->{type}, $journey->{no}, + $delay, $journey->{rt_arrival}->strftime('%H:%M') ); + } + my %tw_data = ( + card => 'summary', + site => '@derfnull', + image => $self->url_for('/static/icons/icon-512x512.png') + ->to_abs->scheme('https'), + title => $title, + description => $description, + ); + my %og_data = ( + type => 'article', + image => $tw_data{image}, + url => $self->url_for->to_abs, + site_name => 'travelynx', + title => $title, + description => $description, + ); + + my $map_data = $self->journeys_to_map_data( + journeys => [$journey], + include_manual => 1, + ); + if ( $journey->{user_data}{comment} + and not $user->{comments_visible} ) + { + delete $journey->{user_data}{comment}; + } + $self->render( + 'journey', + error => undef, + journey => $journey, + with_map => 1, + username => $name, + readonly => 1, + twitter => \%tw_data, + opengraph => \%og_data, + journey_visibility => $visibility, + %{$map_data}, + ); +} + +sub user_status { + my ($self) = @_; + + my $name = $self->stash('name'); + my $ts = $self->stash('ts') // 0; + my $user = $self->users->get_privacy_by_name( name => $name ); + + if ( not $user ) { + $self->render('not_found'); + return; + } + + my $status = $self->get_user_status( $user->{id} ); + + if ( + $ts + and ( not $status->{checked_in} + or $status->{sched_departure}->epoch != $ts ) + ) + { + for my $journey ( + $self->journeys->get( + uid => $user->{id}, + sched_dep_ts => $ts, + limit => 1, + with_visibility => 1, + ) + ) + { + my $visibility + = $self->compute_effective_visibility( + $user->{default_visibility_str}, + $journey->{visibility_str} ); + if ( + $visibility eq 'public' + or ( $visibility eq 'unlisted' + and $self->journey_token_ok( $journey, $ts ) ) + or ( + $visibility eq 'travelynx' + and ( $self->is_user_authenticated + or $self->journey_token_ok( $journey, $ts ) ) + ) + ) + { + my $token = $self->param('token') // q{}; + $self->redirect_to( + "/p/${name}/j/$journey->{id}?token=${token}-${ts}"); + } + else { + $self->render('not_found'); + } + return; + } + $self->render('not_found'); + return; + } + + my %tw_data = ( + card => 'summary', + site => '@derfnull', + image => $self->url_for('/static/icons/icon-512x512.png') + ->to_abs->scheme('https'), + ); + my %og_data = ( + type => 'article', + image => $tw_data{image}, + url => $self->url_for("/status/${name}")->to_abs->scheme('https'), + site_name => 'travelynx', + ); + + my $visibility; + if ( $status->{checked_in} or $status->{arr_name} ) { + $visibility + = $self->compute_effective_visibility( + $user->{default_visibility_str}, + $status->{visibility_str} ); + if ( + not( + $visibility eq 'public' + or ( $visibility eq 'unlisted' + and $self->status_token_ok( $status, $ts ) ) + or ( + $visibility eq 'travelynx' + and ( $self->is_user_authenticated + or $self->status_token_ok( $status, $ts ) ) + ) + ) + ) + { + $status->{checked_in} = 0; + $status->{arr_name} = undef; + } + } + if ( not $status->{checked_in} + and $status->{arr_name} + and not $user->{past_status} ) + { + $status->{arr_name} = undef; + } + + if ( $status->{checked_in} ) { + $og_data{url} .= '/' . $status->{sched_departure}->epoch; + $og_data{title} = $tw_data{title} = "${name} ist unterwegs"; + $og_data{description} = $tw_data{description} = sprintf( + '%s %s von %s nach %s', + $status->{train_type}, $status->{train_line} // $status->{train_no}, + $status->{dep_name}, $status->{arr_name} // 'irgendwo' + ); + if ( $status->{real_arrival}->epoch ) { + $tw_data{description} .= $status->{real_arrival} + ->strftime(' – Ankunft gegen %H:%M Uhr'); + $og_data{description} .= $status->{real_arrival} + ->strftime(' – Ankunft gegen %H:%M Uhr'); + } + } + else { + $og_data{title} = $tw_data{title} + = "${name} ist gerade nicht eingecheckt"; + $og_data{description} = $tw_data{description} = q{}; + } + + $self->render( + 'user_status', + name => $name, + public_level => $user->{public_level}, + journey => $status, + journey_visibility => $visibility, + twitter => \%tw_data, + opengraph => \%og_data, + version => $self->app->config->{version} // 'UNKNOWN', + ); +} + +sub status_card { + my ($self) = @_; + + my $name = $self->stash('name'); + $name =~ s{[.]html$}{}; + my $user = $self->users->get_privacy_by_name( name => $name ); + + delete $self->stash->{layout}; + + if ( not $user ) { + $self->render('not_found'); + return; + } + + my $status = $self->get_user_status( $user->{id} ); + my $visibility; + if ( $status->{checked_in} or $status->{arr_name} ) { + $visibility + = $self->compute_effective_visibility( + $user->{default_visibility_str}, + $status->{visibility_str} ); + if ( + not( + $visibility eq 'public' + or ( $visibility eq 'unlisted' + and $self->status_token_ok($status) ) + or ( + $visibility eq 'travelynx' + and ( $self->is_user_authenticated + or $self->status_token_ok($status) ) + ) + ) + ) + { + $status->{checked_in} = 0; + $status->{arr_name} = undef; + } + } + if ( not $status->{checked_in} + and $status->{arr_name} + and not $user->{past_status} ) + { + $status->{arr_name} = undef; + } + + $self->render( + '_public_status_card', + name => $name, + public_level => $user->{public_level}, + journey => $status, + journey_visibility => $visibility, + ); +} + +1; |