From 0ce5d3ba2671e4e1fecd9dc98e3759bbfeda948a Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:28:35 -0400 Subject: [PATCH 1/9] Accept Like activities from the fediverse This includes database changes; update with `writefreely db migrate`. Ref T906 --- activitypub.go | 116 ++++++++++++++++++++++++++++++++++++++- database_activitypub.go | 49 +++++++++++++++++ migrations/migrations.go | 1 + migrations/v16.go | 38 +++++++++++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 database_activitypub.go create mode 100644 migrations/v16.go diff --git a/activitypub.go b/activitypub.go index 6a3b0a1..323bf90 100644 --- a/activitypub.go +++ b/activitypub.go @@ -22,6 +22,7 @@ import ( "net/http/httputil" "net/url" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -45,6 +46,11 @@ const ( apCacheTime = time.Minute ) +var ( + apCollectionPostIRIRegex = regexp.MustCompile("/api/collections/([a-z0-9\\-]+)/posts/([a-z0-9\\-]+)$") + apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-]{12,16})$") +) + var instanceColl *Collection func initActivityPub(app *App) { @@ -351,11 +357,76 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request a := streams.NewAccept() p := c.PersonObject() var to *url.URL - var isFollow, isUnfollow bool + var isFollow, isUnfollow, isLike bool + var likePostID string fullActor := &activitystreams.Person{} var remoteUser *RemoteUser res := &streams.Resolver{ + LikeCallback: func(l *streams.Like) error { + isLike = true + + // 1) Use the Like concrete type here + // 2) Errors are propagated to res.Deserialize call below + m["@context"] = []string{activitystreams.Namespace} + b, _ := json.Marshal(m) + if debugging { + log.Info("Like: %s", b) + } + + _, likeID := l.GetId() + if likeID == nil { + log.Error("Didn't resolve Like ID") + } + if p := l.HasObject(0); p == streams.NoPresence { + return fmt.Errorf("no object for Like activity at index 0") + } + + obj := l.Raw().GetObjectIRI(0) + /* + // TODO: handle this more robustly + l.ResolveObject(&streams.Resolver{ + LinkCallback: func(link *streams.Link) error { + return nil + }, + }, 0) + */ + + // Get post ID from URL + var collAlias, slug string + if m := apCollectionPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 3 { + collAlias = m[1] + slug = m[2] + } else if m = apDraftPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 2 { + likePostID = m[1] + } else { + return fmt.Errorf("unable to match objectIRI: %s", obj) + } + + // Get postID if all we have is collection and slug + if collAlias != "" && slug != "" { + c, err := app.db.GetCollection(collAlias) + if err != nil { + return err + } + p, err := app.db.GetPost(slug, c.ID) + if err != nil { + return err + } + likePostID = p.ID + } + + // Finally, get actor information + _, from := l.GetActor(0) + if from == nil { + return fmt.Errorf("No valid actor string") + } + fullActor, remoteUser, err = getActor(app, from.String()) + if err != nil { + return err + } + return nil + }, FollowCallback: func(f *streams.Follow) error { isFollow = true @@ -435,6 +506,48 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request return err } + // Handle synchronous activities + if isLike { + t, err := app.db.Begin() + if err != nil { + log.Error("Unable to start transaction: %v", err) + return fmt.Errorf("unable to start transaction: %v", err) + } + + var remoteUserID int64 + if remoteUser != nil { + remoteUserID = remoteUser.ID + } else { + remoteUserID, err = apAddRemoteUser(app, t, fullActor) + } + + // Add like + _, err = t.Exec("INSERT INTO remote_likes (post_id, remote_user_id, created) VALUES (?, ?, NOW())", likePostID, remoteUserID) + if err != nil { + if !app.db.isDuplicateKeyErr(err) { + t.Rollback() + log.Error("Couldn't add like in DB: %v\n", err) + return fmt.Errorf("Couldn't add like in DB: %v", err) + } else { + t.Rollback() + log.Error("Couldn't add like in DB: %v\n", err) + return fmt.Errorf("Couldn't add like in DB: %v", err) + } + } + + err = t.Commit() + if err != nil { + t.Rollback() + log.Error("Rolling back after Commit(): %v\n", err) + return fmt.Errorf("Rolling back after Commit(): %v\n", err) + } + + if debugging { + log.Info("Successfully liked post %s by remote user %s", likePostID, remoteUser.URL) + } + return impart.RenderActivityJSON(w, "", http.StatusOK) + } + go func() { if to == nil { if debugging { @@ -469,6 +582,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request if remoteUser != nil { followerID = remoteUser.ID } else { + // TODO: use apAddRemoteUser() here, instead! // Add follower locally, since it wasn't found before res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url) VALUES (?, ?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox, fullActor.URL) if err != nil { diff --git a/database_activitypub.go b/database_activitypub.go new file mode 100644 index 0000000..9df3724 --- /dev/null +++ b/database_activitypub.go @@ -0,0 +1,49 @@ +/* + * Copyright © 2024 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package writefreely + +import ( + "database/sql" + "fmt" + "github.com/writeas/web-core/activitystreams" + "github.com/writeas/web-core/log" +) + +func apAddRemoteUser(app *App, t *sql.Tx, fullActor *activitystreams.Person) (int64, error) { + // Add remote user locally, since it wasn't found before + res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url) VALUES (?, ?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox, fullActor.URL) + if err != nil { + t.Rollback() + return -1, fmt.Errorf("couldn't add new remoteuser in DB: %v", err) + } + + remoteUserID, err := res.LastInsertId() + if err != nil { + t.Rollback() + return -1, fmt.Errorf("no lastinsertid for followers, rolling back: %v", err) + } + + // Add in key + _, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, remoteUserID, fullActor.PublicKey.PublicKeyPEM) + if err != nil { + if !app.db.isDuplicateKeyErr(err) { + t.Rollback() + log.Error("Couldn't add follower keys in DB: %v\n", err) + return -1, fmt.Errorf("couldn't add follower keys in DB: %v", err) + } else { + t.Rollback() + log.Error("Couldn't add follower keys in DB: %v\n", err) + return -1, fmt.Errorf("couldn't add follower keys in DB: %v", err) + } + } + + return remoteUserID, nil +} diff --git a/migrations/migrations.go b/migrations/migrations.go index fc638ee..6b5b094 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -71,6 +71,7 @@ var migrations = []Migration{ New("support newsletters", supportLetters), // V12 -> V13 New("support password resetting", supportPassReset), // V13 -> V14 New("speed up blog post retrieval", addPostRetrievalIndex), // V14 -> V15 + New("support ActivityPub likes", supportRemoteLikes), // V15 -> V16 (v0.16.0) } // CurrentVer returns the current migration version the application is on diff --git a/migrations/v16.go b/migrations/v16.go new file mode 100644 index 0000000..03ce78a --- /dev/null +++ b/migrations/v16.go @@ -0,0 +1,38 @@ +/* + * Copyright © 2024 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package migrations + +func supportRemoteLikes(db *datastore) error { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec(`CREATE TABLE remote_likes ( + post_id ` + db.typeChar(16) + ` NOT NULL, + remote_user_id ` + db.typeInt() + ` NOT NULL, + created ` + db.typeDateTime() + ` NOT NULL, + PRIMARY KEY (post_id,remote_user_id) +)`) + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + + return nil +} From 7f1cc6bf8f6fb74028bc5c4f9d6fa34789d4294b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:40:18 -0400 Subject: [PATCH 2/9] Support un-liking posts from the fediverse Ref T906 --- activitypub.go | 116 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/activitypub.go b/activitypub.go index 323bf90..267f929 100644 --- a/activitypub.go +++ b/activitypub.go @@ -357,8 +357,8 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request a := streams.NewAccept() p := c.PersonObject() var to *url.URL - var isFollow, isUnfollow, isLike bool - var likePostID string + var isFollow, isUnfollow, isLike, isUnlike bool + var likePostID, unlikePostID string fullActor := &activitystreams.Person{} var remoteUser *RemoteUser @@ -392,29 +392,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request }, 0) */ - // Get post ID from URL - var collAlias, slug string - if m := apCollectionPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 3 { - collAlias = m[1] - slug = m[2] - } else if m = apDraftPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 2 { - likePostID = m[1] - } else { - return fmt.Errorf("unable to match objectIRI: %s", obj) - } - - // Get postID if all we have is collection and slug - if collAlias != "" && slug != "" { - c, err := app.db.GetCollection(collAlias) - if err != nil { - return err - } - p, err := app.db.GetPost(slug, c.ID) - if err != nil { - return err - } - likePostID = p.ID - } + likePostID, err = parsePostIDFromURL(app, obj) // Finally, get actor information _, from := l.GetActor(0) @@ -465,8 +443,6 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request return impart.RenderActivityJSON(w, m, http.StatusOK) }, UndoCallback: func(u *streams.Undo) error { - isUnfollow = true - m["@context"] = []string{activitystreams.Namespace} b, _ := json.Marshal(m) if debugging { @@ -474,6 +450,31 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request } a.AppendObject(u.Raw()) + + // Check type -- we handle Undo:Like and Undo:Follow + _, err := u.ResolveObject(&streams.Resolver{ + LikeCallback: func(like *streams.Like) error { + isUnlike = true + + _, from := like.GetActor(0) + obj := like.Raw().GetObjectIRI(0) + unlikePostID, err = parsePostIDFromURL(app, obj) + fullActor, remoteUser, err = getActor(app, from.String()) + if err != nil { + return err + } + return nil + }, + // TODO: add FollowCallback for more robust handling + }, 0) + if err != nil { + return err + } + if isUnlike { + return nil + } + + isUnfollow = true _, to = u.GetActor(0) // TODO: get actor from object.object, not object obj := u.Raw().GetObjectIRI(0) @@ -546,6 +547,39 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request log.Info("Successfully liked post %s by remote user %s", likePostID, remoteUser.URL) } return impart.RenderActivityJSON(w, "", http.StatusOK) + } else if isUnlike { + t, err := app.db.Begin() + if err != nil { + log.Error("Unable to start transaction: %v", err) + return fmt.Errorf("unable to start transaction: %v", err) + } + + var remoteUserID int64 + if remoteUser != nil { + remoteUserID = remoteUser.ID + } else { + remoteUserID, err = apAddRemoteUser(app, t, fullActor) + } + + // Add follow + _, err = t.Exec("DELETE FROM remote_likes WHERE post_id = ? AND remote_user_id = ?", unlikePostID, remoteUserID) + if err != nil { + t.Rollback() + log.Error("Couldn't delete Like from DB: %v\n", err) + return fmt.Errorf("Couldn't delete Like from DB: %v", err) + } + + err = t.Commit() + if err != nil { + t.Rollback() + log.Error("Rolling back after Commit(): %v\n", err) + return fmt.Errorf("Rolling back after Commit(): %v\n", err) + } + + if debugging { + log.Info("Successfully un-liked post %s by remote user %s", unlikePostID, remoteUser.URL) + } + return impart.RenderActivityJSON(w, "", http.StatusOK) } go func() { @@ -1078,6 +1112,34 @@ func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error { return nil } +func parsePostIDFromURL(app *App, u *url.URL) (string, error) { + // Get post ID from URL + var collAlias, slug, postID string + if m := apCollectionPostIRIRegex.FindStringSubmatch(u.String()); len(m) == 3 { + collAlias = m[1] + slug = m[2] + } else if m = apDraftPostIRIRegex.FindStringSubmatch(u.String()); len(m) == 2 { + postID = m[1] + } else { + return "", fmt.Errorf("unable to match objectIRI: %s", u) + } + + // Get postID if all we have is collection and slug + if collAlias != "" && slug != "" { + c, err := app.db.GetCollection(collAlias) + if err != nil { + return "", err + } + p, err := app.db.GetPost(slug, c.ID) + if err != nil { + return "", err + } + postID = p.ID + } + + return postID, nil +} + func setCacheControl(w http.ResponseWriter, ttl time.Duration) { w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", ttl.Seconds())) } From 984e5bc4158fc623f38bf16b392d3fdb90e70da2 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:49:04 -0400 Subject: [PATCH 3/9] Show number of Likes on posts and Stats pages Ref T906 --- database.go | 19 +++++++++++++++++++ posts.go | 3 +++ templates/collection-post.tmpl | 1 + templates/user/stats.tmpl | 2 ++ 4 files changed, 25 insertions(+) diff --git a/database.go b/database.go index c5f239f..d715fd4 100644 --- a/database.go +++ b/database.go @@ -115,6 +115,7 @@ type writestore interface { DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error) ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error) + GetPostLikeCounts(postID string) (int64, error) GetPostsCount(c *CollectionObj, includeFuture bool) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) @@ -1174,6 +1175,12 @@ func (db *datastore) GetPost(id string, collectionID int64) (*PublicPost, error) return nil, ErrPostUnpublished } + // Get additional information needed before processing post data + p.LikeCount, err = db.GetPostLikeCounts(p.ID) + if err != nil { + return nil, err + } + res := p.processPost() if ownerName.Valid { res.Owner = &PublicUser{Username: ownerName.String} @@ -1236,6 +1243,18 @@ func (db *datastore) GetPostProperty(id string, collectionID int64, property str return res, nil } +func (db *datastore) GetPostLikeCounts(postID string) (int64, error) { + var count int64 + err := db.QueryRow("SELECT COUNT(*) FROM remote_likes WHERE post_id = ?", postID).Scan(&count) + switch { + case err == sql.ErrNoRows: + count = 0 + case err != nil: + return 0, err + } + return count, nil +} + // GetPostsCount modifies the CollectionObj to include the correct number of // standard (non-pinned) posts. It will return future posts if `includeFuture` // is true. diff --git a/posts.go b/posts.go index 19f01fd..1cfc1f0 100644 --- a/posts.go +++ b/posts.go @@ -105,6 +105,7 @@ type ( Created time.Time `db:"created" json:"created"` Updated time.Time `db:"updated" json:"updated"` ViewCount int64 `db:"view_count" json:"-"` + LikeCount int64 `db:"like_count" json:"likes"` Title zero.String `db:"title" json:"title"` HTMLTitle template.HTML `db:"title" json:"-"` Content string `db:"content" json:"body"` @@ -127,6 +128,7 @@ type ( IsTopLevel bool `json:"-"` DisplayDate string `json:"-"` Views int64 `json:"views"` + Likes int64 `json:"likes"` Owner *PublicUser `json:"-"` IsOwner bool `json:"-"` URL string `json:"url,omitempty"` @@ -1184,6 +1186,7 @@ func fetchPostProperty(app *App, w http.ResponseWriter, r *http.Request) error { func (p *Post) processPost() PublicPost { res := &PublicPost{Post: p, Views: 0} res.Views = p.ViewCount + res.Likes = p.LikeCount // TODO: move to own function loc := monday.FuzzyLocale(p.Language.String) res.DisplayDate = monday.Format(p.Created, monday.LongFormatsByLocale[loc], loc) diff --git a/templates/collection-post.tmpl b/templates/collection-post.tmpl index 54d5298..455dcfd 100644 --- a/templates/collection-post.tmpl +++ b/templates/collection-post.tmpl @@ -55,6 +55,7 @@ {{range .PinnedPosts}}{{.PlainDisplayTitle}}{{end}} {{end}} {{ if and .IsOwner .IsFound }}{{largeNumFmt .Views}} {{pluralize "view" "views" .Views}} + {{if .Likes}}{{largeNumFmt .Likes}} {{pluralize "like" "likes" .Likes}}{{end}} Edit {{if .IsPinned}}Unpin{{end}} {{ end }} diff --git a/templates/user/stats.tmpl b/templates/user/stats.tmpl index b7f3322..a0c08ec 100644 --- a/templates/user/stats.tmpl +++ b/templates/user/stats.tmpl @@ -51,11 +51,13 @@ td.none { Post {{if not .Collection}}Blog{{end}} Total Views + {{if .Federation}}Likes{{end}} {{range .TopPosts}} {{if ne .DisplayTitle ""}}{{.DisplayTitle}}{{else}}{{.ID}}{{end}} {{ if not $.Collection }}{{if .Collection}}{{.Collection.Title}}{{else}}Draft{{end}}{{ end }} {{.ViewCount}} + {{if $.Federation}}{{.LikeCount}}{{end}} {{end}} From 74a0947fdbae5dd52968d867aef5a00b29419809 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:58:23 -0400 Subject: [PATCH 4/9] Fix whitespace in collection-post.tmpl --- templates/collection-post.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/collection-post.tmpl b/templates/collection-post.tmpl index 455dcfd..280ab0e 100644 --- a/templates/collection-post.tmpl +++ b/templates/collection-post.tmpl @@ -4,7 +4,7 @@ {{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}} - + {{if .CustomCSS}}{{end}} @@ -45,7 +45,7 @@ - +
@@ -61,7 +61,7 @@ {{ end }}
- + {{if .Silenced}} {{template "user-silenced"}} {{end}} @@ -71,7 +71,7 @@

{{ end }} - + {{if .Collection.CanShowScript}} {{range .Collection.ExternalScripts}}{{end}} {{if .Collection.Script}}{{end}} From 9c0a2f8b13d4e2f69dea53174c84479e9499b781 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 12:18:21 -0400 Subject: [PATCH 5/9] Fix post ID extraction regex Ref T906 --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 267f929..6b3b8ee 100644 --- a/activitypub.go +++ b/activitypub.go @@ -48,7 +48,7 @@ const ( var ( apCollectionPostIRIRegex = regexp.MustCompile("/api/collections/([a-z0-9\\-]+)/posts/([a-z0-9\\-]+)$") - apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-]{12,16})$") + apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-])$") ) var instanceColl *Collection From 1b20d3704f96002db8b3aa47d2556bda121f7891 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 12:23:39 -0400 Subject: [PATCH 6/9] Catch errors around Like/Unlike actions Previously, we'd get nil panics or insert blank post IDs Ref T906 --- activitypub.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/activitypub.go b/activitypub.go index 6b3b8ee..33ec2fe 100644 --- a/activitypub.go +++ b/activitypub.go @@ -392,7 +392,13 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request }, 0) */ + if obj == nil { + return fmt.Errorf("didn't get ObjectIRI to Like") + } likePostID, err = parsePostIDFromURL(app, obj) + if err != nil { + return err + } // Finally, get actor information _, from := l.GetActor(0) @@ -458,7 +464,13 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request _, from := like.GetActor(0) obj := like.Raw().GetObjectIRI(0) + if obj == nil { + return fmt.Errorf("didn't get ObjectIRI for Undo Like") + } unlikePostID, err = parsePostIDFromURL(app, obj) + if err != nil { + return err + } fullActor, remoteUser, err = getActor(app, from.String()) if err != nil { return err From 8193a410825e9437ae900b096ac1518330047542 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 12:37:37 -0400 Subject: [PATCH 7/9] Fix post ID extraction regex, actually Ref T906 --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 33ec2fe..1f7ec63 100644 --- a/activitypub.go +++ b/activitypub.go @@ -48,7 +48,7 @@ const ( var ( apCollectionPostIRIRegex = regexp.MustCompile("/api/collections/([a-z0-9\\-]+)/posts/([a-z0-9\\-]+)$") - apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-])$") + apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-]+)$") ) var instanceColl *Collection From 6b4179fa01d94cf43f61b3d3a1f0290d39f0133d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 1 Dec 2024 18:15:58 -0500 Subject: [PATCH 8/9] Fix INSERT remote_likes query for SQLite --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 1f7ec63..960b863 100644 --- a/activitypub.go +++ b/activitypub.go @@ -535,7 +535,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request } // Add like - _, err = t.Exec("INSERT INTO remote_likes (post_id, remote_user_id, created) VALUES (?, ?, NOW())", likePostID, remoteUserID) + _, err = t.Exec("INSERT INTO remote_likes (post_id, remote_user_id, created) VALUES (?, ?, "+app.db.now()+")", likePostID, remoteUserID) if err != nil { if !app.db.isDuplicateKeyErr(err) { t.Rollback() From eca7bcda0a8ed5067c9474b1d70a1691fc640c31 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 1 Dec 2024 18:30:32 -0500 Subject: [PATCH 9/9] Fix comment in activitypub.go --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 960b863..2bbc7ad 100644 --- a/activitypub.go +++ b/activitypub.go @@ -573,7 +573,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request remoteUserID, err = apAddRemoteUser(app, t, fullActor) } - // Add follow + // Remove like _, err = t.Exec("DELETE FROM remote_likes WHERE post_id = ? AND remote_user_id = ?", unlikePostID, remoteUserID) if err != nil { t.Rollback()