add profile editor

This commit is contained in:
Derf Null 2023-06-04 14:28:04 +02:00
parent d4a6470141
commit 0172f0ce8a
No known key found for this signature in database
GPG key ID: 19E6E524EBB177BA
6 changed files with 204 additions and 9 deletions

View file

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

View file

@ -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) = @_;

View file

@ -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],

View file

@ -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 ) = @_;

View file

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

View file

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