Skip to content

Commit

Permalink
Merge branch 'main' into fedtest2
Browse files Browse the repository at this point in the history
  • Loading branch information
dimkr committed Feb 8, 2025
2 parents a2a8916 + 965fe98 commit 583db1f
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 21 deletions.
4 changes: 2 additions & 2 deletions fed/inbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ func (l *Listener) handleInbox(w http.ResponseWriter, r *http.Request) {
for queued.Type == ap.Announce {
if inner, ok := queued.Object.(*ap.Activity); ok {
queued = inner
} else if o, ok := queued.Object.(*ap.Object); ok && o.Type == ap.Page {
} else if o, ok := queued.Object.(*ap.Object); ok {
slog.Debug("Wrapping object with Update activity", "activity", &activity, "sender", sender.ID, "object", o.ID)

// hack for Lemmy: wrap a Page inside Announce with Update
Expand Down Expand Up @@ -382,7 +382,7 @@ func (l *Listener) handleInbox(w http.ResponseWriter, r *http.Request) {
Actor: queued.Actor,
Object: id,
}
} else if err == nil && exists && activity.Type == ap.Delete {
} else if err == nil && exists && queued.Type == ap.Delete {
slog.Warn("Ignoring forwarded Delete activity for existing object", "activity", &activity, "id", id, "sender", sender.ID)
w.WriteHeader(http.StatusBadRequest)
return
Expand Down
9 changes: 2 additions & 7 deletions front/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,8 @@ var supportedImageTypes = map[string]struct{}{
}

func (h *Handler) uploadAvatar(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
return
}

if r.Body == nil {
w.Redirect("/users/oops")
if r.User == nil || r.Body == nil {
w.Redirectf("gemini://%s/users/oops", h.Domain)
return
}

Expand Down
83 changes: 83 additions & 0 deletions front/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright 2025 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 (
"bufio"
"encoding/csv"

"github.com/dimkr/tootik/front/text"
)

const (
csvBufferSize = 32 * 1024
csvRows = 200
)

var csvHeader = []string{"ID", "Type", "Inserted", "Activity"}

func (h *Handler) export(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
return
}

output := csv.NewWriter(bufio.NewWriterSize(w, csvBufferSize))

rows, err := h.DB.QueryContext(
r.Context,
`
select activity->>'$.id', activity->>'$.type', datetime(inserted, 'unixepoch'), activity from outbox
where
activity->>'$.actor' = ?
order by inserted desc
limit ?
`,
r.User.ID,
csvRows,
)
if err != nil {
r.Log.Warn("Failed to fetch activities", "error", err)
w.Error()
return
}
defer rows.Close()

w.Status(20, "text/csv")

if err := output.Write(csvHeader); err != nil {
r.Log.Warn("Failed to write header", "error", err)
return
}

var fields [4]string
for rows.Next() {
if err := rows.Scan(&fields[0], &fields[1], &fields[2], &fields[3]); err != nil {
r.Log.Warn("Failed to scan activity", "error", err)
continue
}
if err := output.Write(fields[:]); err != nil {
r.Log.Warn("Failed to write a line", "error", err)
return
}
}

output.Flush()
if err := output.Error(); err != nil {
r.Log.Warn("Failed to flush output", "error", err)
}
}
13 changes: 7 additions & 6 deletions front/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ func NewHandler(domain string, closed bool, cfg *cfg.Config, resolver ap.Resolve

h.handlers[regexp.MustCompile(`^/users/upload/avatar;([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.uploadAvatar
h.handlers[regexp.MustCompile(`^/users/bio$`)] = h.bio
h.handlers[regexp.MustCompile(`^/users/upload/bio;([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.uploadBio
h.handlers[regexp.MustCompile(`^/users/upload/bio;([a-z]+)=([^;]+)(?:;([a-z]+)=([^;]+)){0,1}$`)] = h.uploadBio
h.handlers[regexp.MustCompile(`^/users/name$`)] = h.name
h.handlers[regexp.MustCompile(`^/users/alias$`)] = h.alias
h.handlers[regexp.MustCompile(`^/users/move$`)] = h.move
h.handlers[regexp.MustCompile(`^/users/certificates$`)] = withUserMenu(h.certificates)
h.handlers[regexp.MustCompile(`^/users/certificates/approve/(\S+)$`)] = withUserMenu(h.approve)
h.handlers[regexp.MustCompile(`^/users/certificates/revoke/(\S+)$`)] = withUserMenu(h.revoke)
h.handlers[regexp.MustCompile(`^/users/export$`)] = h.export

h.handlers[regexp.MustCompile(`^/view/(\S+)$`)] = withUserMenu(h.view)
h.handlers[regexp.MustCompile(`^/users/view/(\S+)$`)] = withUserMenu(h.view)
Expand All @@ -115,11 +116,11 @@ func NewHandler(domain string, closed bool, cfg *cfg.Config, resolver ap.Resolve
h.handlers[regexp.MustCompile(`^/users/edit/(\S+)`)] = h.edit
h.handlers[regexp.MustCompile(`^/users/delete/(\S+)`)] = h.delete

h.handlers[regexp.MustCompile(`^/users/upload/dm;([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.uploadDM
h.handlers[regexp.MustCompile(`^/users/upload/whisper;([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.uploadWhisper
h.handlers[regexp.MustCompile(`^/users/upload/say;([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.uploadSay
h.handlers[regexp.MustCompile(`^/users/upload/edit/([^;]+);([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.editUpload
h.handlers[regexp.MustCompile(`^/users/upload/reply/([^;]+);([a-z]+)=([^;]+);([a-z]+)=([^;]+)`)] = h.replyUpload
h.handlers[regexp.MustCompile(`^/users/upload/dm;([a-z]+)=([^;]+)(?:;([a-z]+)=([^;]+)){0,1}$`)] = h.uploadDM
h.handlers[regexp.MustCompile(`^/users/upload/whisper;([a-z]+)=([^;]+)(?:;([a-z]+)=([^;]+)){0,1}$`)] = h.uploadWhisper
h.handlers[regexp.MustCompile(`^/users/upload/say;([a-z]+)=([^;]+)(?:;([a-z]+)=([^;]+)){0,1}$`)] = h.uploadSay
h.handlers[regexp.MustCompile(`^/users/upload/edit/([^;]+);([a-z]+)=([^;]+)(?:;([a-z]+)=([^;]+)){0,1}$`)] = h.editUpload
h.handlers[regexp.MustCompile(`^/users/upload/reply/([^;]+);([a-z]+)=([^;]+)(?:;([a-z]+)=([^;]+)){0,1}$`)] = h.replyUpload

h.handlers[regexp.MustCompile(`^/users/resolve$`)] = withUserMenu(h.resolve)

Expand Down
9 changes: 6 additions & 3 deletions front/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,20 @@ func readQuery(w text.Writer, r *Request, prompt string) (string, bool) {

func (h *Handler) readBody(w text.Writer, r *Request, args []string) (string, bool) {
if r.Body == nil {
w.Redirect("/users/oops")
w.Redirectf("gemini://%s/users/oops", h.Domain)
return "", false
}

var sizeStr, mimeType string
if args[1] == "size" && args[3] == "mime" {
if len(args) == 5 && args[1] == "size" && args[3] == "mime" {
sizeStr = args[2]
mimeType = args[4]
} else if args[1] == "mime" && args[3] == "size" {
} else if len(args) == 5 && args[1] == "mime" && args[3] == "size" {
sizeStr = args[4]
mimeType = args[2]
} else if args[1] == "size" {
sizeStr = args[2]
mimeType = "text/plain"
} else {
r.Log.Warn("Invalid parameters")
w.Status(40, "Invalid parameters")
Expand Down
1 change: 1 addition & 0 deletions front/static/users/help.gmi
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ This page allows you to:
* Notify followers about account migration from this instance
* Upload a .png, .jpg or .gif image to serve as your avatar (use your client certificate for authentication): up to {{.Config.MaxAvatarWidth}}x{{.Config.MaxAvatarHeight}} and {{.Config.MaxAvatarSize}} bytes, downscaled to {{.Config.AvatarWidth}}x{{.Config.AvatarHeight}}
* Manage client certificates associated with your account
* Export your 200 most recent activities to a CSV file containing raw ActivityPub data

> 📊 Status

Expand Down
1 change: 1 addition & 0 deletions front/static/users/settings.gmi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
## Account

=> /users/certificates 🎓 Certificates
=> /users/export 🥡 Export recent activities

## Migration

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/google/uuid v1.6.0
github.com/mattn/go-sqlite3 v1.14.24
github.com/stretchr/testify v1.10.0
golang.org/x/image v0.23.0
golang.org/x/image v0.24.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
35 changes: 35 additions & 0 deletions test/upload_reply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,38 @@ func TestUploadReply_PostToFollowers(t *testing.T) {
assert.NotContains(local, "Hello world")
assert.NotContains(local, "Welcome Bob")
}

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

assert := assert.New(t)

follow := server.Handle("/users/follow/"+strings.TrimPrefix(server.Bob.ID, "https://"), server.Alice)
assert.Equal(fmt.Sprintf("30 /users/outbox/%s\r\n", strings.TrimPrefix(server.Bob.ID, "https://")), follow)

whisper := server.Handle("/users/whisper?Hello%20world", server.Bob)
assert.Regexp(`^30 /users/view/\S+\r\n$`, whisper)

id := whisper[15 : len(whisper)-2]

view := server.Handle("/users/view/"+id, server.Bob)
assert.Contains(view, "Hello world")
assert.NotContains(view, "Welcome Bob")

reply := server.Upload(fmt.Sprintf("/users/upload/reply/%s;size=11", id), server.Alice, []byte("Welcome Bob"))
assert.Regexp(fmt.Sprintf("^30 gemini://%s/users/view/\\S+\r\n$", domain), reply)

view = server.Handle("/users/view/"+id, server.Alice)
assert.Contains(view, "Hello world")
assert.Contains(view, "Welcome Bob")

assert.NoError((inbox.FeedUpdater{Domain: domain, Config: server.cfg, DB: server.db}).Run(context.Background()))

users := server.Handle("/users", server.Bob)
assert.Contains(users, "Welcome Bob")

local := server.Handle("/local", nil)
assert.NotContains(local, "Hello world")
assert.NotContains(local, "Welcome Bob")
}

0 comments on commit 583db1f

Please sign in to comment.