Skip to content

Commit

Permalink
allow users to set their bio (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimkr committed Jan 5, 2024
1 parent 3f62e43 commit d650bb4
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 24 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Welcome, fedinaut! localhost.localdomain:8443 is an instance of tootik, a federa
🔭 Find user
🔥 Hashtags
📊 Statistics
📜 Set bio
🔔 New post
📣 New public post
🛟 Help
Expand Down Expand Up @@ -154,6 +155,7 @@ Users are authenticated using TLS client certificates; see [Gemini protocol spec
* /users/follow sends a follow request to a user.
* /users/unfollow deletes a follow request.
* /users/outbox is equivalent to /outbox but also includes a link to /users/follow or /users/unfollow.
* /users/bio allows users to edit their bio.

Some clients generate a certificate for / (all pages of this capsule) when /foo requests a client certificate, while others use the certificate requested by /foo only for /foo and /foo/bar. Therefore, pages that don't require authentication are also mirrored under /users:

Expand Down
4 changes: 3 additions & 1 deletion ap/actor.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 Dima Krasner
Copyright 2023, 2024 Dima Krasner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,4 +38,6 @@ type Actor struct {
Icon Attachment `json:"icon,omitempty"`
ManuallyApprovesFollowers bool `json:"manuallyApprovesFollowers"`
AlsoKnownAs Audience `json:"alsoKnownAs,omitempty"`
Published Time `json:"published"`
Updated *Time `json:"updated,omitempty"`
}
75 changes: 75 additions & 0 deletions front/bio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright 2024 Dima Krasner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package front

import (
"crypto/sha256"
"github.com/dimkr/tootik/front/text"
"github.com/dimkr/tootik/front/text/plain"
"net/url"
"time"
"unicode/utf8"
)

const (
minBioEditInterval = time.Minute * 30
maxBioLength = 500
)

func bio(w text.Writer, r *request) {
if r.User == nil {
w.Redirect("/users")
return
}

now := time.Now()

if (r.User.Updated != nil && now.Sub(r.User.Updated.Time) < minBioEditInterval) || (r.User.Updated == nil && now.Sub(r.User.Published.Time) < minBioEditInterval) {
r.Log.Warn("Throttled request to set summary")
w.Status(40, "Please try again later")
return
}

if r.URL.RawQuery == "" {
w.Status(10, "Summary")
return
}

summary, err := url.QueryUnescape(r.URL.RawQuery)
if err != nil {
w.Status(40, "Bad input")
return
}

if utf8.RuneCountInString(summary) > maxBioLength {
w.Status(40, "Summary is too long")
return
}

if _, err := r.Exec(
"update persons set actor = json_set(actor, '$.summary', $1, '$.updated', $2) where id = $3",
plain.ToHTML(summary, nil),
now.Format(time.RFC3339Nano),
r.User.ID,
); err != nil {
r.Log.Error("Failed to update summary", "error", err)
w.Error()
return
}

w.Redirectf("/users/outbox/%x", sha256.Sum256([]byte(r.User.ID)))
}
4 changes: 3 additions & 1 deletion front/handler.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 Dima Krasner
Copyright 2023, 2024 Dima Krasner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -72,6 +72,8 @@ func NewHandler(closed bool) Handler {
h[regexp.MustCompile(`^/outbox/[0-9a-f]{64}$`)] = withUserMenu(userOutbox)
h[regexp.MustCompile(`^/users/outbox/[0-9a-f]{64}$`)] = withUserMenu(userOutbox)

h[regexp.MustCompile(`^/users/bio$`)] = bio

h[regexp.MustCompile(`^/view/[0-9a-f]{64}$`)] = withUserMenu(view)
h[regexp.MustCompile(`^/users/view/[0-9a-f]{64}$`)] = withUserMenu(view)

Expand Down
3 changes: 2 additions & 1 deletion front/menu.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 Dima Krasner
Copyright 2023, 2024 Dima Krasner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -52,6 +52,7 @@ func writeUserMenu(w text.Writer, user *ap.Actor) {
if user == nil {
w.Link(fmt.Sprintf("gemini://%s/users", cfg.Domain), "🔑 Sign in")
} else {
w.Link("/users/bio", "📜 Set bio")
w.Link("/users/whisper", "🔔 New post")
w.Link("/users/say", "📣 New public post")
}
Expand Down
4 changes: 4 additions & 0 deletions front/static/users/help.gmi
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ This page shows popular hashtags, allowing you to discover trends and shared int

This page shows various statistics about this server and the parts of the fediverse it's connected to.

> 📜 Set bio

Follow this link to set the short (up to 500 characters long) description that appears at the top of your profile.

> 🔔 New post

Follow this link to publish a private post and send it to your followers.
Expand Down
4 changes: 3 additions & 1 deletion front/user/create.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 Dima Krasner
Copyright 2023, 2024 Dima Krasner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,6 +30,7 @@ import (
"github.com/dimkr/tootik/ap"
"github.com/dimkr/tootik/cfg"
"github.com/dimkr/tootik/fed/icon"
"time"
)

func gen(ctx context.Context) ([]byte, []byte, error) {
Expand Down Expand Up @@ -95,6 +96,7 @@ func Create(ctx context.Context, db *sql.DB, id, name, certHash string) (*ap.Act
PublicKeyPem: string(pub),
},
ManuallyApprovesFollowers: false,
Published: ap.Time{Time: time.Now()},
}

body, err := json.Marshal(actor)
Expand Down
96 changes: 96 additions & 0 deletions test/bio_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Copyright 2024 Dima Krasner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import (
"crypto/sha256"
"fmt"
"github.com/stretchr/testify/assert"
"strings"
"testing"
"time"
)

func TestBio_Throttled(t *testing.T) {
server := newTestServer()
defer server.Shutdown()

assert := assert.New(t)

summary := server.Handle("/users/bio?Hello%20world", server.Alice)
assert.Equal("40 Please try again later\r\n", summary)
}

func TestBio_HappyFlow(t *testing.T) {
server := newTestServer()
defer server.Shutdown()

assert := assert.New(t)

server.Alice.Published.Time = server.Alice.Published.Time.Add(-time.Hour)

summary := server.Handle("/users/bio?Hello%20world", server.Alice)
assert.Equal(fmt.Sprintf("30 /users/outbox/%x\r\n", sha256.Sum256([]byte(server.Alice.ID))), summary)

outbox := server.Handle(fmt.Sprintf("/users/outbox/%x", sha256.Sum256([]byte(server.Alice.ID))), server.Bob)
assert.Contains(strings.Split(outbox, "\n"), "> Hello world")
}

func TestBio_TooLong(t *testing.T) {
server := newTestServer()
defer server.Shutdown()

assert := assert.New(t)

server.Alice.Published.Time = server.Alice.Published.Time.Add(-time.Hour)

summary := server.Handle("/users/bio?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", server.Alice)
assert.Equal("40 Summary is too long\r\n", summary)
}

func TestBio_MultiLine(t *testing.T) {
server := newTestServer()
defer server.Shutdown()

assert := assert.New(t)

server.Alice.Published.Time = server.Alice.Published.Time.Add(-time.Hour)

summary := server.Handle("/users/bio?Hello%0Aworld", server.Alice)
assert.Equal(fmt.Sprintf("30 /users/outbox/%x\r\n", sha256.Sum256([]byte(server.Alice.ID))), summary)

outbox := strings.Split(server.Handle(fmt.Sprintf("/users/outbox/%x", sha256.Sum256([]byte(server.Alice.ID))), server.Bob), "\n")
assert.Contains(outbox, "> Hello")
assert.Contains(outbox, "> world")
}

func TestBio_MultiLineWithLink(t *testing.T) {
server := newTestServer()
defer server.Shutdown()

assert := assert.New(t)

server.Alice.Published.Time = server.Alice.Published.Time.Add(-time.Hour)

summary := server.Handle("/users/bio?Hi%21%0A%0AI%27m%20a%20friend%20of%20https%3a%2f%2flocalhost.localdomain%3a8443%2fuser%2fbob", server.Alice)
assert.Equal(fmt.Sprintf("30 /users/outbox/%x\r\n", sha256.Sum256([]byte(server.Alice.ID))), summary)

outbox := strings.Split(server.Handle(fmt.Sprintf("/users/outbox/%x", sha256.Sum256([]byte(server.Alice.ID))), server.Bob), "\n")
assert.Contains(outbox, "> Hi!")
assert.Contains(outbox, "> I'm a friend of https://localhost.localdomain:8443/user/bob")
assert.Contains(outbox, "=> https://localhost.localdomain:8443/user/bob https://localhost.localdomain:8443/user/bob")
}
Loading

0 comments on commit d650bb4

Please sign in to comment.