summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDerf Null <derf@finalrewind.org>2023-06-04 14:28:04 +0200
committerDerf Null <derf@finalrewind.org>2023-06-04 14:28:04 +0200
commit0172f0ce8ac543d287fef02786960d3e980556f4 (patch)
treeb164d901bfe11ac9842e9cc344569d4764558440
parentd4a647014194d635d1025f83e595138c71db9c1d (diff)
add profile editor
-rwxr-xr-xlib/Travelynx.pm2
-rw-r--r--lib/Travelynx/Controller/Account.pm68
-rwxr-xr-xlib/Travelynx/Controller/Profile.pm56
-rw-r--r--lib/Travelynx/Model/Users.pm24
-rw-r--r--templates/account.html.ep3
-rw-r--r--templates/edit_profile.html.ep60
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