diff options
Diffstat (limited to 'lib')
-rwxr-xr-x | lib/Travelynx.pm | 26 | ||||
-rw-r--r-- | lib/Travelynx/Command/database.pm | 114 | ||||
-rw-r--r-- | lib/Travelynx/Controller/Account.pm | 78 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 668 | ||||
-rw-r--r-- | lib/Travelynx/Model/InTransit.pm | 51 | ||||
-rwxr-xr-x | lib/Travelynx/Model/Journeys.pm | 66 | ||||
-rw-r--r-- | lib/Travelynx/Model/Users.pm | 53 |
7 files changed, 766 insertions, 290 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 98b22d8..6d903e6 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -421,6 +421,25 @@ sub startup { ); $self->helper( + 'visibility_icon' => sub { + my ( $self, $visibility ) = @_; + if ( $visibility eq 'public' ) { + return 'language'; + } + if ( $visibility eq 'travelynx' ) { + return 'lock_open'; + } + if ( $visibility eq 'unlisted' ) { + return 'lock_outline'; + } + if ( $visibility eq 'private' ) { + return 'lock'; + } + return 'help_outline'; + } + ); + + $self->helper( 'checkin' => sub { my ( $self, %opt ) = @_; @@ -1348,7 +1367,8 @@ sub startup { uid => $uid, db => $db, with_data => 1, - with_timestamps => 1 + with_timestamps => 1, + with_visibility => 1, ); if ($in_transit) { @@ -1425,6 +1445,8 @@ sub startup { messages => $in_transit->{messages}, extra_data => $in_transit->{data}, comment => $in_transit->{user_data}{comment}, + visibility => $in_transit->{visibility}, + visibility_str => $in_transit->{visibility_str}, }; my $traewelling = $self->traewelling->get( @@ -2205,6 +2227,7 @@ sub startup { $authed_r->get('/history/:year/:month')->to('traveling#monthly_history'); $authed_r->get('/journey/add')->to('traveling#add_journey_form'); $authed_r->get('/journey/comment')->to('traveling#comment_form'); + $authed_r->get('/journey/visibility')->to('traveling#visibility_form'); $authed_r->get('/journey/:id')->to('traveling#journey_details'); $authed_r->get('/s/*station')->to('traveling#station'); $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); @@ -2215,6 +2238,7 @@ sub startup { $authed_r->post('/account/services')->to('account#services'); $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'); $authed_r->post('/journey/edit')->to('traveling#edit_journey'); $authed_r->post('/journey/passenger_rights/*filename') ->to('passengerrights#generate'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 5ef80f0..82f1f47 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1312,6 +1312,120 @@ my @migrations = ( } ); }, + + # v32 -> v33 + # add optional per-status visibility that overrides global visibility + sub { + my ($db) = @_; + $db->query( + qq{ + alter table journeys add column visibility smallint; + alter table in_transit add column visibility smallint; + drop view journeys_str; + drop view in_transit_str; + create view journeys_str as select + journeys.id as journey_id, user_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, + cancelled, edited, route, messages, user_data, + dep_platform, arr_platform + from journeys + left join polylines on polylines.id = polyline_id + left join stations as dep_station on checkin_station_id = dep_station.eva + left join stations as arr_station on checkout_station_id = arr_station.eva + ; + create view in_transit_str as select + user_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, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join stations as dep_station on checkin_station_id = dep_station.eva + left join stations as arr_station on checkout_station_id = arr_station.eva + ; + } + ); + my $res = $db->select( 'users', [ 'id', 'public_level' ] ); + while ( my $row = $res->hash ) { + my $old_level = $row->{public_level}; + my $new_level = 0; + if ( $old_level & 0x01 ) { + + # status: account required + $new_level = 80; + } + if ( $old_level & 0x02 ) { + + # status: public + $new_level = 100; + } + if ( $old_level & 0x04 ) { + + # comment public + $new_level |= 0x80; + } + if ( $old_level & 0x10 ) { + + # past: account required + $new_level |= 0x100; + } + if ( $old_level & 0x20 ) { + + # past: public + $new_level |= 0x200; + } + if ( $old_level & 0x40 ) { + + # past: infinite (default is 4 weeks) + $new_level |= 0x400; + } + my $r = $db->update( + 'users', + { public_level => $new_level }, + { id => $row->{id} } + )->rows; + if ( $r != 1 ) { + die("oh no"); + } + } + $db->update( 'schema_version', { version => 33 } ); + }, ); sub sync_stations { diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index f0e9346..e8bfcaf 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -9,6 +9,22 @@ use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); use JSON; use UUID::Tiny qw(:std); +my %visibility_itoa = ( + 100 => 'public', + 80 => 'travelynx', + 60 => 'followers', + 30 => 'unlisted', + 10 => 'private', +); + +my %visibility_atoi = ( + public => 100, + travelynx => 80, + followers => 60, + unlisted => 30, + private => 10, +); + # Internal Helpers sub hash_password { @@ -438,50 +454,30 @@ sub privacy { my $public_level = $user->{is_public}; if ( $self->param('action') and $self->param('action') eq 'save' ) { - if ( $self->param('status_level') eq 'intern' ) { - $public_level |= 0x01; - $public_level &= ~0x02; - } - elsif ( $self->param('status_level') eq 'extern' ) { - $public_level |= 0x02; - $public_level &= ~0x01; - } - else { - $public_level &= ~0x03; + my %opt; + my $default_visibility + = $visibility_atoi{ $self->param('status_level') }; + if ( defined $default_visibility ) { + $opt{default_visibility} = $default_visibility; } - # public comment with non-public status does not make sense - if ( $self->param('public_comment') - and $self->param('status_level') ne 'private' ) - { - $public_level |= 0x04; - } - else { - $public_level &= ~0x04; - } + $opt{comments_visible} = $self->param('public_comment') ? 1 : 0; + + $opt{past_all} = $self->param('history_age') eq 'infinite' ? 1 : 0; if ( $self->param('history_level') eq 'intern' ) { - $public_level |= 0x10; - $public_level &= ~0x20; + $opt{past_visible} = 1; } elsif ( $self->param('history_level') eq 'extern' ) { - $public_level |= 0x20; - $public_level &= ~0x10; - } - else { - $public_level &= ~0x30; - } - - if ( $self->param('history_age') eq 'infinite' ) { - $public_level |= 0x40; + $opt{past_visible} = 2; } else { - $public_level &= ~0x40; + $opt{past_visible} = 0; } $self->users->set_privacy( - uid => $user->{id}, - level => $public_level + uid => $user->{id}, + %opt ); $self->flash( success => 'privacy' ); @@ -489,18 +485,14 @@ sub privacy { } else { $self->param( - status_level => $public_level & 0x01 ? 'intern' - : $public_level & 0x02 ? 'extern' - : 'private' - ); - $self->param( public_comment => $public_level & 0x04 ? 1 : 0 ); + status_level => $visibility_itoa{ $user->{default_visibility} } ); + $self->param( public_comment => $user->{comments_visible} ); $self->param( - history_level => $public_level & 0x10 ? 'intern' - : $public_level & 0x20 ? 'extern' - : 'private' + history_level => $user->{past_visible} & 0x01 ? 'intern' + : $user->{past_visible} & 0x02 ? 'extern' + : 'private' ); - $self->param( - history_age => $public_level & 0x40 ? 'infinite' : 'month' ); + $self->param( history_age => $user->{past_all} ? 'infinite' : 'month' ); $self->render( 'privacy', name => $user->{name} ); } } diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 3f39996..15b131a 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -370,6 +370,14 @@ sub get_connecting_trains_p { return $promise; } +sub compute_effective_visibility { + my ( $self, $default_visibility, $journey_visibility ) = @_; + if ( $journey_visibility eq 'default' ) { + return $default_visibility; + } + return $journey_visibility; +} + # Controllers sub homepage { @@ -378,6 +386,10 @@ sub homepage { my $status = $self->get_user_status; my @recent_targets; if ( $status->{checked_in} ) { + my $journey_visibility + = $self->compute_effective_visibility( + $self->current_user->{default_visibility_str}, + $status->{visibility_str} ); if ( defined $status->{arrival_countdown} and $status->{arrival_countdown} < ( 40 * 60 ) ) { @@ -389,11 +401,10 @@ sub homepage { 'landingpage', version => $self->app->config->{version} // 'UNKNOWN', - user_status => $status, - connections => $connecting_trains, - transit_fyi => $transit_fyi, - with_autocomplete => 1, - with_geolocation => 1 + user_status => $status, + journey_visibility => $journey_visibility, + connections => $connecting_trains, + transit_fyi => $transit_fyi, ); $self->users->mark_seen( uid => $self->current_user->{id} ); @@ -404,9 +415,8 @@ sub homepage { 'landingpage', version => $self->app->config->{version} // 'UNKNOWN', - user_status => $status, - with_autocomplete => 1, - with_geolocation => 1 + user_status => $status, + journey_visibility => $journey_visibility, ); $self->users->mark_seen( uid => $self->current_user->{id} ); @@ -414,6 +424,16 @@ sub homepage { )->wait; return; } + else { + $self->render( + 'landingpage', + version => $self->app->config->{version} // 'UNKNOWN', + user_status => $status, + journey_visibility => $journey_visibility, + ); + $self->users->mark_seen( uid => $self->current_user->{id} ); + return; + } } else { @recent_targets = uniq_by { $_->{eva} } @@ -439,6 +459,26 @@ sub homepage { } } +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 + and $ts2 == $status->{sched_departure}->epoch ) + { + return 1; + } + return; +} + sub user_status { my ($self) = @_; @@ -446,47 +486,33 @@ sub user_status { my $ts = $self->stash('ts') // 0; my $user = $self->users->get_privacy_by_name( name => $name ); - if ( not $user or not $user->{public_level} & 0x03 ) { + if ( not $user ) { $self->render('not_found'); return; } - if ( $user->{public_level} & 0x01 and not $self->is_user_authenticated ) { - $self->render( 'login', redirect_to => $self->req->url ); - return; - } - my $status = $self->get_user_status( $user->{id} ); - my $journey; if ( $ts and ( not $status->{checked_in} or $status->{sched_departure}->epoch != $ts ) - and ( - $user->{public_level} & 0x20 - or ( $user->{public_level} & 0x10 - and $self->is_user_authenticated ) - ) ) { for my $candidate ( $self->journeys->get( uid => $user->{id}, - limit => 10, + limit => 20, ) ) { if ( $candidate->{sched_dep_ts} eq $ts ) { - $journey = $self->journeys->get_single( - uid => $user->{id}, - journey_id => $candidate->{id}, - verbose => 1, - with_datetime => 1, - with_polyline => 1, - ); + $self->redirect_to("/p/${name}/j/$candidate->{id}"); + return; } } + $self->render('not_found'); + return; } my %tw_data = ( @@ -502,24 +528,30 @@ sub user_status { site_name => 'travelynx', ); - if ($journey) { - $og_data{title} = $tw_data{title} = sprintf( 'Fahrt von %s nach %s', - $journey->{from_name}, $journey->{to_name} ); - $og_data{description} = $tw_data{description} - = $journey->{rt_arrival}->strftime('Ankunft am %d.%m.%Y um %H:%M'); - $og_data{url} .= "/${ts}"; - } - elsif ( - $ts - and ( not $status->{checked_in} - or $status->{sched_departure}->epoch != $ts ) - ) - { - $og_data{title} = $tw_data{title} = "Bahnfahrt beendet"; - $og_data{description} = $tw_data{description} - = "${name} hat das Ziel erreicht"; + my $visibility; + if ( $status->{checked_in} ) { + $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; + } } - elsif ( $status->{checked_in} ) { + + 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( @@ -537,39 +569,18 @@ sub user_status { else { $og_data{title} = $tw_data{title} = "${name} ist gerade nicht eingecheckt"; - $og_data{description} = $tw_data{description} - = "Letztes Fahrtziel: $status->{arr_name}"; + $og_data{description} = $tw_data{description} = q{}; } - if ($journey) { - if ( not $user->{public_level} & 0x04 ) { - delete $journey->{user_data}{comment}; - } - my $map_data = $self->journeys_to_map_data( - journeys => [$journey], - include_manual => 1, - ); - $self->render( - 'journey', - error => undef, - with_map => 1, - readonly => 1, - journey => $journey, - twitter => \%tw_data, - opengraph => \%og_data, - %{$map_data}, - ); - } - else { - $self->render( - 'user_status', - name => $name, - public_level => $user->{public_level}, - journey => $status, - twitter => \%tw_data, - opengraph => \%og_data, - ); - } + $self->render( + 'user_status', + name => $name, + public_level => $user->{public_level}, + journey => $status, + journey_visibility => $visibility, + twitter => \%tw_data, + opengraph => \%og_data, + ); } sub public_profile { @@ -578,48 +589,77 @@ sub public_profile { my $name = $self->stash('name'); my $user = $self->users->get_privacy_by_name( name => $name ); + if ( not $user ) { + $self->render('not_found'); + } + + my $status = $self->get_user_status( $user->{id} ); + my $visibility; + if ( $status->{checked_in} ) { + $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; + } + } + + 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 - and ( - $user->{public_level} & 0x22 - or ( $user->{public_level} & 0x11 - and $self->is_user_authenticated ) - ) + $user->{default_visibility_str} eq 'public' + or ( $user->{default_visibility_str} eq 'travelynx' + and $self->is_user_authenticated ) ) { - my $status = $self->get_user_status( $user->{id} ); - my @journeys; - if ( $user->{public_level} & 0x40 ) { - @journeys = $self->journeys->get( - uid => $user->{id}, - limit => 10, - with_datetime => 1 - ); - } - else { - my $now = DateTime->now( time_zone => 'Europe/Berlin' ); - my $month_ago = $now->clone->subtract( weeks => 4 ); - @journeys = $self->journeys->get( - uid => $user->{id}, - limit => 10, - with_datetime => 1, - after => $month_ago, - before => $now - ); - } - $self->render( - 'profile', - name => $name, - uid => $user->{id}, - public_level => $user->{public_level}, - journey => $status, - journeys => [@journeys], - version => $self->app->config->{version} // 'UNKNOWN', - ); + $opt{with_default_visibility} = 1; } else { - $self->render('not_found'); + $opt{with_default_visibility} = 0; } + + if ( $self->is_user_authenticated ) { + $opt{min_visibility} = 'travelynx'; + } + else { + $opt{min_visibility} = 'public'; + } + + my @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 public_journey_details { @@ -630,7 +670,26 @@ sub public_journey_details { $self->param( journey_id => $journey_id ); - if ( not( $journey_id and $journey_id =~ m{ ^ \d+ $ }x ) ) { + 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, @@ -640,98 +699,93 @@ sub public_journey_details { return; } + my $visibility + = $self->compute_effective_visibility( $user->{default_visibility_str}, + $journey->{visibility_str} ); + if ( - $user - and ( - $user->{public_level} & 0x20 - or ( $user->{public_level} & 0x10 - and $self->is_user_authenticated ) + not( + $visibility eq 'public' + or ( $visibility eq 'unlisted' + and $self->status_token_ok($journey) ) + or ( + $visibility eq 'travelynx' + and ( $self->is_user_authenticated + or $self->status_token_ok($journey) ) + ) ) ) { - my $journey = $self->journeys->get_single( - uid => $user->{id}, - journey_id => $journey_id, - verbose => 1, - with_datetime => 1, - with_polyline => 1, + $self->render( + 'journey', + status => 404, + error => 'notfound', + journey => {} ); + return; + } - if ( not( $user->{public_level} & 0x40 ) ) { - my $month_ago = DateTime->now( time_zone => 'Europe/Berlin' ) - ->subtract( weeks => 4 )->epoch; - if ( $journey and $journey->{rt_dep_ts} < $month_ago ) { - $journey = undef; - } - } - - if ($journey) { - 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, - ); + # TODO re-add age check unless status_token_ok (helper function?) - my $map_data = $self->journeys_to_map_data( - journeys => [$journey], - include_manual => 1, - ); - if ( $journey->{user_data}{comment} - and not $user->{public_level} & 0x04 ) - { - 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, - %{$map_data}, - ); - } - else { - $self->render('not_found'); - } + 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 + ); } - else { - $self->render('not_found'); + 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->{public_level} & 0x04 ) + { + 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 public_status_card { @@ -743,26 +797,42 @@ sub public_status_card { delete $self->stash->{layout}; - if ( - $user - and ( - $user->{public_level} & 0x02 - or ( $user->{public_level} & 0x01 - and $self->is_user_authenticated ) - ) - ) - { - my $status = $self->get_user_status( $user->{id} ); - $self->render( - '_public_status_card', - name => $name, - public_level => $user->{public_level}, - journey => $status - ); - } - else { + if ( not $user ) { $self->render('not_found'); + return; + } + + my $status = $self->get_user_status( $user->{id} ); + my $visibility; + if ( $status->{checked_in} ) { + $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; + } } + + $self->render( + '_public_status_card', + name => $name, + public_level => $user->{public_level}, + journey => $status, + journey_visibility => $visibility, + ); } sub status_card { @@ -772,6 +842,10 @@ sub status_card { delete $self->stash->{layout}; if ( $status->{checked_in} ) { + my $journey_visibility + = $self->compute_effective_visibility( + $self->current_user->{default_visibility_str}, + $status->{visibility_str} ); if ( defined $status->{arrival_countdown} and $status->{arrival_countdown} < ( 40 * 60 ) ) { @@ -781,19 +855,28 @@ sub status_card { my ( $connecting_trains, $transit_fyi ) = @_; $self->render( '_checked_in', - journey => $status, - connections => $connecting_trains, - transit_fyi => $transit_fyi + journey => $status, + journey_visibility => $journey_visibility, + connections => $connecting_trains, + transit_fyi => $transit_fyi ); } )->catch( sub { - $self->render( '_checked_in', journey => $status ); + $self->render( + '_checked_in', + journey => $status, + journey_visibility => $journey_visibility, + ); } )->wait; return; } - $self->render( '_checked_in', journey => $status ); + $self->render( + '_checked_in', + journey => $status, + journey_visibility => $journey_visibility, + ); } elsif ( $status->{cancellation} ) { $self->render_later; @@ -1693,11 +1776,12 @@ sub journey_details { } my $journey = $self->journeys->get_single( - uid => $uid, - journey_id => $journey_id, - verbose => 1, - with_datetime => 1, - with_polyline => 1, + uid => $uid, + journey_id => $journey_id, + verbose => 1, + with_datetime => 1, + with_polyline => 1, + with_visibility => 1, ); if ($journey) { @@ -1705,15 +1789,18 @@ sub journey_details { journeys => [$journey], include_manual => 1, ); + my $with_share; my $share_text; - my $with_share = $user->{is_public} & 0x40 ? 1 : 0; - if ( not $with_share and $user->{is_public} & 0x20 ) { - my $month_ago = DateTime->now( time_zone => 'Europe/Berlin' ) - ->subtract( weeks => 4 )->epoch; - $with_share = $journey->{rt_dep_ts} > $month_ago ? 1 : 0; - } - if ($with_share) { + my $visibility + = $self->compute_effective_visibility( + $user->{default_visibility_str}, + $journey->{visibility_str} ); + + if ( $visibility eq 'public' + or $visibility eq 'travelynx' + or $visibility eq 'unlisted' ) + { my $delay = 'pünktlich '; if ( $journey->{rt_arrival} != $journey->{sched_arrival} ) { $delay = sprintf( @@ -1724,6 +1811,7 @@ sub journey_details { ) / 60 ); } + $with_share = 1; $share_text = $journey->{km_route} ? sprintf( '%.0f km', $journey->{km_route} ) @@ -1733,13 +1821,15 @@ sub journey_details { $delay, $journey->{rt_arrival}->strftime('%H:%M') ); } + # TODO add token if visibility != public $self->render( 'journey', - error => undef, - journey => $journey, - with_map => 1, - with_share => $with_share, - share_text => $share_text, + error => undef, + journey => $journey, + journey_visibility => $visibility, + with_map => 1, + with_share => $with_share, + share_text => $share_text, %{$map_data}, ); } @@ -1754,6 +1844,112 @@ sub journey_details { } +sub visibility_form { + my ($self) = @_; + my $dep_ts = $self->param('dep_ts'); + my $journey_id = $self->param('id'); + my $action = $self->param('action') // 'none'; + my $user = $self->current_user; + my $user_level = $user->{default_visibility_str}; + my $uid = $user->{id}; + my $status = $self->get_user_status; + my $visibility = $status->{visibility}; + my $journey; + + if ($journey_id) { + $journey = $self->journeys->get_single( + uid => $uid, + journey_id => $journey_id, + with_datetime => 1, + with_visibility => 1, + ); + $visibility = $journey->{visibility}; + } + + if ( $action eq 'save' ) { + if ( $self->validation->csrf_protect->has_error('csrf_token') ) { + $self->render( + 'edit_visibility', + error => 'csrf', + user_level => $user_level, + journey => {} + ); + } + elsif ( $dep_ts and $dep_ts != $status->{sched_departure}->epoch ) { + + # TODO find and update appropriate past journey (if it exists) + $self->render( + 'edit_visibility', + error => 'old', + user_level => $user_level, + journey => {} + ); + } + else { + $self->app->log->debug("set visibility"); + if ($dep_ts) { + $self->in_transit->update_visibility( + uid => $uid, + visibility => $self->param('status_level'), + ); + $self->redirect_to('/'); + } + elsif ($journey_id) { + $self->journeys->update_visibility( + uid => $uid, + id => $journey_id, + visibility => $self->param('status_level'), + ); + $self->redirect_to( '/journey/' . $journey_id ); + } + } + return; + } + + # todo use visibility_str + if ( not defined $visibility ) { + $self->param( status_level => 'default' ); + } + elsif ( $visibility == 100 ) { + $self->param( status_level => 'public' ); + } + elsif ( $visibility == 80 ) { + $self->param( status_level => 'travelynx' ); + } + elsif ( $visibility == 30 ) { + $self->param( status_level => 'unlisted' ); + } + elsif ( $visibility == 10 ) { + $self->param( status_level => 'private' ); + } + + if ($journey_id) { + $self->render( + 'edit_visibility', + error => undef, + user_level => $user_level, + journey => $journey + ); + } + elsif ( $status->{checked_in} ) { + $self->param( dep_ts => $status->{sched_departure}->epoch ); + $self->render( + 'edit_visibility', + error => undef, + user_level => $user_level, + journey => $status + ); + } + else { + $self->render( + 'edit_visibility', + error => 'notfound', + user_level => $user_level, + journey => {} + ); + } +} + sub comment_form { my ($self) = @_; my $dep_ts = $self->param('dep_ts'); diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index d3b6d6b..78fd297 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -11,6 +11,22 @@ use 5.020; use DateTime; use JSON; +my %visibility_itoa = ( + 100 => 'public', + 80 => 'travelynx', + 60 => 'followers', + 30 => 'unlisted', + 10 => 'private', +); + +my %visibility_atoi = ( + public => 100, + travelynx => 80, + followers => 60, + unlisted => 30, + private => 10, +); + sub new { my ( $class, %opt ) = @_; @@ -117,11 +133,23 @@ sub get { } my $res = $db->select( $table, '*', { user_id => $uid } ); + my $ret; if ( $opt{with_data} ) { - return $res->expand->hash; + $ret = $res->expand->hash; + } + else { + $ret = $res->hash; } - return $res->hash; + + if ( $opt{with_visibility} and $ret ) { + $ret->{visibility_str} + = $ret->{visibility} + ? $visibility_itoa{ $ret->{visibility} } + : 'default'; + } + + return $ret; } sub get_all_active { @@ -449,4 +477,23 @@ sub update_user_data { ); } +sub update_visibility { + my ( $self, %opt ) = @_; + + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + + my $visibility; + + if ( $opt{visibility} and $visibility_atoi{ $opt{visibility} } ) { + $visibility = $visibility_atoi{ $opt{visibility} }; + } + + $db->update( + 'in_transit', + { visibility => $visibility }, + { user_id => $uid } + ); +} + 1; diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index 08d9ff0..a9c6200 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -15,6 +15,22 @@ use utf8; use DateTime; use JSON; +my %visibility_itoa = ( + 100 => 'public', + 80 => 'travelynx', + 60 => 'followers', + 30 => 'unlisted', + 10 => 'private', +); + +my %visibility_atoi = ( + public => 100, + travelynx => 80, + followers => 60, + unlisted => 30, + private => 10, +); + my @month_name = ( qw(Januar Februar März April Mai Juni Juli August September Oktober November Dezember) @@ -509,7 +525,7 @@ sub get { my @select = ( - qw(journey_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) + qw(journey_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) ); my %where = ( user_id => $uid, @@ -548,6 +564,24 @@ sub get { push( @select, 'polyline' ); } + if ( $opt{min_visibility} ) { + if ( $visibility_atoi{ $opt{min_visibility} } ) { + $opt{min_visibility} = $visibility_atoi{ $opt{min_visibility} }; + } + if ( $opt{with_default_visibility} ) { + $where{visibility} = [ + -or => { '=', undef }, + { '>=', $opt{min_visibility} } + ]; + } + else { + $where{visibility} = [ + -and => { '!=', undef }, + { '>=', $opt{min_visibility} } + ]; + } + } + my @travels; my $res = $db->select( 'journeys_str', \@select, \%where, \%order ); @@ -577,8 +611,16 @@ sub get { route => $entry->{route}, edited => $entry->{edited}, user_data => $entry->{user_data}, + visibility => $entry->{visibility}, }; + if ( $opt{with_visibility} ) { + $ref->{visibility_str} + = $ref->{visibility} + ? $visibility_itoa{ $ref->{visibility} } + : 'default'; + } + if ( $opt{with_polyline} ) { $ref->{polyline} = $entry->{polyline}; } @@ -1703,4 +1745,26 @@ sub get_connection_targets { return @destinations; } +sub update_visibility { + my ( $self, %opt ) = @_; + + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + + my $visibility; + + if ( $opt{visibility} and $visibility_atoi{ $opt{visibility} } ) { + $visibility = $visibility_atoi{ $opt{visibility} }; + } + + $db->update( + 'journeys', + { visibility => $visibility }, + { + user_id => $uid, + id => $opt{id} + } + ); +} + 1; diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 8f5ce1f..63cc261 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -11,6 +11,22 @@ use 5.020; use DateTime; use JSON; +my %visibility_itoa = ( + 100 => 'public', + 80 => 'travelynx', + 60 => 'followers', + 30 => 'unlisted', + 10 => 'private', +); + +my %visibility_atoi = ( + public => 100, + travelynx => 80, + followers => 60, + unlisted => 30, + private => 10, +); + my @sb_templates = ( undef, [ 'DBF', 'https://dbf.finalrewind.org/{name}?rt=1#{tt}{tn}' ], @@ -153,7 +169,16 @@ sub get_privacy_by_name { ); if ( my $user = $res->hash ) { - return $user; + return { + id => $user->{id}, + public_level => $user->{public_level}, # todo remove? + default_visibility => $user->{public_level} & 0x7f, + default_visibility_str => + $visibility_itoa{ $user->{public_level} & 0x7f }, + comments_visible => $user->{public_level} & 0x80 ? 1 : 0, + past_visible => ( $user->{public_level} & 0x300 ) >> 8, + past_all => $user->{public_level} & 0x400 ? 1 : 0, + }; } return; } @@ -164,6 +189,14 @@ sub set_privacy { my $uid = $opt{uid}; my $public_level = $opt{level}; + if ( not defined $public_level and defined $opt{default_visibility} ) { + $public_level + = ( $opt{default_visibility} & 0x7f ) + | ( $opt{comments_visible} ? 0x80 : 0x00 ) + | ( ( ( $opt{past_visible} // 0 ) << 8 ) & 0x300 ) + | ( $opt{past_all} ? 0x400 : 0 ); + } + $db->update( 'users', { public_level => $public_level }, { id => $uid } ); } @@ -333,12 +366,18 @@ sub get { )->hash; if ($user) { return { - id => $user->{id}, - name => $user->{name}, - status => $user->{status}, - is_public => $user->{public_level}, - email => $user->{email}, - sb_name => $user->{external_services} + id => $user->{id}, + name => $user->{name}, + status => $user->{status}, + is_public => $user->{public_level}, + default_visibility => $user->{public_level} & 0x7f, + default_visibility_str => + $visibility_itoa{ $user->{public_level} & 0x7f }, + comments_visible => $user->{public_level} & 0x80 ? 1 : 0, + past_visible => ( $user->{public_level} & 0x300 ) >> 8, + past_all => $user->{public_level} & 0x400 ? 1 : 0, + email => $user->{email}, + sb_name => $user->{external_services} ? $sb_templates[ $user->{external_services} & 0x07 ][0] : undef, sb_template => $user->{external_services} |