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/log"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@ -2471,3 +2472,11 @@ func DeleteOrphanedIssues() error {
}
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,
})
}
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"
}
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) {
return false
}
var notEmpty bool
switch i.GetType() {
case CommitType:
OnCommit(i, func(c *Commit) error {
notEmpty = ap.NotEmpty(c.Object)
return nil
})
c, err := ToCommit(i)
if err != nil {
return false
}
return ap.NotEmpty(c.Object)
case BranchType:
OnBranch(i, func(b *Branch) error {
notEmpty = ap.NotEmpty(b.Object)
return nil
})
b, err := ToBranch(i)
if err != nil {
return false
}
return ap.NotEmpty(b.Object)
case RepositoryType:
OnRepository(i, func(r *Repository) error {
notEmpty = ap.NotEmpty(r.Actor)
return nil
})
r, err := ToRepository(i)
if err != nil {
return false
}
return ap.NotEmpty(r.Actor)
case PushType:
OnPush(i, func(p *Push) error {
notEmpty = ap.NotEmpty(p.Object)
return nil
})
p, err := ToPush(i)
if err != nil {
return false
}
return ap.NotEmpty(p.Object)
case TicketType:
OnTicket(i, func(t *Ticket) error {
notEmpty = ap.NotEmpty(t.Object)
return nil
})
default:
notEmpty = ap.NotEmpty(i)
t, err := ToTicket(i)
if err != nil {
return false
}
return ap.NotEmpty(t.Object)
}
return notEmpty
return ap.NotEmpty(i)
}

View File

@ -42,7 +42,12 @@ func AuthorizeInteraction(ctx *context.Context) {
switch object.GetType() {
case ap.PersonType:
// 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 {
ctx.ServerError("FederatedUserNew", err)
return

View File

@ -67,7 +67,10 @@ func createPerson(ctx context.Context, person *ap.Person) error {
}
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()
if err != nil {
return err
@ -112,7 +115,11 @@ func createPersonFromIRI(ctx context.Context, personIRI ap.IRI) error {
}
// 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
@ -133,7 +140,8 @@ func createRepository(ctx context.Context, repository *forgefed.Repository) erro
}
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 {
return err
@ -281,7 +289,7 @@ func createComment(ctx context.Context, note *ap.Note) error {
return err
}
actorUser, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink())
user, err := activitypub.PersonIRIToUser(ctx, note.AttributedTo.GetLink())
if err != nil {
return err
}
@ -299,7 +307,7 @@ func createComment(ctx context.Context, note *ap.Note) error {
return err
}
_, err = issues_model.CreateCommentCtx(ctx, &issues_model.CreateCommentOptions{
Doer: actorUser,
Doer: user,
Repo: repo,
Issue: issue,
Content: note.Content.String(),

View File

@ -42,8 +42,8 @@ func Person(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
person := ap.PersonNew(ap.IRI(link))
iri := ctx.ContextUser.GetIRI()
person := ap.PersonNew(ap.IRI(iri))
person.Name = ap.NaturalLanguageValuesNew()
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)),
}
person.Inbox = ap.IRI(link + "/inbox")
person.Outbox = ap.IRI(link + "/outbox")
person.Following = ap.IRI(link + "/following")
person.Followers = ap.IRI(link + "/followers")
person.Liked = ap.IRI(link + "/liked")
person.Inbox = ap.IRI(iri + "/inbox")
person.Outbox = ap.IRI(iri + "/outbox")
person.Following = ap.IRI(iri + "/following")
person.Followers = ap.IRI(iri + "/followers")
person.Liked = ap.IRI(iri + "/liked")
person.PublicKey.ID = ap.IRI(link + "#main-key")
person.PublicKey.Owner = ap.IRI(link)
person.PublicKey.ID = ap.IRI(iri + "#main-key")
person.PublicKey.Owner = ap.IRI(iri)
publicKeyPem, err := activitypub.GetPublicKey(ctx.ContextUser)
if err != nil {
ctx.ServerError("GetPublicKey", err)
@ -164,13 +164,13 @@ func PersonOutbox(ctx *context.APIContext) {
// "200":
// "$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.First = ap.IRI(link + "/outbox?page=1")
orderedCollection := ap.OrderedCollectionNew(ap.IRI(iri + "/outbox"))
orderedCollection.First = ap.IRI(iri + "/outbox?page=1")
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{
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.
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.
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 {
@ -249,7 +249,7 @@ func PersonFollowing(ctx *context.APIContext) {
// "200":
// "$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))
if err != nil {
@ -257,7 +257,7 @@ func PersonFollowing(ctx *context.APIContext) {
return
}
following := ap.OrderedCollectionNew(ap.IRI(link + "/following"))
following := ap.OrderedCollectionNew(ap.IRI(iri + "/following"))
following.TotalItems = uint(len(users))
for _, user := range users {
@ -290,7 +290,7 @@ func PersonFollowers(ctx *context.APIContext) {
// "200":
// "$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))
if err != nil {
@ -298,7 +298,7 @@ func PersonFollowers(ctx *context.APIContext) {
return
}
followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers"))
followers := ap.OrderedCollectionNew(ap.IRI(iri + "/followers"))
followers.TotalItems = uint(len(users))
for _, user := range users {
@ -331,7 +331,7 @@ func PersonLiked(ctx *context.APIContext) {
// "200":
// "$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{
Actor: ctx.Doer,
@ -343,7 +343,7 @@ func PersonLiked(ctx *context.APIContext) {
return
}
liked := ap.OrderedCollectionNew(ap.IRI(link + "/liked"))
liked := ap.OrderedCollectionNew(ap.IRI(iri + "/liked"))
liked.TotalItems = uint(count)
for _, repo := range repos {

View File

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

View File

@ -43,12 +43,6 @@ func Ticket(ctx *context.APIContext) {
// "200":
// "$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)
if err != nil {
ctx.ServerError("GetRepositoryByOwnerAndNameCtx", err)
@ -64,6 +58,12 @@ func Ticket(ctx *context.APIContext) {
ctx.ServerError("GetIssueByIndex", err)
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
// Workaround: set it to #1 instead of 1

View File

@ -12,42 +12,22 @@ import (
)
// Create and send Follow activity
func Follow(userID, followID int64) error {
followUser, err := user_model.GetUserByID(followID)
if err != nil {
return err
}
actorUser, err := user_model.GetUserByID(userID)
if err != nil {
return err
}
func Follow(actorUser, followUser *user_model.User) *ap.Follow {
object := ap.PersonNew(ap.IRI(followUser.LoginName))
follow := ap.FollowNew("", object)
follow.Type = ap.FollowType
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"))}
return Send(actorUser, follow)
return follow
}
// Create and send Undo Follow activity
func Unfollow(userID, followID int64) error {
followUser, err := user_model.GetUserByID(followID)
if err != nil {
return err
}
actorUser, err := user_model.GetUserByID(userID)
if err != nil {
return err
}
func Unfollow(actorUser, followUser *user_model.User) *ap.Undo {
object := ap.PersonNew(ap.IRI(followUser.LoginName))
follow := ap.FollowNew("", object)
follow.Actor = ap.PersonNew(ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + actorUser.Name))
unfollow := ap.UndoNew("", follow)
unfollow.Type = ap.UndoType
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
import (
"strings"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
)
// CreateIssueComment creates a plain issue comment.
@ -27,6 +32,20 @@ func CreateIssueComment(doer *user_model.User, repo *repo_model.Repository, issu
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)
if err != nil {
return nil, err

View File

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