From 3e690fbae2b45d9e07ab9cbc7321b4d1abfd9493 Mon Sep 17 00:00:00 2001 From: Anthony Wang Date: Sun, 27 Nov 2022 19:09:10 +0000 Subject: [PATCH] Federated issue creation --- routers/api/v1/activitypub/repo.go | 42 +++++++++++---------- routers/api/v1/activitypub/ticket.go | 55 ++-------------------------- services/activitypub/create.go | 17 +++++++++ services/activitypub/objects.go | 54 ++++++++++++++++++++++++++- services/comments/comments.go | 11 +----- services/issue/issue.go | 15 ++++++++ 6 files changed, 113 insertions(+), 81 deletions(-) create mode 100644 services/activitypub/create.go diff --git a/routers/api/v1/activitypub/repo.go b/routers/api/v1/activitypub/repo.go index 33730a76f7..cab978dbdf 100644 --- a/routers/api/v1/activitypub/repo.go +++ b/routers/api/v1/activitypub/repo.go @@ -41,6 +41,7 @@ func Repo(ctx *context.APIContext) { iri := ctx.Repo.Repository.GetIRI() repo := forgefed.RepositoryNew(ap.IRI(iri)) + repo.Type = forgefed.RepositoryType repo.Name = ap.NaturalLanguageValuesNew() err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name)) @@ -123,24 +124,27 @@ func RepoInbox(ctx *context.APIContext) { // Process activity switch activity.Type { case ap.CreateType: - err = ap.OnObject(activity.Object, func(o *ap.Object) error { - switch o.Type { - case forgefed.RepositoryType: - // Fork created by remote instance - return forgefed.OnRepository(o, func(r *forgefed.Repository) error { - return createRepository(ctx, r) - }) - case forgefed.TicketType: - // New issue or pull request - return forgefed.OnTicket(o, func(t *forgefed.Ticket) error { - return createTicket(ctx, t) - }) - case ap.NoteType: - // New comment - return createComment(ctx, o) - } - return nil - }) + switch activity.Object.GetType() { + case forgefed.RepositoryType: + // Fork created by remote instance + err = forgefed.OnRepository(activity.Object, func(r *forgefed.Repository) error { + return createRepository(ctx, r) + }) + case forgefed.TicketType: + // New issue or pull request + err = forgefed.OnTicket(activity.Object, func(t *forgefed.Ticket) error { + return createTicket(ctx, t) + }) + case ap.NoteType: + // New comment + err = ap.On(activity.Object, func(n *ap.Note) error { + return createComment(ctx, n) + }) + 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: err = star(ctx, activity) default: @@ -149,7 +153,7 @@ func RepoInbox(ctx *context.APIContext) { return } if err != nil { - ctx.ServerError("Error when processing: %s", err) + ctx.ServerError("Error when processing", err) } ctx.Status(http.StatusNoContent) diff --git a/routers/api/v1/activitypub/ticket.go b/routers/api/v1/activitypub/ticket.go index 8f6fe1fc1f..97e5ae7926 100644 --- a/routers/api/v1/activitypub/ticket.go +++ b/routers/api/v1/activitypub/ticket.go @@ -8,12 +8,8 @@ import ( "strconv" 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/forgefed" - "code.gitea.io/gitea/modules/setting" - - ap "github.com/go-ap/activitypub" + "code.gitea.io/gitea/services/activitypub" ) // Ticket function returns the Ticket object for an issue or PR @@ -43,63 +39,20 @@ func Ticket(ctx *context.APIContext) { // "200": // "$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) if err != nil { ctx.ServerError("ParseInt", err) return } - issue, err := issues_model.GetIssueByIndex(repo.ID, index) + issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, index) if err != nil { 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 - ticket.Name = ap.NaturalLanguageValuesNew() - err = ticket.Name.Set("en", ap.Content("#"+ctx.Params("id"))) + ticket, err := activitypub.Ticket(issue) if err != nil { - ctx.ServerError("Set Name", err) + ctx.ServerError("Ticket", err) 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) } diff --git a/services/activitypub/create.go b/services/activitypub/create.go new file mode 100644 index 0000000000..344cf330db --- /dev/null +++ b/services/activitypub/create.go @@ -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))}, + } +} diff --git a/services/activitypub/objects.go b/services/activitypub/objects.go index 6758b85d0c..aa6be99f4a 100644 --- a/services/activitypub/objects.go +++ b/services/activitypub/objects.go @@ -5,12 +5,17 @@ package activitypub import ( + "strconv" + + "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/forgefed" 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{ Type: ap.NoteType, AttributedTo: ap.IRI(comment.Poster.GetIRI()), @@ -18,5 +23,50 @@ func Note(comment *issues_model.Comment) ap.Note { } note.Content = ap.NaturalLanguageValuesNew() _ = note.Content.Set("en", ap.Content(comment.Content)) - return note + return ¬e +} + +// 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 } diff --git a/services/comments/comments.go b/services/comments/comments.go index 145e117df2..a28c4c7f52 100644 --- a/services/comments/comments.go +++ b/services/comments/comments.go @@ -14,8 +14,6 @@ import ( "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. @@ -34,13 +32,8 @@ func CreateIssueComment(doer *user_model.User, repo *repo_model.Repository, issu if strings.Contains(repo.OwnerName, "@") { // 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) + create := activitypub.Create(repo.OriginalURL + "/inbox", activitypub.Note(comment)) + err = activitypub.Send(doer, create) if err != nil { return nil, err } diff --git a/services/issue/issue.go b/services/issue/issue.go index 47782e50d3..e18e65b5a4 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -6,6 +6,7 @@ package issue import ( "fmt" + "strings" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" @@ -19,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/activitypub" ) // 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 } + 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 { if err := AddAssigneeIfNotAssigned(issue, issue.Poster, assigneeID); err != nil { return err