Big refactor: Improve inbox handling logic, move some IRI stuff to iri.go
parent
a63b2be21b
commit
56717396fd
|
@ -17,7 +17,7 @@ var (
|
|||
ErrNameEmpty = errors.New("Name is empty")
|
||||
|
||||
// AlphaDashDotPattern characters prohibited in a user name (anything except A-Za-z0-9_.-)
|
||||
AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.@]`)
|
||||
AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.@]`) // Ugly hack to allow remote usernames to contain @
|
||||
)
|
||||
|
||||
// ErrNameReserved represents a "reserved name" error.
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
const ForgeFedNamespaceURI = "https://forgefed.org/ns"
|
||||
|
||||
// GetItemByType instantiates a new ForgeFed object if the type matches
|
||||
// otherwise it defaults to existing activitypub package typer function.
|
||||
func GetItemByType(typ ap.ActivityVocabularyType) (ap.Item, error) {
|
||||
|
|
|
@ -27,6 +27,10 @@ type Ticket struct {
|
|||
ResolvedBy ap.Item `jsonld:"resolvedBy,omitempty"`
|
||||
// Resolved When the ticket has been marked as resolved
|
||||
Resolved time.Time `jsonld:"resolved,omitempty"`
|
||||
// Origin The head branch if this ticket is a pull request
|
||||
Origin ap.Item `jsonld:"origin,omitempty"`
|
||||
// Target The base branch if this ticket is a pull request
|
||||
Target ap.Item `jsonld:"target,omitempty"`
|
||||
}
|
||||
|
||||
// TicketNew initializes a Ticket type Object
|
||||
|
@ -56,6 +60,12 @@ func (t Ticket) MarshalJSON() ([]byte, error) {
|
|||
if !t.Resolved.IsZero() {
|
||||
ap.WriteTimeJSONProp(&b, "resolved", t.Resolved)
|
||||
}
|
||||
if t.Origin != nil {
|
||||
ap.WriteItemJSONProp(&b, "origin", t.Origin)
|
||||
}
|
||||
if t.Target != nil {
|
||||
ap.WriteItemJSONProp(&b, "target", t.Target)
|
||||
}
|
||||
ap.Write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
|
@ -72,6 +82,8 @@ func (t *Ticket) UnmarshalJSON(data []byte) error {
|
|||
t.IsResolved = ap.JSONGetBoolean(val, "isResolved")
|
||||
t.ResolvedBy = ap.JSONGetItem(val, "resolvedBy")
|
||||
t.Resolved = ap.JSONGetTime(val, "resolved")
|
||||
t.Origin = ap.JSONGetItem(val, "origin")
|
||||
t.Target = ap.JSONGetItem(val, "target")
|
||||
|
||||
return ap.OnObject(&t.Object, func(a *ap.Object) error {
|
||||
return ap.LoadObject(val, a)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// Create a comment
|
||||
func Comment(ctx context.Context, note ap.Note) {
|
||||
actorUser, err := personIRIToUser(ctx, note.AttributedTo.GetLink())
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find actor", err)
|
||||
}
|
||||
|
||||
// TODO: Move IRI processing stuff to iri.go
|
||||
context := note.Context.GetLink()
|
||||
contextSplit := strings.Split(context.String(), "/")
|
||||
username := contextSplit[3]
|
||||
reponame := contextSplit[4]
|
||||
repo, _ := repo_model.GetRepositoryByOwnerAndName(username, reponame)
|
||||
|
||||
idx, _ := strconv.ParseInt(contextSplit[len(contextSplit)-1], 10, 64)
|
||||
issue, _ := issues.GetIssueByIndex(repo.ID, idx)
|
||||
issues.CreateCommentCtx(ctx, &issues.CreateCommentOptions{
|
||||
Doer: actorUser,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
Content: note.Content.String(),
|
||||
})
|
||||
}
|
|
@ -14,33 +14,61 @@ import (
|
|||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func Follow(ctx context.Context, activity ap.Follow) {
|
||||
actorIRI := activity.Actor.GetID()
|
||||
objectIRI := activity.Object.GetID()
|
||||
actorIRISplit := strings.Split(actorIRI.String(), "/")
|
||||
objectIRISplit := strings.Split(objectIRI.String(), "/")
|
||||
actorName := actorIRISplit[len(actorIRISplit)-1] + "@" + actorIRISplit[2]
|
||||
objectName := objectIRISplit[len(objectIRISplit)-1]
|
||||
// Process a Follow activity
|
||||
func Follow(ctx context.Context, follow ap.Follow) {
|
||||
// Actor is the user performing the follow
|
||||
actorIRI := follow.Actor.GetID()
|
||||
actorUser, err := personIRIToUser(ctx, actorIRI)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find actor user for follow", err)
|
||||
return
|
||||
}
|
||||
|
||||
err := FederatedUserNew(actorName, actorIRI)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't create new user", err)
|
||||
}
|
||||
actorUser, err := user_model.GetUserByName(ctx, actorName)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find actor", err)
|
||||
}
|
||||
objectUser, err := user_model.GetUserByName(ctx, objectName)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find object", err)
|
||||
// Object is the user being followed
|
||||
objectIRI := follow.Object.GetID()
|
||||
objectUser, err := personIRIToUser(ctx, objectIRI)
|
||||
// Must be a local user
|
||||
if strings.Contains(objectUser.Name, "@") || err != nil {
|
||||
log.Warn("Couldn't find object user for follow", err)
|
||||
return
|
||||
}
|
||||
|
||||
user_model.FollowUser(actorUser.ID, objectUser.ID)
|
||||
|
||||
accept := ap.AcceptNew(objectIRI, activity)
|
||||
// Send back an Accept activity
|
||||
accept := ap.AcceptNew(objectIRI, follow)
|
||||
accept.Actor = ap.Person{ID: objectIRI}
|
||||
accept.To = ap.ItemCollection{ap.IRI(actorIRI.String() + "/inbox")}
|
||||
accept.Object = activity
|
||||
|
||||
accept.Object = follow
|
||||
Send(objectUser, accept)
|
||||
}
|
||||
|
||||
// Process a Undo follow activity
|
||||
// I haven't tried this yet so hopefully it works
|
||||
func Unfollow(ctx context.Context, unfollow ap.Undo) {
|
||||
// Actor is the user performing the undo follow
|
||||
actorIRI := unfollow.Actor.GetID()
|
||||
actorUser, err := personIRIToUser(ctx, actorIRI)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find actor user for follow", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Object is the user being unfollowed
|
||||
objectIRI := unfollow.Object.GetID()
|
||||
objectUser, err := personIRIToUser(ctx, objectIRI)
|
||||
// Must be a local user
|
||||
if strings.Contains(objectUser.Name, "@") || err != nil {
|
||||
log.Warn("Couldn't find object user for follow", err)
|
||||
return
|
||||
}
|
||||
|
||||
user_model.UnfollowUser(actorUser.ID, objectUser.ID)
|
||||
|
||||
// Send back an Accept activity
|
||||
accept := ap.AcceptNew(objectIRI, unfollow)
|
||||
accept.Actor = ap.Person{ID: objectIRI}
|
||||
accept.To = ap.ItemCollection{ap.IRI(actorIRI.String() + "/inbox")}
|
||||
accept.Object = unfollow
|
||||
Send(objectUser, accept)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
|
||||
//"code.gitea.io/gitea/models/forgefed"
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -20,6 +20,8 @@ import (
|
|||
)
|
||||
|
||||
func Fork(ctx context.Context, instance, username, reponame, destUsername string) {
|
||||
// TODO: Clean this up
|
||||
|
||||
// Migrate repository code
|
||||
user, _ := user_model.GetUserByName(ctx, destUsername)
|
||||
_, err := migrations.MigrateRepository(ctx, user, destUsername, migrations.MigrateOptions{
|
||||
|
@ -30,25 +32,27 @@ func Fork(ctx context.Context, instance, username, reponame, destUsername string
|
|||
log.Warn("Couldn't create fork", err)
|
||||
}
|
||||
|
||||
// Make the migrated repo a fork
|
||||
// TODO: Make the migrated repo a fork
|
||||
|
||||
// Send a Create activity to the instance we are forking from
|
||||
create := ap.Create{Type: ap.CreateType}
|
||||
create.To = ap.ItemCollection{ap.IRI("https://" + instance + "/api/v1/activitypub/repo/" + username + "/" + reponame + "/inbox")}
|
||||
repo := ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + destUsername + "/" + reponame)
|
||||
// repo := forgefed.RepositoryNew(ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + destUsername + "/" + reponame))
|
||||
repo := ap.IRI(setting.AppURL + "api/v1/activitypub/repo/" + destUsername + "/" + reponame)
|
||||
// repo := forgefed.RepositoryNew(ap.IRI(setting.AppURL + "api/v1/activitypub/repo/" + destUsername + "/" + reponame))
|
||||
// repo.ForkedFrom = forgefed.RepositoryNew(ap.IRI())
|
||||
create.Object = repo
|
||||
|
||||
Send(user, &create)
|
||||
}
|
||||
|
||||
func ForkFromCreate(ctx context.Context, activity ap.Create) {
|
||||
func ForkFromCreate(ctx context.Context, repository forgefed.Repository) {
|
||||
// TODO: Clean this up
|
||||
|
||||
// Don't create an actual copy of the remote repo!
|
||||
// https://gitea.com/Ta180m/gitea/issues/7
|
||||
|
||||
// Create the fork
|
||||
repoIRI := activity.Object.GetID()
|
||||
repoIRI := repository.GetID()
|
||||
repoIRISplit := strings.Split(repoIRI.String(), "/")
|
||||
instance := repoIRISplit[2]
|
||||
username := repoIRISplit[7]
|
||||
|
@ -63,6 +67,4 @@ func ForkFromCreate(ctx context.Context, activity ap.Create) {
|
|||
|
||||
_, err := repo_service.ForkRepository(ctx, user, user, repo_service.ForkRepoOptions{BaseRepo: repo, Name: reponame, Description: "this is a remote fork"})
|
||||
log.Warn("Couldn't create copy of remote fork", err)
|
||||
|
||||
// TODO: send back accept
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// Returns the username corresponding to a Person actor IRI
|
||||
func personIRIToName(personIRI ap.IRI) (string, error) {
|
||||
personIRISplit := strings.Split(personIRI.String(), "/")
|
||||
if len(personIRISplit) < 3 {
|
||||
return "", errors.New("Not a Person actor IRI")
|
||||
}
|
||||
|
||||
instance := personIRISplit[2]
|
||||
name := personIRISplit[len(personIRISplit)-1]
|
||||
if instance == setting.Domain {
|
||||
// Local user
|
||||
return name, nil
|
||||
} else {
|
||||
// Remote user
|
||||
// Get name in username@instance.com format
|
||||
return name + "@" + instance, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the user corresponding to a Person actor IRI
|
||||
func personIRIToUser(ctx context.Context, personIRI ap.IRI) (*user_model.User, error) {
|
||||
name, err := personIRIToName(personIRI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, name)
|
||||
if err != nil || !strings.Contains(name, "@") {
|
||||
return user, err
|
||||
}
|
||||
FederatedUserNew(personIRI)
|
||||
return user_model.GetUserByName(ctx, name)
|
||||
}
|
||||
|
||||
// Returns the owner and name corresponding to a Repository actor IRI
|
||||
func repositoryIRIToName(repoIRI ap.IRI) (string, string, error) {
|
||||
repoIRISplit := strings.Split(repoIRI.String(), "/")
|
||||
if len(repoIRISplit) < 5 {
|
||||
return "", "", errors.New("Not a Repository actor IRI")
|
||||
}
|
||||
|
||||
instance := repoIRISplit[2]
|
||||
username := repoIRISplit[len(repoIRISplit)-2]
|
||||
reponame := repoIRISplit[len(repoIRISplit)-1]
|
||||
if instance == setting.Domain {
|
||||
// Local repo
|
||||
return username, reponame, nil
|
||||
} else {
|
||||
// Remote repo
|
||||
return username + "@" + instance, reponame, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the repository corresponding to a Repository actor IRI
|
||||
func repositoryIRIToRepository(ctx context.Context, repoIRI ap.IRI) (*repo_model.Repository, error) {
|
||||
username, reponame, err := repositoryIRIToName(repoIRI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo_model.GetRepositoryByOwnerAndName(username, reponame)
|
||||
}
|
|
@ -6,41 +6,11 @@ package activitypub
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/log"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
)
|
||||
|
||||
func Comment(ctx context.Context, activity ap.Note) {
|
||||
actorIRI := activity.AttributedTo.GetLink()
|
||||
actorIRISplit := strings.Split(actorIRI.String(), "/")
|
||||
actorName := actorIRISplit[len(actorIRISplit)-1] + "@" + actorIRISplit[2]
|
||||
err := FederatedUserNew(actorName, actorIRI)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't create new user", err)
|
||||
}
|
||||
actorUser, err := user_model.GetUserByName(ctx, actorName)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find actor", err)
|
||||
}
|
||||
|
||||
context := activity.Context.GetLink()
|
||||
contextSplit := strings.Split(context.String(), "/")
|
||||
username := contextSplit[3]
|
||||
reponame := contextSplit[4]
|
||||
repo, _ := repo_model.GetRepositoryByOwnerAndName(username, reponame)
|
||||
idx, _ := strconv.ParseInt(contextSplit[len(contextSplit)-1], 10, 64)
|
||||
issue, _ := issues.GetIssueByIndex(repo.ID, idx)
|
||||
issues.CreateCommentCtx(ctx, &issues.CreateCommentOptions{
|
||||
Doer: actorUser,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
Content: activity.Content.String(),
|
||||
})
|
||||
// Create an issue
|
||||
func Issue(ctx context.Context, ticket forgefed.Ticket) {
|
||||
// TODO
|
||||
}
|
||||
|
|
|
@ -9,31 +9,23 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
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/log"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func PullRequest(ctx context.Context, activity ap.Move) {
|
||||
actorIRI := activity.AttributedTo.GetLink()
|
||||
actorIRISplit := strings.Split(actorIRI.String(), "/")
|
||||
actorName := actorIRISplit[len(actorIRISplit)-1] + "@" + actorIRISplit[2]
|
||||
err := FederatedUserNew(actorName, actorIRI)
|
||||
func PullRequest(ctx context.Context, ticket forgefed.Ticket) {
|
||||
// TODO: Clean this up
|
||||
|
||||
actorUser, err := personIRIToUser(ctx, ticket.AttributedTo.GetLink())
|
||||
if err != nil {
|
||||
log.Warn("Couldn't create new user", err)
|
||||
}
|
||||
actorUser, err := user_model.GetUserByName(ctx, actorName)
|
||||
if err != nil {
|
||||
log.Warn("Couldn't find actor", err)
|
||||
log.Warn("Couldn't find ticket actor user", err)
|
||||
}
|
||||
|
||||
// This code is really messy
|
||||
// The IRI processing stuff should be in a separate function
|
||||
originIRI := activity.Origin.GetLink()
|
||||
// TODO: The IRI processing stuff should be moved to iri.go
|
||||
originIRI := ticket.Origin.GetLink()
|
||||
originIRISplit := strings.Split(originIRI.String(), "/")
|
||||
originInstance := originIRISplit[2]
|
||||
originUsername := originIRISplit[3]
|
||||
|
@ -41,7 +33,7 @@ func PullRequest(ctx context.Context, activity ap.Move) {
|
|||
originBranch := originIRISplit[len(originIRISplit)-1]
|
||||
originRepo, _ := repo_model.GetRepositoryByOwnerAndName(originUsername+"@"+originInstance, originReponame)
|
||||
|
||||
targetIRI := activity.Target.GetLink()
|
||||
targetIRI := ticket.Target.GetLink()
|
||||
targetIRISplit := strings.Split(targetIRI.String(), "/")
|
||||
// targetInstance := targetIRISplit[2]
|
||||
targetUsername := targetIRISplit[3]
|
||||
|
@ -56,19 +48,18 @@ func PullRequest(ctx context.Context, activity ap.Move) {
|
|||
PosterID: actorUser.ID,
|
||||
Poster: actorUser,
|
||||
IsPull: true,
|
||||
Content: "🎉",
|
||||
Content: "🎉", // TODO: Get content from Ticket object
|
||||
}
|
||||
|
||||
pr := &issues_model.PullRequest{
|
||||
HeadRepoID: originRepo.ID,
|
||||
BaseRepoID: targetRepo.ID,
|
||||
HeadBranch: originBranch,
|
||||
HeadCommitID: "73f228996f27fad2c7bb60435f912d943b66b0ee", // hardcoded for now
|
||||
BaseBranch: targetBranch,
|
||||
HeadRepo: originRepo,
|
||||
BaseRepo: targetRepo,
|
||||
MergeBase: "",
|
||||
Type: issues_model.PullRequestGitea,
|
||||
HeadRepoID: originRepo.ID,
|
||||
BaseRepoID: targetRepo.ID,
|
||||
HeadBranch: originBranch,
|
||||
BaseBranch: targetBranch,
|
||||
HeadRepo: originRepo,
|
||||
BaseRepo: targetRepo,
|
||||
MergeBase: "",
|
||||
Type: issues_model.PullRequestGitea,
|
||||
}
|
||||
|
||||
err = pull_service.NewPullRequest(ctx, targetRepo, prIssue, []int64{}, []string{}, pr, []int64{})
|
||||
|
|
|
@ -10,15 +10,17 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/go-ap/jsonld"
|
||||
)
|
||||
|
||||
// Fetch a remote ActivityStreams object
|
||||
func Fetch(iri *url.URL) (b []byte, err error) {
|
||||
req := httplib.NewRequest(iri.String(), http.MethodGet)
|
||||
req.Header("Accept", ActivityStreamsContentType)
|
||||
|
@ -37,22 +39,21 @@ func Fetch(iri *url.URL) (b []byte, err error) {
|
|||
return b, err
|
||||
}
|
||||
|
||||
// Send an activity
|
||||
func Send(user *user_model.User, activity *ap.Activity) {
|
||||
body, err := activity.MarshalJSON()
|
||||
binary, err := jsonld.WithContext(
|
||||
jsonld.IRI(ap.ActivityBaseURI),
|
||||
jsonld.IRI(ap.SecurityContextURI),
|
||||
jsonld.IRI(forgefed.ForgeFedNamespaceURI),
|
||||
).Marshal(activity)
|
||||
if err != nil {
|
||||
log.Warn("Marshal", err)
|
||||
return
|
||||
}
|
||||
var jsonmap map[string]interface{}
|
||||
err = json.Unmarshal(body, &jsonmap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
jsonmap["@context"] = "https://www.w3.org/ns/activitystreams"
|
||||
body, _ = json.Marshal(jsonmap)
|
||||
|
||||
for _, to := range activity.To {
|
||||
client, _ := NewClient(user, setting.AppURL+"api/v1/activitypub/user/"+user.Name+"#main-key")
|
||||
resp, _ := client.Post(body, to.GetID().String())
|
||||
resp, _ := client.Post(binary, to.GetID().String())
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize))
|
||||
log.Debug(string(respBody))
|
||||
}
|
|
@ -11,10 +11,15 @@ import (
|
|||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func FederatedUserNew(name string, IRI ap.IRI) error {
|
||||
func FederatedUserNew(IRI ap.IRI) error {
|
||||
name, err := personIRIToName(IRI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &user_model.User{
|
||||
Name: name,
|
||||
Email: name, // TODO: change this to something else to prevent collisions with normal users
|
||||
Email: name, // TODO: change this to something else to prevent collisions with normal users, maybe fetch email using Gitea API
|
||||
LoginType: auth.Federated,
|
||||
Website: IRI.String(),
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package activitypub
|
|||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
|
@ -39,7 +38,7 @@ func Person(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
person := ap.PersonNew(ap.IRI(link))
|
||||
|
||||
person.Name = ap.NaturalLanguageValuesNew()
|
||||
|
@ -66,15 +65,12 @@ func Person(ctx *context.APIContext) {
|
|||
|
||||
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.PublicKey.ID = ap.IRI(link + "#main-key")
|
||||
person.PublicKey.Owner = ap.IRI(link)
|
||||
|
||||
publicKeyPem, err := activitypub.GetPublicKey(ctx.ContextUser)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetPublicKey", err)
|
||||
|
@ -109,10 +105,13 @@ func PersonInbox(ctx *context.APIContext) {
|
|||
|
||||
var activity ap.Activity
|
||||
activity.UnmarshalJSON(body)
|
||||
if activity.Type == ap.FollowType {
|
||||
switch activity.Type {
|
||||
case ap.FollowType:
|
||||
activitypub.Follow(ctx, activity)
|
||||
} else {
|
||||
log.Warn("ActivityStreams type not supported", activity)
|
||||
case ap.UndoType:
|
||||
activitypub.Unfollow(ctx, activity)
|
||||
default:
|
||||
log.Debug("ActivityStreams type not supported", activity)
|
||||
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
|
||||
return
|
||||
}
|
||||
|
@ -137,7 +136,7 @@ func PersonOutbox(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
|
||||
feed, err := models.GetFeeds(ctx, models.GetFeedsOptions{
|
||||
RequestedUser: ctx.ContextUser,
|
||||
|
@ -186,7 +185,7 @@ func PersonFollowing(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
|
||||
users, _, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
|
@ -223,7 +222,7 @@ func PersonFollowers(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
|
||||
users, _, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
|
@ -235,6 +234,7 @@ func PersonFollowers(ctx *context.APIContext) {
|
|||
followers.TotalItems = uint(len(users))
|
||||
|
||||
for _, user := range users {
|
||||
// TODO: handle non-Federated users
|
||||
person := ap.PersonNew(ap.IRI(user.Website))
|
||||
followers.OrderedItems.Append(person)
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ func PersonLiked(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
||||
|
||||
repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{
|
||||
Actor: ctx.Doer,
|
||||
|
@ -275,7 +275,8 @@ func PersonLiked(ctx *context.APIContext) {
|
|||
liked.TotalItems = uint(count)
|
||||
|
||||
for _, repo := range repos {
|
||||
repo := forgefed.RepositoryNew(ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + repo.OwnerName + "/" + repo.Name))
|
||||
// TODO: Handle remote starred repos
|
||||
repo := forgefed.RepositoryNew(ap.IRI(setting.AppURL + "api/v1/activitypub/repo/" + repo.OwnerName + "/" + repo.Name))
|
||||
liked.OrderedItems.Append(repo)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,21 +7,18 @@ package activitypub
|
|||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// Repo function
|
||||
// Repo function returns the Repository actor of a repo
|
||||
func Repo(ctx *context.APIContext) {
|
||||
// swagger:operation GET /activitypub/repo/{username}/{reponame} activitypub activitypubRepo
|
||||
// ---
|
||||
|
@ -43,7 +40,7 @@ func Repo(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name
|
||||
link := setting.AppURL + "api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name
|
||||
repo := forgefed.RepositoryNew(ap.IRI(link))
|
||||
|
||||
repo.Name = ap.NaturalLanguageValuesNew()
|
||||
|
@ -53,7 +50,7 @@ func Repo(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
repo.AttributedTo = ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name)
|
||||
repo.AttributedTo = ap.IRI(setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name)
|
||||
|
||||
repo.Summary = ap.NaturalLanguageValuesNew()
|
||||
err = repo.Summary.Set("en", ap.Content(ctx.Repo.Repository.Description))
|
||||
|
@ -70,7 +67,7 @@ func Repo(ctx *context.APIContext) {
|
|||
response(ctx, repo)
|
||||
}
|
||||
|
||||
// RepoInbox function
|
||||
// RepoInbox function handles the incoming data for a repo inbox
|
||||
func RepoInbox(ctx *context.APIContext) {
|
||||
// swagger:operation POST /activitypub/repo/{username}/{reponame}/inbox activitypub activitypubRepoInbox
|
||||
// ---
|
||||
|
@ -95,29 +92,63 @@ func RepoInbox(ctx *context.APIContext) {
|
|||
body, err := io.ReadAll(ctx.Req.Body)
|
||||
if err != nil {
|
||||
ctx.ServerError("Error reading request body", err)
|
||||
return
|
||||
}
|
||||
var activity ap.Activity
|
||||
activity.UnmarshalJSON(body) // This function doesn't support ForgeFed types!!!
|
||||
log.Warn("Debug", activity)
|
||||
switch activity.Type {
|
||||
case ap.NoteType:
|
||||
// activitypub.Comment(ctx, activity)
|
||||
|
||||
var activity map[string]interface{}
|
||||
err = json.Unmarshal(body, activity)
|
||||
if err != nil {
|
||||
ctx.ServerError("Unmarshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch activity["type"].(ap.ActivityVocabularyType) {
|
||||
case ap.CreateType:
|
||||
// if activity.Object.GetType() == forgefed.RepositoryType {
|
||||
// Fork created by remote instance
|
||||
activitypub.ForkFromCreate(ctx, activity)
|
||||
//}
|
||||
case ap.MoveType:
|
||||
// This should actually be forgefed.TicketType but that the UnmarshalJSON function above doesn't support ForgeFed!
|
||||
activitypub.PullRequest(ctx, activity)
|
||||
// Create activity, extract the object
|
||||
object, ok := activity["object"].(map[string]interface{})
|
||||
if ok {
|
||||
ctx.ServerError("Activity does not contain object", err)
|
||||
return
|
||||
}
|
||||
objectBinary, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
ctx.ServerError("Marshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch object["type"].(ap.ActivityVocabularyType) {
|
||||
case forgefed.RepositoryType:
|
||||
// Fork created by remote instance
|
||||
var repository forgefed.Repository
|
||||
repository.UnmarshalJSON(objectBinary)
|
||||
activitypub.ForkFromCreate(ctx, repository)
|
||||
case forgefed.TicketType:
|
||||
// New issue or pull request
|
||||
var ticket forgefed.Ticket
|
||||
ticket.UnmarshalJSON(objectBinary)
|
||||
if ticket.Origin != nil {
|
||||
// New pull request
|
||||
activitypub.PullRequest(ctx, ticket)
|
||||
} else {
|
||||
// New issue
|
||||
activitypub.Issue(ctx, ticket)
|
||||
}
|
||||
case ap.NoteType:
|
||||
// New comment
|
||||
var note ap.Note
|
||||
note.UnmarshalJSON(objectBinary)
|
||||
activitypub.Comment(ctx, note)
|
||||
}
|
||||
default:
|
||||
log.Warn("ActivityStreams type not supported", activity)
|
||||
ctx.PlainText(http.StatusNotImplemented, "ActivityStreams type not supported")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// RepoOutbox function
|
||||
// RepoOutbox function returns the repo's Outbox OrderedCollection
|
||||
func RepoOutbox(ctx *context.APIContext) {
|
||||
// swagger:operation GET /activitypub/repo/{username}/outbox activitypub activitypubPersonOutbox
|
||||
// ---
|
||||
|
@ -139,34 +170,11 @@ func RepoOutbox(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name
|
||||
|
||||
feed, err := models.GetFeeds(ctx, models.GetFeedsOptions{
|
||||
RequestedUser: ctx.ContextUser,
|
||||
Actor: ctx.ContextUser,
|
||||
IncludePrivate: false,
|
||||
OnlyPerformedBy: true,
|
||||
IncludeDeleted: false,
|
||||
Date: ctx.FormString("date"),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("Couldn't fetch outbox", err)
|
||||
}
|
||||
|
||||
outbox := ap.OrderedCollectionNew(ap.IRI(link + "/outbox"))
|
||||
for _, action := range feed {
|
||||
/*if action.OpType == ExampleType {
|
||||
activity := ap.ExampleNew()
|
||||
outbox.OrderedItems.Append(activity)
|
||||
}*/
|
||||
log.Debug(action.Content)
|
||||
}
|
||||
outbox.TotalItems = uint(len(outbox.OrderedItems))
|
||||
|
||||
response(ctx, outbox)
|
||||
// TODO
|
||||
ctx.Status(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// RepoFollowers function
|
||||
// RepoFollowers function returns the repo's Followers OrderedCollection
|
||||
func RepoFollowers(ctx *context.APIContext) {
|
||||
// swagger:operation GET /activitypub/repo/{username}/{reponame}/followers activitypub activitypubRepoFollowers
|
||||
// ---
|
||||
|
@ -188,21 +196,6 @@ func RepoFollowers(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name
|
||||
|
||||
users, _, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserFollowers", err)
|
||||
return
|
||||
}
|
||||
|
||||
followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers"))
|
||||
followers.TotalItems = uint(len(users))
|
||||
|
||||
for _, user := range users {
|
||||
person := ap.PersonNew(ap.IRI(user.Website))
|
||||
followers.OrderedItems.Append(person)
|
||||
}
|
||||
|
||||
response(ctx, followers)
|
||||
// TODO
|
||||
ctx.Status(http.StatusNotImplemented)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package activitypub
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -15,12 +16,18 @@ import (
|
|||
"github.com/go-ap/jsonld"
|
||||
)
|
||||
|
||||
// Respond with an ActivityStreams object
|
||||
func response(ctx *context.APIContext, v interface{}) {
|
||||
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(v)
|
||||
binary, err := jsonld.WithContext(
|
||||
jsonld.IRI(ap.ActivityBaseURI),
|
||||
jsonld.IRI(ap.SecurityContextURI),
|
||||
jsonld.IRI(forgefed.ForgeFedNamespaceURI),
|
||||
).Marshal(v)
|
||||
if err != nil {
|
||||
ctx.ServerError("Marshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
if _, err = ctx.Resp.Write(binary); err != nil {
|
||||
|
|
Loading…
Reference in New Issue