diff options
author | Derf Null <derf@finalrewind.org> | 2023-06-04 14:28:04 +0200 |
---|---|---|
committer | Derf Null <derf@finalrewind.org> | 2023-06-04 14:28:04 +0200 |
commit | 0172f0ce8ac543d287fef02786960d3e980556f4 (patch) | |
tree | b164d901bfe11ac9842e9cc344569d4764558440 | |
parent | d4a647014194d635d1025f83e595138c71db9c1d (diff) |
add profile editor
-rwxr-xr-x | lib/Travelynx.pm | 2 | ||||
-rw-r--r-- | lib/Travelynx/Controller/Account.pm | 68 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Profile.pm | 56 | ||||
-rw-r--r-- | lib/Travelynx/Model/Users.pm | 24 | ||||
-rw-r--r-- | templates/account.html.ep | 3 | ||||
-rw-r--r-- | templates/edit_profile.html.ep | 60 |
6 files changed, 204 insertions, 9 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 2688556..adb6132 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -2213,6 +2213,7 @@ sub startup { $authed_r->get('/account')->to('account#account'); $authed_r->get('/account/privacy')->to('account#privacy'); + $authed_r->get('/account/profile')->to('account#profile'); $authed_r->get('/account/hooks')->to('account#webhook'); $authed_r->get('/account/traewelling')->to('traewelling#settings'); $authed_r->get('/account/insight')->to('account#insight'); @@ -2239,6 +2240,7 @@ sub startup { $authed_r->get('/s/*station')->to('traveling#station'); $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); $authed_r->post('/account/privacy')->to('account#privacy'); + $authed_r->post('/account/profile')->to('account#profile'); $authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/account/traewelling')->to('traewelling#settings'); $authed_r->post('/account/insight')->to('account#insight'); diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 1fa762a..af97c96 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -7,6 +7,8 @@ use Mojo::Base 'Mojolicious::Controller'; use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); use JSON; +use Mojo::Util qw(xml_escape); +use Text::Markdown; use UUID::Tiny qw(:std); my %visibility_itoa = ( @@ -499,6 +501,72 @@ sub privacy { } } +sub profile { + my ($self) = @_; + my $user = $self->current_user; + + if ( $self->param('action') and $self->param('action') eq 'save' ) { + if ( $self->validation->csrf_protect->has_error('csrf_token') ) { + $self->render( + 'edit_profile', + invalid => 'csrf', + ); + return; + } + my $md = Text::Markdown->new; + my $bio = $self->param('bio'); + + if ( length($bio) > 2000 ) { + $bio = substr( $bio, 0, 2000 ) . '…'; + } + + my $profile = { + bio => { + markdown => $bio, + html => $md->markdown( xml_escape($bio) ), + }, + metadata => [], + }; + for my $i ( 0 .. 20 ) { + my $key = $self->param("key_$i"); + my $value = $self->param("value_$i"); + if ($key) { + if ( length($value) > 500 ) { + $value = substr( $value, 0, 500 ) . '…'; + } + my $html_value + = ( $value + =~ s{ \[ ([^]]+) \]\( ([^)]+) \) }{'<a href="' . xml_escape($2) . '">' . xml_escape($1) .'</a>' }egrx + ); + $profile->{metadata}[$i] = { + key => $key, + value => { + markdown => $value, + html => $html_value, + }, + }; + } + else { + last; + } + } + $self->users->set_profile( + uid => $user->{id}, + profile => $profile + ); + $self->redirect_to( '/p/' . $user->{name} ); + } + + my $profile = $self->users->get_profile( uid => $user->{id} ); + $self->param( bio => $profile->{bio}{markdown} ); + for my $i ( 0 .. $#{ $profile->{metadata} } ) { + $self->param( "key_$i" => $profile->{metadata}[$i]{key} ); + $self->param( "value_$i" => $profile->{metadata}[$i]{value}{markdown} ); + } + + $self->render( 'edit_profile', name => $user->{name} ); +} + sub insight { my ($self) = @_; diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm index 1660a5b..86b8922 100755 --- a/lib/Travelynx/Controller/Profile.pm +++ b/lib/Travelynx/Controller/Profile.pm @@ -70,6 +70,30 @@ sub profile { return; } + my $profile = $self->users->get_profile( uid => $user->{id} ); + + my $my_user; + my $relation; + my $inverse_relation; + my $is_self; + if ( $self->is_user_authenticated ) { + $my_user = $self->current_user; + if ( $my_user->{id} == $user->{id} ) { + $is_self = 1; + $my_user = undef; + } + else { + $relation = $self->users->get_relation( + subject => $my_user->{id}, + object => $user->{id} + ); + $inverse_relation = $self->users->get_relation( + subject => $user->{id}, + object => $my_user->{id} + ); + } + } + my $status = $self->get_user_status( $user->{id} ); my $visibility; if ( $status->{checked_in} or $status->{arr_name} ) { @@ -84,7 +108,12 @@ sub profile { and $self->status_token_ok($status) ) or ( $visibility eq 'travelynx' - and ( $self->is_user_authenticated + and ( $my_user + or $self->status_token_ok($status) ) + ) + or ( + $visibility eq 'followers' + and ( ( $relation and $relation eq 'follows' ) or $self->status_token_ok($status) ) ) ) @@ -104,7 +133,7 @@ sub profile { my @journeys; if ( $user->{past_visible} == 2 - or ( $user->{past_visible} == 1 and $self->is_user_authenticated ) ) + or ( $user->{past_visible} == 1 and $my_user ) ) { my %opt = ( @@ -122,7 +151,10 @@ sub profile { if ( $user->{default_visibility_str} eq 'public' or ( $user->{default_visibility_str} eq 'travelynx' - and $self->is_user_authenticated ) + and $my_user ) + or ( $user->{default_visibility_str} eq 'followers' + and $relation + and $relation eq 'follows' ) ) { $opt{with_default_visibility} = 1; @@ -131,8 +163,13 @@ sub profile { $opt{with_default_visibility} = 0; } - if ( $self->is_user_authenticated ) { - $opt{min_visibility} = 'travelynx'; + if ($my_user) { + if ( $relation and $relation eq 'follows' ) { + $opt{min_visibility} = 'followers'; + } + else { + $opt{min_visibility} = 'travelynx'; + } } else { $opt{min_visibility} = 'public'; @@ -143,9 +180,12 @@ sub profile { $self->render( 'profile', - name => $name, - uid => $user->{id}, - public_level => $user->{public_level}, + name => $name, + uid => $user->{id}, + bio => $profile->{bio}{html}, + metadata => $profile->{metadata}, + public_level => $user->{public_level}, + is_self => $is_self, journey => $status, journey_visibility => $visibility, journeys => [@journeys], diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index fbd53fd..7326e34 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -750,6 +750,30 @@ sub update_webhook_status { ); } +sub set_profile { + my ( $self, %opt ) = @_; + + my $db = $opt{db} // $self->{pg}->db; + my $uid = $opt{uid}; + my $profile = $opt{profile}; + + $db->update( + 'users', + { profile => JSON->new->encode($profile) }, + { id => $uid } + ); +} + +sub get_profile { + my ( $self, %opt ) = @_; + + my $db = $opt{db} // $self->{pg}->db; + my $uid = $opt{uid}; + + return $db->select( 'users', ['profile'], { id => $uid } ) + ->expand->hash->{profile}; +} + sub get_relation { my ( $self, %opt ) = @_; diff --git a/templates/account.html.ep b/templates/account.html.ep index e5dba60..ef6b847 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -73,7 +73,8 @@ <th scope="row">Sichtbarkeit</th> <td> <a href="/account/privacy"><i class="material-icons">edit</i></a> - <span><i class="material-icons"><%= visibility_icon($acc->{default_visibility_str}) %></i></span> + <i class="material-icons"><%= visibility_icon($acc->{default_visibility_str}) %></i> + • <a href="/p/<%= $acc->{name} %>">Öffentliches Profil</a> </td> </tr> <tr> diff --git a/templates/edit_profile.html.ep b/templates/edit_profile.html.ep new file mode 100644 index 0000000..55b1e1e --- /dev/null +++ b/templates/edit_profile.html.ep @@ -0,0 +1,60 @@ +<div class="row"> + <div class="col s12"> + <h1>Profil bearbeiten</h1> + </div> +</div> +%= form_for '/account/profile' => (method => 'POST') => begin + %= csrf_field + <div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title"><%= $name %></span> + <p> + Markdown möglich, maximal 2000 Zeichen. + %= text_area 'bio', id => 'bio', class => 'materialize-textarea' + </p> + </div> + <div class="card-action"> + <a href="/p/<%= $name %>" class="waves-effect waves-light btn"> + Abbrechen + </a> + <button class="btn waves-effect waves-light right" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + </div> + </div> + </div> + <div class="row"> + <div class="col s12"> + Metadaten: Markdown-Links im Inhalt erlaubt, jeweils maximal 500 Zeichen + </div> + </div> + % for my $i (0 .. 10) { + <div class="row"> + <div class="input-field col l3 m12 s12"> + %= text_field "key_$i", id => "key_$i", maxlength => 50 + <label for="key_<%= $i %>">Attribut</label> + </div> + <div class="input-field col l9 m12 s12"> + %= text_field "value_$i", id => "value_$i", maxlength => 500 + <label for="value_<%= $i %>">Inhalt</label> + </div> + </div> + % } + <div class="row center-align"> + <div class="col s6"> + <a href="/p/<%= $name %>" class="waves-effect waves-light btn"> + Abbrechen + </a> + </div> + <div class="col s6"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + </div> +%= end |