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