set visibility per journey (wip)

some odds and ends left to polish, but ready for testing
This commit is contained in:
Daniel Friesel 2023-02-27 22:14:54 +01:00
parent fb3878665b
commit 6d261197e3
No known key found for this signature in database
GPG key ID: 100D5BFB5166E005
16 changed files with 883 additions and 382 deletions

View file

@ -420,6 +420,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');

View file

@ -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 {

View file

@ -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;
$opt{past_visible} = 2;
}
else {
$public_level &= ~0x30;
}
if ( $self->param('history_age') eq 'infinite' ) {
$public_level |= 0x40;
}
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} );
}
}

View file

@ -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}";
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 (
$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";
}
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 (
$user
and (
$user->{public_level} & 0x22
or ( $user->{public_level} & 0x11
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',
);
}
else {
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->{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';
}
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,7 @@ 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,
@ -640,98 +680,112 @@ sub public_journey_details {
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 $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 => {}
);
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,
);
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');
}
return;
}
else {
$self->render('not_found');
# TODO re-add age check unless status_token_ok (helper function?)
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->{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');

View file

@ -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;
}
return $res->hash;
else {
$ret = $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;

View file

@ -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;

View file

@ -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}

View file

@ -8,8 +8,13 @@ var j_duration = 0;
var j_arrival = 0;
var j_dest = '';
var j_stops = [];
var j_token = '';
function upd_journey_data() {
$('.countdown').each(function() {
const journey_token = $(this).data('token');
if (journey_token) {
j_token = journey_token;
}
var journey_data = $(this).data('journey');
if (journey_data) {
journey_data = journey_data.split(';');
@ -105,7 +110,7 @@ function tvly_update_public() {
$('.publicstatuscol').each(function() {
user_name = $(this).data('user');
});
$.get('/ajax/status/' + user_name + '.html', function(data) {
$.get('/ajax/status/' + user_name + '.html', {token: j_token}, function(data) {
$('.publicstatuscol').html(data);
upd_journey_data();
setTimeout(tvly_update_public, 40000);

View file

@ -244,13 +244,16 @@
</div>
<div class="card-action">
% if ($journey->{arr_name}) {
<a style="margin-right: 0;" href="/journey/comment">
<i class="material-icons left" aria-hidden="true">comment</i> Kommentar
<a href="/journey/comment">
<i class="material-icons">comment</i>
</a>
<a style="margin-right: 0;" href="/journey/visibility">
<i class="material-icons"><%= visibility_icon($journey_visibility) %></i>
</a>
% }
% else {
<a class="action-undo blue-text" data-id="in_transit" data-checkints="<%= $journey->{timestamp}->epoch %>" style="margin-right: 0;">
<i class="material-icons left" aria-hidden="true">undo</i> Checkin Rückgängig
<i class="material-icons left" aria-hidden="true">undo</i> Rückgängig
</a>
% }
% if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) {
@ -259,7 +262,7 @@
style="margin-right: 0;"
data-station="<%= $journey->{arr_name}%>">
<i class="material-icons left">done</i>
Jetzt auschecken
Auschecken
</a>
% }
% elsif ($journey->{arr_name}) {
@ -270,7 +273,7 @@
<a class="action-share blue-text right"
style="margin-right: 0;"
% my $arr_text = q{};
% if ($journey->{real_arrival}->epoch and not $user->{is_public} & 0x02) {
% if ($journey->{real_arrival}->epoch and $journey_visibility eq 'private') {
% $arr_text = $journey->{real_arrival}->strftime(' Ankunft gegen %H:%M Uhr');
% }
% if ($user->{is_public} & 0x04 and $journey->{comment}) {
@ -279,13 +282,21 @@
% else {
data-text="Ich bin gerade <%= $attrib %> <%= $journey->{train_type} %> <%= $journey->{train_no} %> nach <%= $journey->{arr_name} . $arr_text %> #travelynx"
% }
% if ($user->{is_public} & 0x02) {
% if ($journey_visibility eq 'public') {
data-url="<%= url_for('/status')->to_abs->scheme('https') %>/<%= $user->{name} %>/<%= $journey->{sched_departure}->epoch %>"
% }
% elsif ($journey_visibility eq 'travelynx' or $journey_visibility eq 'unlisted') {
data-url="<%= url_for('/status')->to_abs->scheme('https') %>/<%= $user->{name} %>/<%= $journey->{sched_departure}->epoch %>?token=<%= $journey->{dep_eva} %>-<%= $journey->{timestamp}->epoch %>"
% }
>
<i class="material-icons left" aria-hidden="true">share</i> Teilen
</a>
% }
% else {
<a class="right" href="/journey/visibility">
<i class="material-icons left"><%= visibility_icon($journey_visibility) %></i> Sichtbarkeit
</a>
% }
</div>
</div>
% if ($journey->{arr_name}) {

View file

@ -3,7 +3,11 @@
<div class="card">
<div class="card-content">
<i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i>
<span class="card-title"><%= $name %> ist unterwegs</span>
<span class="card-title"><%= $name %> ist unterwegs
% if ($journey_visibility) {
<i class="material-icons right"><%= visibility_icon($journey_visibility) %></i>
% }
</span>
% if ($public_level & 0x04 and $journey->{comment}) {
<p>„<%= $journey->{comment} %>“</p>
% }
@ -16,6 +20,9 @@
% }
<div class="center-align countdown"
data-duration="<%= $journey->{journey_duration} // 0 %>"
% if (param('token')) {
data-token="<%= $journey->{dep_eva} %>-<%= $journey->{timestamp}->epoch %>-<%= $journey->{sched_departure}->epoch %>"
% }
data-arrival="<%= $journey->{real_arrival}->epoch %>">
% if ($journey->{departure_countdown} > 120) {
Abfahrt in <%= sprintf('%.f', $journey->{departure_countdown} / 60) %> Minuten
@ -166,20 +173,6 @@
<i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i>
<span class="card-title"><%= $name %> ist gerade nicht eingecheckt</span>
<p>
% if ($journey->{arr_name}) {
Zuletzt gesehen
% if ($journey->{real_arrival}->epoch and ($public_level & 0x20 or ($public_level & 0x10 and is_user_authenticated()))) {
%= $journey->{real_arrival}->strftime('am %d.%m.%Y')
in <b><%= $journey->{arr_name} %></b>
%= $journey->{real_arrival}->strftime('(Ankunft um %H:%M Uhr)')
% }
% else {
in <b><%= $journey->{arr_name} %></b>
% }
% }
% else {
Noch keine Zugfahrten geloggt.
% }
</p>
</div>
</div>

View file

@ -70,36 +70,10 @@
</td>
</tr>
<tr>
<th scope="row">Öffentliche Daten</th>
<th scope="row">Sichtbarkeit</th>
<td>
<a href="/account/privacy"><i class="material-icons">edit</i></a>
% if ($acc->{is_public} == 0) {
<span style="color: #999999;">Keine</span>
% }
% if ($acc->{is_public} & 0x01) {
Aktueller Status (nur mit Anmeldung)
% }
% elsif ($acc->{is_public} & 0x02) {
Aktueller Status
% }
% if ($acc->{is_public} & 0x0f and $acc->{is_public} & 0xf0) {
<br/>
% }
% if ($acc->{is_public} & 0x30) {
% if ($acc->{is_public} & 0x40) {
Vergangene Fahrten
% }
% else {
Fahrten der letzten vier Wochen
% }
% if ($acc->{is_public} & 0x10) {
(nur mit Anmeldung)
% }
% }
% if ($acc->{is_public} & 0x04) {
<br/>
Kommentare
% }
<span><i class="material-icons"><%= visibility_icon($acc->{default_visibility_str}) %></i></span>
</td>
</tr>
<tr>

View file

@ -38,6 +38,16 @@
% }
am
<b><%= $journey->{sched_departure}->strftime('%d.%m.%Y') %></b>
% if ($journey_visibility) {
% if (stash('username')) {
<i class="material-icons right"><%= visibility_icon($journey_visibility) %></i>
% }
% else {
<a class="right" href="/journey/visibility?id=<%= $journey->{id} %>">
<i class="material-icons"><%= visibility_icon($journey_visibility) %></i>
</a>
% }
% }
</p>
% if ($journey->{edited}) {
<p>

View file

@ -15,7 +15,7 @@
<div class="row">
<div class="col s12 statuscol">
% if ($status->{checked_in}) {
%= include '_checked_in', journey => $status;
%= include '_checked_in', journey => $status, journey_visibility => stash('journey_visibility');
% }
% elsif ($status->{cancelled}) {
<div class="card info-color">

View file

@ -1,10 +1,11 @@
<h1>Öffentliche Daten</h1>
<div class="row">
<div class="col s12">
Hier kannst du auswählen, welche Aspekte deines Accounts bzw. deiner
Bahnfahrten öffentlich einsehbar sind. Öffentliche Daten sind
grundsätzlich für <i>alle</i> einsehbar, die die (leicht erratbare) URL
kennen.
Hier kannst du auswählen, welche Personengruppen deine Fahrten bei
travelynx einsehen können und ob dies auch für vergangene Fahrten
gelten soll. Nach dem Einchecken in einen Zug hast du im
Checkin-Fenster die Möglichkeit, für die aktuelle Fahrt eine
abweichende Sichtbarkeit einzustellen.
</div>
</div>
%= form_for '/account/privacy' => (method => 'POST') => begin
@ -13,33 +14,62 @@
<div class="row">
<div class="input-field col s12">
<div>
<label>
%= radio_button status_level => 'private'
<span>Nicht sichtbar</span>
</label>
</div><div>
<label>
%= radio_button status_level => 'intern'
<span>Nur mit Anmeldung</span>
</label>
</div><div>
<label>
%= radio_button status_level => 'extern'
<span>Öffentlich</span>
</label>
<label>
%= radio_button status_level => 'public'
<span><i class="material-icons left"><%= visibility_icon('public') %></i>Öffentlich: Im Profil verlinkt und beliebig zugänglich.</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<div>
<label>
%= radio_button status_level => 'travelynx'
<span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Lokal: Nur für<!-- Personen, die dir folgen oder auf dieser Seite angemeldet sind --> auf dieser Seite angemeldete Acounts sowie nicht angemeldete Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span>
</label>
</div>
</div>
</div>
<!--
<div class="row">
<div class="input-field col s12">
<div>
<label>
%= radio_button status_level => 'fedi'
<span><i class="material-icons left"><%= visibility_icon('fedi') %></i>Fedi: nur für Personen, die deinem Account folgen.</span>
</label>
</div>
</div>
</div>
-->
<div class="row">
<div class="input-field col s12">
<div>
<label>
%= radio_button status_level => 'unlisted'
<span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Verlinkbar: Nur für Personen zugänglich, denen du mithilfe der Teilen-Funktion einen Link schickst.</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<div>
<label>
%= radio_button status_level => 'private'
<span><i class="material-icons left"><%= visibility_icon('private') %></i>Privat: nur für dich sichtbar.</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
Hier kannst du auswählen, ob dein aktueller Status unter <a
href="/status/<%= $name %>">/status/<%= $name %></a> sowie <a
href="/p/<%= $name %>">/p/<%= $name %></a> abrufbar ist.
Wenn du eingecheckt bist, werden dort Zug, Start- und Zielstation
sowie Abfahrts- und Ankunftszeit gezeigt, andernfalls lediglich der
Zielbahnhof der letzten Reise. Wann die letzte Reise beendet wurde,
wird nur angegeben, wenn deine vergangenen Zugfahrten sichtbar sind
(siehe unten).
Wenn du (mit passender Sichtbarkeit) eingecheckt bist, werden unter
<a href="/status/<%= $name %>">/status/<%= $name %></a> sowie <a
href="/p/<%= $name %>">/p/<%= $name %></a> Zug, Start- und
Zielstation sowie Abfahrts- und Ankunftszeit gezeigt. Andernfalls
wird angegeben, dass du gerade nicht eingecheckt seist.
</div>
</div>
<h2>Vergangene Zugfahrten</h2>
@ -85,7 +115,10 @@
dort nicht eingetragene Fahrten sind jedoch weiterhin über /p/<%=
$name %>/j/ID zugänglich. Da die ID (mit Lücken) aufsteigend vergeben
wird, sind effektiv alle deiner vergangenen Fahrten (oder alle Fahrten
der letzten vier Wochen) öffentlich.
der letzten vier Wochen) öffentlich. Auch hier besteht die
Möglichkeit, für einzelne Fahrten eine abweichende Sichtbarkeit
einzustellen. Sofern die Sichtbarkeit auf die letzten vier Wochen
beschränkt ist, hat dies jedoch Vorrang.
</div>
</div>
<h2>Sonstiges</h2>
@ -101,6 +134,7 @@
<div class="col s12">
Wenn aktiv, sind von dir eingetragene Freitext-Kommentare in deinem
aktuellen Status sowie bei deinen vergangenen Zugfahrten sichtbar.
Diese Einstellung kann nicht pro Fahrt verändert werden.
</div>
</div>
<div class="row">

View file

@ -10,14 +10,12 @@
</div>
</div>
% }
% if ($public_level & 0x02 or ($public_level & 0x01 and is_user_authenticated())) {
<div class="row">
<div class="col s12 publicstatuscol" data-user="<%= $name %>">
%= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey
</div>
<div class="row">
<div class="col s12 publicstatuscol" data-user="<%= $name %>">
%= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey, journey_visibility => $journey_visibility
</div>
% }
% if ($public_level & 0x20 or ($public_level & 0x10 and is_user_authenticated())) {
</div>
% if ($journeys and @{$journeys}) {
<div class="row">
<div class="col s12">
<h2>Letzte Fahrten von <%= $name %></h1>

View file

@ -1,5 +1,5 @@
<div class="row">
<div class="col s12 publicstatuscol" data-user="<%= $name %>">
%= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey
%= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey, journey_visibility => $journey_visibility
</div>
</div>