Implement commenting and fix lint errors

forgejo-federation
Anthony Wang 2022-11-27 04:18:39 +00:00
parent 3f5f626264
commit 1066cfe785
No known key found for this signature in database
GPG Key ID: 42A5B952E6DD8D38
13 changed files with 158 additions and 91 deletions

View File

@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -2471,3 +2472,11 @@ func DeleteOrphanedIssues() error {
} }
return nil return nil
} }
func (issue *Issue) GetIRI() string {
_ = issue.LoadRepo(db.DefaultContext)
if strings.Contains(issue.Repo.OwnerName, "@") {
return issue.OriginalAuthor
}
return setting.AppURL + "api/v1/activitypub/ticket/" + issue.Repo.OwnerName + "/" + issue.Repo.Name + "/" + strconv.FormatInt(issue.Index, 10)
}

View File

@ -802,3 +802,10 @@ func FixNullArchivedRepository() (int64, error) {
IsArchived: false, IsArchived: false,
}) })
} }
func (r *Repository) GetIRI() string {
if strings.Contains(r.OwnerName, "@") {
return r.OriginalURL
}
return setting.AppURL + "api/v1/activitypub/repo/" + r.OwnerName + "/" + r.Name
}

View File

@ -1339,3 +1339,10 @@ func GetOrderByName() string {
} }
return "name" return "name"
} }
func (u *User) GetIRI() string {
if u.LoginType == auth.Federated {
return u.LoginName
}
return setting.AppURL + "api/v1/activitypub/user/" + u.Name
}

View File

@ -62,35 +62,37 @@ func NotEmpty(i ap.Item) bool {
if ap.IsNil(i) { if ap.IsNil(i) {
return false return false
} }
var notEmpty bool
switch i.GetType() { switch i.GetType() {
case CommitType: case CommitType:
OnCommit(i, func(c *Commit) error { c, err := ToCommit(i)
notEmpty = ap.NotEmpty(c.Object) if err != nil {
return nil return false
}) }
return ap.NotEmpty(c.Object)
case BranchType: case BranchType:
OnBranch(i, func(b *Branch) error { b, err := ToBranch(i)
notEmpty = ap.NotEmpty(b.Object) if err != nil {
return nil return false
}) }
return ap.NotEmpty(b.Object)
case RepositoryType: case RepositoryType:
OnRepository(i, func(r *Repository) error { r, err := ToRepository(i)
notEmpty = ap.NotEmpty(r.Actor) if err != nil {
return nil return false
}) }
return ap.NotEmpty(r.Actor)
case PushType: case PushType:
OnPush(i, func(p *Push) error { p, err := ToPush(i)
notEmpty = ap.NotEmpty(p.Object) if err != nil {
return nil return false
}) }
return ap.NotEmpty(p.Object)
case TicketType: case TicketType:
OnTicket(i, func(t *Ticket) error { t, err := ToTicket(i)
notEmpty = ap.NotEmpty(t.Object) if err != nil {
return nil return false
}) }
default: return ap.NotEmpty(t.Object)
notEmpty = ap.NotEmpty(i)
} }
return notEmpty return ap.NotEmpty(i)
} }

View File

@ -42,7 +42,12 @@ func AuthorizeInteraction(ctx *context.Context) {
switch object.GetType() { switch object.GetType() {
case ap.PersonType: case ap.PersonType:
// Federated user // Federated user
err = createPerson(ctx, object.(*ap.Person)) person, err := ap.ToActor(object)
if err != nil {
ctx.ServerError("ToActor", err)
return
}
err = createPerson(ctx, person)
if err != nil { if err != nil {
ctx.ServerError("FederatedUserNew", err) ctx.ServerError("FederatedUserNew", err)
return return

View File

@ -67,7 +67,10 @@ func createPerson(ctx context.Context, person *ap.Person) error {
} }
if person.Icon != nil { if person.Icon != nil {
icon := person.Icon.(*ap.Image) icon, err := ap.ToObject(person.Icon)
if err != nil {
return err
}
iconURL, err := icon.URL.GetLink().URL() iconURL, err := icon.URL.GetLink().URL()
if err != nil { if err != nil {
return err return err
@ -112,7 +115,11 @@ func createPersonFromIRI(ctx context.Context, personIRI ap.IRI) error {
} }
// Create federated user // Create federated user
return createPerson(ctx, object.(*ap.Person)) person, err := ap.ToActor(object)
if err != nil {
return err
}
return createPerson(ctx, person)
} }
// Create a new federated repo from a Repository object // Create a new federated repo from a Repository object
@ -133,7 +140,8 @@ func createRepository(ctx context.Context, repository *forgefed.Repository) erro
} }
repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{ repo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{
Name: repository.Name.String(), Name: repository.Name.String(),
OriginalURL: repository.GetLink().String(),
}) })
if err != nil { if err != nil {
return err return err
@ -281,7 +289,7 @@ func createComment(ctx context.Context, note *ap.Note) error {
return err return err
} }
actorUser, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink()) user, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink())
if err != nil { if err != nil {
return err return err
} }
@ -299,7 +307,7 @@ func createComment(ctx context.Context, note *ap.Note) error {
return err return err
} }
_, err = issues_model.CreateCommentCtx(ctx, &issues_model.CreateCommentOptions{ _, err = issues_model.CreateCommentCtx(ctx, &issues_model.CreateCommentOptions{
Doer: actorUser, Doer: user,
Repo: repo, Repo: repo,
Issue: issue, Issue: issue,
Content: note.Content.String(), Content: note.Content.String(),

View File

@ -42,8 +42,8 @@ func Person(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name iri := ctx.ContextUser.GetIRI()
person := ap.PersonNew(ap.IRI(link)) person := ap.PersonNew(ap.IRI(iri))
person.Name = ap.NaturalLanguageValuesNew() person.Name = ap.NaturalLanguageValuesNew()
err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName)) err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName))
@ -68,14 +68,14 @@ func Person(ctx *context.APIContext) {
URL: ap.IRI(ctx.ContextUser.AvatarFullLinkWithSize(2048)), URL: ap.IRI(ctx.ContextUser.AvatarFullLinkWithSize(2048)),
} }
person.Inbox = ap.IRI(link + "/inbox") person.Inbox = ap.IRI(iri + "/inbox")
person.Outbox = ap.IRI(link + "/outbox") person.Outbox = ap.IRI(iri + "/outbox")
person.Following = ap.IRI(link + "/following") person.Following = ap.IRI(iri + "/following")
person.Followers = ap.IRI(link + "/followers") person.Followers = ap.IRI(iri + "/followers")
person.Liked = ap.IRI(link + "/liked") person.Liked = ap.IRI(iri + "/liked")
person.PublicKey.ID = ap.IRI(link + "#main-key") person.PublicKey.ID = ap.IRI(iri + "#main-key")
person.PublicKey.Owner = ap.IRI(link) person.PublicKey.Owner = ap.IRI(iri)
publicKeyPem, err := activitypub.GetPublicKey(ctx.ContextUser) publicKeyPem, err := activitypub.GetPublicKey(ctx.ContextUser)
if err != nil { if err != nil {
ctx.ServerError("GetPublicKey", err) ctx.ServerError("GetPublicKey", err)
@ -164,13 +164,13 @@ func PersonOutbox(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name iri := ctx.ContextUser.GetIRI()
orderedCollection := ap.OrderedCollectionNew(ap.IRI(link + "/outbox")) orderedCollection := ap.OrderedCollectionNew(ap.IRI(iri + "/outbox"))
orderedCollection.First = ap.IRI(link + "/outbox?page=1") orderedCollection.First = ap.IRI(iri + "/outbox?page=1")
outbox := ap.OrderedCollectionPageNew(orderedCollection) outbox := ap.OrderedCollectionPageNew(orderedCollection)
outbox.First = ap.IRI(link + "/outbox?page=1") outbox.First = ap.IRI(iri + "/outbox?page=1")
feed, err := activities.GetFeeds(ctx, activities.GetFeedsOptions{ feed, err := activities.GetFeeds(ctx, activities.GetFeedsOptions{
RequestedUser: ctx.ContextUser, RequestedUser: ctx.ContextUser,
@ -183,12 +183,12 @@ func PersonOutbox(ctx *context.APIContext) {
// Only specify next if this amount of feed corresponds to the calculated limit. // Only specify next if this amount of feed corresponds to the calculated limit.
if len(feed) == convert.ToCorrectPageSize(ctx.FormInt("limit")) { if len(feed) == convert.ToCorrectPageSize(ctx.FormInt("limit")) {
outbox.Next = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", link, ctx.FormInt("page")+1)) outbox.Next = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", iri, ctx.FormInt("page")+1))
} }
// Only specify previous page when there is one. // Only specify previous page when there is one.
if ctx.FormInt("page") > 1 { if ctx.FormInt("page") > 1 {
outbox.Prev = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", link, ctx.FormInt("page")-1)) outbox.Prev = ap.IRI(fmt.Sprintf("%s/outbox?page=%d", iri, ctx.FormInt("page")-1))
} }
if err != nil { if err != nil {
@ -249,7 +249,7 @@ func PersonFollowing(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name iri := ctx.ContextUser.GetIRI()
users, _, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx)) users, _, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx))
if err != nil { if err != nil {
@ -257,7 +257,7 @@ func PersonFollowing(ctx *context.APIContext) {
return return
} }
following := ap.OrderedCollectionNew(ap.IRI(link + "/following")) following := ap.OrderedCollectionNew(ap.IRI(iri + "/following"))
following.TotalItems = uint(len(users)) following.TotalItems = uint(len(users))
for _, user := range users { for _, user := range users {
@ -290,7 +290,7 @@ func PersonFollowers(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name iri := ctx.ContextUser.GetIRI()
users, _, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx)) users, _, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx))
if err != nil { if err != nil {
@ -298,7 +298,7 @@ func PersonFollowers(ctx *context.APIContext) {
return return
} }
followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers")) followers := ap.OrderedCollectionNew(ap.IRI(iri + "/followers"))
followers.TotalItems = uint(len(users)) followers.TotalItems = uint(len(users))
for _, user := range users { for _, user := range users {
@ -331,7 +331,7 @@ func PersonLiked(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name iri := ctx.ContextUser.GetIRI()
repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{ repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
@ -343,7 +343,7 @@ func PersonLiked(ctx *context.APIContext) {
return return
} }
liked := ap.OrderedCollectionNew(ap.IRI(link + "/liked")) liked := ap.OrderedCollectionNew(ap.IRI(iri + "/liked"))
liked.TotalItems = uint(count) liked.TotalItems = uint(count)
for _, repo := range repos { for _, repo := range repos {

View File

@ -39,8 +39,8 @@ func Repo(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name iri := ctx.Repo.Repository.GetIRI()
repo := forgefed.RepositoryNew(ap.IRI(link)) repo := forgefed.RepositoryNew(ap.IRI(iri))
repo.Name = ap.NaturalLanguageValuesNew() repo.Name = ap.NaturalLanguageValuesNew()
err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name)) err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name))
@ -58,10 +58,10 @@ func Repo(ctx *context.APIContext) {
return return
} }
repo.Inbox = ap.IRI(link + "/inbox") repo.Inbox = ap.IRI(iri + "/inbox")
repo.Outbox = ap.IRI(link + "/outbox") repo.Outbox = ap.IRI(iri + "/outbox")
repo.Followers = ap.IRI(link + "/followers") repo.Followers = ap.IRI(iri + "/followers")
repo.Team = ap.IRI(link + "/team") repo.Team = ap.IRI(iri + "/team")
response(ctx, repo) response(ctx, repo)
} }

View File

@ -43,12 +43,6 @@ func Ticket(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/ticket/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name + "/" + ctx.Params("id")
ticket := forgefed.TicketNew()
ticket.Type = forgefed.TicketType
ticket.ID = ap.IRI(link)
repo, err := repo_model.GetRepositoryByOwnerAndNameCtx(ctx, ctx.ContextUser.Name, ctx.Repo.Repository.Name) repo, err := repo_model.GetRepositoryByOwnerAndNameCtx(ctx, ctx.ContextUser.Name, ctx.Repo.Repository.Name)
if err != nil { if err != nil {
ctx.ServerError("GetRepositoryByOwnerAndNameCtx", err) ctx.ServerError("GetRepositoryByOwnerAndNameCtx", err)
@ -64,6 +58,12 @@ func Ticket(ctx *context.APIContext) {
ctx.ServerError("GetIssueByIndex", err) ctx.ServerError("GetIssueByIndex", err)
return return
} }
iri := issue.GetIRI()
// TODO: move this to services/activitypub/objects.go
ticket := forgefed.TicketNew()
ticket.Type = forgefed.TicketType
ticket.ID = ap.IRI(iri)
// Setting a NaturalLanguageValue to a number causes go-ap's JSON parsing to do weird things // Setting a NaturalLanguageValue to a number causes go-ap's JSON parsing to do weird things
// Workaround: set it to #1 instead of 1 // Workaround: set it to #1 instead of 1

View File

@ -12,42 +12,22 @@ import (
) )
// Create and send Follow activity // Create and send Follow activity
func Follow(userID, followID int64) error { func Follow(actorUser, followUser *user_model.User) *ap.Follow {
followUser, err := user_model.GetUserByID(followID)
if err != nil {
return err
}
actorUser, err := user_model.GetUserByID(userID)
if err != nil {
return err
}
object := ap.PersonNew(ap.IRI(followUser.LoginName)) object := ap.PersonNew(ap.IRI(followUser.LoginName))
follow := ap.FollowNew("", object) follow := ap.FollowNew("", object)
follow.Type = ap.FollowType follow.Type = ap.FollowType
follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name)) follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name))
follow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))} follow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))}
return Send(actorUser, follow) return follow
} }
// Create and send Undo Follow activity // Create and send Undo Follow activity
func Unfollow(userID, followID int64) error { func Unfollow(actorUser, followUser *user_model.User) *ap.Undo {
followUser, err := user_model.GetUserByID(followID)
if err != nil {
return err
}
actorUser, err := user_model.GetUserByID(userID)
if err != nil {
return err
}
object := ap.PersonNew(ap.IRI(followUser.LoginName)) object := ap.PersonNew(ap.IRI(followUser.LoginName))
follow := ap.FollowNew("", object) follow := ap.FollowNew("", object)
follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name)) follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name))
unfollow := ap.UndoNew("", follow) unfollow := ap.UndoNew("", follow)
unfollow.Type = ap.UndoType unfollow.Type = ap.UndoType
unfollow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))} unfollow.To = ap.ItemCollection{ap.Item(ap.IRI(followUser.LoginName + "/inbox"))}
return Send(actorUser, unfollow) return unfollow
} }

View File

@ -0,0 +1,22 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package activitypub
import (
issues_model "code.gitea.io/gitea/models/issues"
ap "github.com/go-ap/activitypub"
)
func Note(comment *issues_model.Comment) ap.Note {
note := ap.Note{
Type: ap.NoteType,
AttributedTo: ap.IRI(comment.Poster.GetIRI()),
Context: ap.IRI(comment.Issue.GetIRI()),
}
note.Content = ap.NaturalLanguageValuesNew()
_ = note.Content.Set("en", ap.Content(comment.Content))
return note
}

View File

@ -5,12 +5,17 @@
package comments package comments
import ( import (
"strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
) )
// CreateIssueComment creates a plain issue comment. // CreateIssueComment creates a plain issue comment.
@ -27,6 +32,20 @@ func CreateIssueComment(doer *user_model.User, repo *repo_model.Repository, issu
return nil, err return nil, err
} }
if strings.Contains(repo.Owner.Name, "@") {
// Federated comment
// Refactor this to its own function in services/activitypub
create := ap.Create{
Type: ap.CreateType,
Object: activitypub.Note(comment),
To: ap.ItemCollection{ap.Item(ap.IRI(repo.OriginalURL + "/inbox"))},
}
err = activitypub.Send(doer, &create)
if err != nil {
return nil, err
}
}
mentions, err := issues_model.FindAndUpdateIssueMentions(db.DefaultContext, issue, doer, comment.Content) mentions, err := issues_model.FindAndUpdateIssueMentions(db.DefaultContext, issue, doer, comment.Content)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -295,9 +295,13 @@ func FollowUser(userID, followID int64) (err error) {
} }
if followUser.LoginType == auth.Federated { if followUser.LoginType == auth.Federated {
// Following remote user // Following remote user
err = activitypub.Follow(userID, followID) actorUser, err := user_model.GetUserByID(userID)
if err != nil { if err != nil {
return return err
}
err = activitypub.Send(actorUser, activitypub.Follow(actorUser, followUser))
if err != nil {
return err
} }
} }
@ -316,9 +320,13 @@ func UnfollowUser(userID, followID int64) (err error) {
} }
if followUser.LoginType == auth.Federated { if followUser.LoginType == auth.Federated {
// Unfollowing remote user // Unfollowing remote user
err = activitypub.Unfollow(userID, followID) actorUser, err := user_model.GetUserByID(userID)
if err != nil { if err != nil {
return return err
}
err = activitypub.Send(actorUser, activitypub.Unfollow(actorUser, followUser))
if err != nil {
return err
} }
} }