Federated issue creation

forgejo-federation
Anthony Wang 2022-11-27 19:09:10 +00:00
parent 77896f1a50
commit 3e690fbae2
No known key found for this signature in database
GPG Key ID: 42A5B952E6DD8D38
6 changed files with 113 additions and 81 deletions

View File

@ -41,6 +41,7 @@ func Repo(ctx *context.APIContext) {
iri := ctx.Repo.Repository.GetIRI() iri := ctx.Repo.Repository.GetIRI()
repo := forgefed.RepositoryNew(ap.IRI(iri)) repo := forgefed.RepositoryNew(ap.IRI(iri))
repo.Type = forgefed.RepositoryType
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))
@ -123,24 +124,27 @@ func RepoInbox(ctx *context.APIContext) {
// Process activity // Process activity
switch activity.Type { switch activity.Type {
case ap.CreateType: case ap.CreateType:
err = ap.OnObject(activity.Object, func(o *ap.Object) error { switch activity.Object.GetType() {
switch o.Type { case forgefed.RepositoryType:
case forgefed.RepositoryType: // Fork created by remote instance
// Fork created by remote instance err = forgefed.OnRepository(activity.Object, func(r *forgefed.Repository) error {
return forgefed.OnRepository(o, func(r *forgefed.Repository) error { return createRepository(ctx, r)
return createRepository(ctx, r) })
}) case forgefed.TicketType:
case forgefed.TicketType: // New issue or pull request
// New issue or pull request err = forgefed.OnTicket(activity.Object, func(t *forgefed.Ticket) error {
return forgefed.OnTicket(o, func(t *forgefed.Ticket) error { return createTicket(ctx, t)
return createTicket(ctx, t) })
}) case ap.NoteType:
case ap.NoteType: // New comment
// New comment err = ap.On(activity.Object, func(n *ap.Note) error {
return createComment(ctx, o) return createComment(ctx, n)
} })
return nil default:
}) log.Info("Incoming unsupported ActivityStreams object type: %s", activity.Object.GetType())
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams object type not supported")
return
}
case ap.LikeType: case ap.LikeType:
err = star(ctx, activity) err = star(ctx, activity)
default: default:
@ -149,7 +153,7 @@ func RepoInbox(ctx *context.APIContext) {
return return
} }
if err != nil { if err != nil {
ctx.ServerError("Error when processing: %s", err) ctx.ServerError("Error when processing", err)
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)

View File

@ -8,12 +8,8 @@ import (
"strconv" "strconv"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/forgefed" "code.gitea.io/gitea/services/activitypub"
"code.gitea.io/gitea/modules/setting"
ap "github.com/go-ap/activitypub"
) )
// Ticket function returns the Ticket object for an issue or PR // Ticket function returns the Ticket object for an issue or PR
@ -43,63 +39,20 @@ func Ticket(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/ActivityPub" // "$ref": "#/responses/ActivityPub"
repo, err := repo_model.GetRepositoryByOwnerAndNameCtx(ctx, ctx.ContextUser.Name, ctx.Repo.Repository.Name)
if err != nil {
ctx.ServerError("GetRepositoryByOwnerAndNameCtx", err)
return
}
index, err := strconv.ParseInt(ctx.Params("id"), 10, 64) index, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
if err != nil { if err != nil {
ctx.ServerError("ParseInt", err) ctx.ServerError("ParseInt", err)
return return
} }
issue, err := issues_model.GetIssueByIndex(repo.ID, index) issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, index)
if err != nil { if err != nil {
ctx.ServerError("GetIssueByIndex", err) ctx.ServerError("GetIssueByIndex", err)
return return
} }
iri := issue.GetIRI() ticket, err := activitypub.Ticket(issue)
// 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
ticket.Name = ap.NaturalLanguageValuesNew()
err = ticket.Name.Set("en", ap.Content("#"+ctx.Params("id")))
if err != nil { if err != nil {
ctx.ServerError("Set Name", err) ctx.ServerError("Ticket", err)
return return
} }
ticket.Context = ap.IRI(setting.AppURL + "api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name)
err = issue.LoadPoster()
if err != nil {
ctx.ServerError("LoadPoster", err)
return
}
ticket.AttributedTo = ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + issue.Poster.Name)
ticket.Summary = ap.NaturalLanguageValuesNew()
err = ticket.Summary.Set("en", ap.Content(issue.Title))
if err != nil {
ctx.ServerError("Set Summary", err)
return
}
ticket.Content = ap.NaturalLanguageValuesNew()
err = ticket.Content.Set("en", ap.Content(issue.Content))
if err != nil {
ctx.ServerError("Set Content", err)
return
}
if issue.IsClosed {
ticket.IsResolved = true
}
response(ctx, ticket) response(ctx, ticket)
} }

View File

@ -0,0 +1,17 @@
// 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 (
ap "github.com/go-ap/activitypub"
)
func Create(to string, object ap.ObjectOrLink) *ap.Create {
return &ap.Create{
Type: ap.CreateType,
Object: object,
To: ap.ItemCollection{ap.Item(ap.IRI(to))},
}
}

View File

@ -5,12 +5,17 @@
package activitypub package activitypub
import ( import (
"strconv"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/forgefed"
ap "github.com/go-ap/activitypub" ap "github.com/go-ap/activitypub"
) )
func Note(comment *issues_model.Comment) ap.Note { // Construct a Note object from a comment
func Note(comment *issues_model.Comment) *ap.Note {
note := ap.Note{ note := ap.Note{
Type: ap.NoteType, Type: ap.NoteType,
AttributedTo: ap.IRI(comment.Poster.GetIRI()), AttributedTo: ap.IRI(comment.Poster.GetIRI()),
@ -18,5 +23,50 @@ func Note(comment *issues_model.Comment) ap.Note {
} }
note.Content = ap.NaturalLanguageValuesNew() note.Content = ap.NaturalLanguageValuesNew()
_ = note.Content.Set("en", ap.Content(comment.Content)) _ = note.Content.Set("en", ap.Content(comment.Content))
return note return &note
}
// Construct a Ticket object from an issue
func Ticket(issue *issues_model.Issue) (*forgefed.Ticket, error) {
iri := issue.GetIRI()
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
ticket.Name = ap.NaturalLanguageValuesNew()
err := ticket.Name.Set("en", ap.Content("#"+strconv.FormatInt(issue.Index, 10)))
if err != nil {
return nil, err
}
err = issue.LoadRepo(db.DefaultContext)
if err != nil {
return nil, err
}
ticket.Context = ap.IRI(issue.Repo.GetIRI())
err = issue.LoadPoster()
if err != nil {
return nil, err
}
ticket.AttributedTo = ap.IRI(issue.Poster.GetIRI())
ticket.Summary = ap.NaturalLanguageValuesNew()
err = ticket.Summary.Set("en", ap.Content(issue.Title))
if err != nil {
return nil, err
}
ticket.Content = ap.NaturalLanguageValuesNew()
err = ticket.Content.Set("en", ap.Content(issue.Content))
if err != nil {
return nil, err
}
if issue.IsClosed {
ticket.IsResolved = true
}
return ticket, nil
} }

View File

@ -14,8 +14,6 @@ import (
"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" "code.gitea.io/gitea/services/activitypub"
ap "github.com/go-ap/activitypub"
) )
// CreateIssueComment creates a plain issue comment. // CreateIssueComment creates a plain issue comment.
@ -34,13 +32,8 @@ func CreateIssueComment(doer *user_model.User, repo *repo_model.Repository, issu
if strings.Contains(repo.OwnerName, "@") { if strings.Contains(repo.OwnerName, "@") {
// Federated comment // Federated comment
// Refactor this to its own function in services/activitypub create := activitypub.Create(repo.OriginalURL + "/inbox", activitypub.Note(comment))
create := ap.Create{ err = activitypub.Send(doer, 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,6 +6,7 @@ package issue
import ( import (
"fmt" "fmt"
"strings"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -19,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/activitypub"
) )
// NewIssue creates new issue with labels for repository. // NewIssue creates new issue with labels for repository.
@ -27,6 +29,19 @@ func NewIssue(repo *repo_model.Repository, issue *issues_model.Issue, labelIDs [
return err return err
} }
if strings.Contains(repo.OwnerName, "@") {
// Federated issue
ticket, err := activitypub.Ticket(issue)
if err != nil {
return err
}
create := activitypub.Create(repo.OriginalURL + "/inbox", ticket)
err = activitypub.Send(issue.Poster, create)
if err != nil {
return err
}
}
for _, assigneeID := range assigneeIDs { for _, assigneeID := range assigneeIDs {
if err := AddAssigneeIfNotAssigned(issue, issue.Poster, assigneeID); err != nil { if err := AddAssigneeIfNotAssigned(issue, issue.Poster, assigneeID); err != nil {
return err return err