Finish initial ForgeFed implementation

This commit is contained in:
Anthony Wang 2022-06-20 15:38:57 -05:00
parent d12fd434ba
commit a7f32d3382
No known key found for this signature in database
GPG Key ID: BC96B00AEC5F2D76
8 changed files with 309 additions and 28 deletions

55
models/forgefed/branch.go Normal file
View File

@ -0,0 +1,55 @@
// 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 forgefed
import (
ap "github.com/go-ap/activitypub"
"github.com/valyala/fastjson"
)
const (
BranchType ap.ActivityVocabularyType = "Branch"
)
type Branch struct {
ap.Object
// Ref the unique identifier of the branch within the repo
Ref ap.Item `jsonld:"ref,omitempty"`
}
// BranchNew initializes a Branch type Object
func BranchNew() *Branch {
a := ap.ObjectNew(BranchType)
o := Branch{Object: *a}
return &o
}
func (br Branch) MarshalJSON() ([]byte, error) {
b, err := br.Object.MarshalJSON()
if len(b) == 0 || err != nil {
return make([]byte, 0), err
}
b = b[:len(b)-1]
if br.Ref != nil {
ap.WriteItemJSONProp(&b, "ref", br.Ref)
}
ap.Write(&b, '}')
return b, nil
}
func (br *Branch) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
br.Ref = ap.JSONGetItem(val, "ref")
return ap.OnObject(&br.Object, func(a *ap.Object) error {
return ap.LoadObject(val, a)
})
}

63
models/forgefed/commit.go Normal file
View File

@ -0,0 +1,63 @@
// 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 forgefed
import (
"time"
ap "github.com/go-ap/activitypub"
"github.com/valyala/fastjson"
)
const (
CommitType ap.ActivityVocabularyType = "Commit"
)
type Commit struct {
ap.Object
// Created time at which the commit was written by its author
Created time.Time `jsonld:"created,omitempty"`
// Committed time at which the commit was committed by its committer
Committed time.Time `jsonld:"committed,omitempty"`
}
// CommitNew initializes a Commit type Object
func CommitNew() *Commit {
a := ap.ObjectNew(CommitType)
o := Commit{Object: *a}
return &o
}
func (c Commit) MarshalJSON() ([]byte, error) {
b, err := c.Object.MarshalJSON()
if len(b) == 0 || err != nil {
return make([]byte, 0), err
}
b = b[:len(b)-1]
if !c.Created.IsZero() {
ap.WriteTimeJSONProp(&b, "created", c.Created)
}
if !c.Committed.IsZero() {
ap.WriteTimeJSONProp(&b, "committed", c.Committed)
}
ap.Write(&b, '}')
return b, nil
}
func (c *Commit) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
c.Created = ap.JSONGetTime(val, "created")
c.Committed = ap.JSONGetTime(val, "committed")
return ap.OnObject(&c.Object, func(a *ap.Object) error {
return ap.LoadObject(val, a)
})
}

View File

@ -0,0 +1,26 @@
// 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 forgefed
import (
ap "github.com/go-ap/activitypub"
)
// 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) {
if typ == CommitType {
return CommitNew(), nil
} else if typ == BranchType {
return BranchNew(), nil
} else if typ == RepositoryType {
return RepositoryNew(""), nil
} else if typ == PushType {
return PushNew(), nil
} else if typ == TicketType {
return TicketNew(), nil
}
return ap.GetItemByType(typ)
}

67
models/forgefed/push.go Normal file
View File

@ -0,0 +1,67 @@
// 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 forgefed
import (
ap "github.com/go-ap/activitypub"
"github.com/valyala/fastjson"
)
const (
PushType ap.ActivityVocabularyType = "Push"
)
type Push struct {
ap.Object
// Target the specific repo history tip onto which the commits were added
Target ap.Item `jsonld:"target,omitempty"`
// HashBefore hash before adding the new commits
HashBefore ap.Item `jsonld:"hashBefore,omitempty"`
// HashAfter hash before adding the new commits
HashAfter ap.Item `jsonld:"hashAfter,omitempty"`
}
// PushNew initializes a Push type Object
func PushNew() *Push {
a := ap.ObjectNew(PushType)
o := Push{Object: *a}
return &o
}
func (p Push) MarshalJSON() ([]byte, error) {
b, err := p.Object.MarshalJSON()
if len(b) == 0 || err != nil {
return make([]byte, 0), err
}
b = b[:len(b)-1]
if p.Target != nil {
ap.WriteItemJSONProp(&b, "target", p.Target)
}
if p.HashBefore != nil {
ap.WriteItemJSONProp(&b, "hashBefore", p.HashBefore)
}
if p.HashAfter != nil {
ap.WriteItemJSONProp(&b, "hashAfter", p.HashAfter)
}
ap.Write(&b, '}')
return b, nil
}
func (c *Push) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
c.Target = ap.JSONGetItem(val, "target")
c.HashBefore = ap.JSONGetItem(val, "hashBefore")
c.HashAfter = ap.JSONGetItem(val, "hashAfter")
return ap.OnObject(&c.Object, func(a *ap.Object) error {
return ap.LoadObject(val, a)
})
}

View File

@ -21,15 +21,6 @@ type Repository struct {
Forks ap.Item `jsonld:"forks,omitempty"`
}
// GetItemByType instantiates a new Repository object if the type matches
// otherwise it defaults to existing activitypub package typer function.
func GetItemByType(typ ap.ActivityVocabularyType) (ap.Item, error) {
if typ == RepositoryType {
return RepositoryNew(""), nil
}
return ap.GetItemByType(typ)
}
// RepositoryNew initializes a Repository type actor
func RepositoryNew(id ap.ID) *Repository {
a := ap.ActorNew(id, RepositoryType)

79
models/forgefed/ticket.go Normal file
View File

@ -0,0 +1,79 @@
// 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 forgefed
import (
"time"
ap "github.com/go-ap/activitypub"
"github.com/valyala/fastjson"
)
const (
TicketType ap.ActivityVocabularyType = "Ticket"
)
type Ticket struct {
ap.Object
// Dependants Collection of Tickets which depend on this ticket
Dependants ap.ItemCollection `jsonld:"dependants,omitempty"`
// Dependencies Collection of Tickets on which this ticket depends
Dependencies ap.ItemCollection `jsonld:"dependencies,omitempty"`
// IsResolved Whether the work on this ticket is done
IsResolved bool `jsonld:"isResolved,omitempty"`
// ResolvedBy If the work on this ticket is done, who marked the ticket as resolved, or which activity did so
ResolvedBy ap.Item `jsonld:"resolvedBy,omitempty"`
// Resolved When the ticket has been marked as resolved
Resolved time.Time `jsonld:"resolved,omitempty"`
}
// TicketNew initializes a Ticket type Object
func TicketNew() *Ticket {
a := ap.ObjectNew(TicketType)
o := Ticket{Object: *a}
return &o
}
func (t Ticket) MarshalJSON() ([]byte, error) {
b, err := t.Object.MarshalJSON()
if len(b) == 0 || err != nil {
return make([]byte, 0), err
}
b = b[:len(b)-1]
if t.Dependants != nil {
ap.WriteItemCollectionJSONProp(&b, "dependants", t.Dependants)
}
if t.Dependencies != nil {
ap.WriteItemCollectionJSONProp(&b, "dependencies", t.Dependencies)
}
ap.WriteBoolJSONProp(&b, "isResolved", t.IsResolved)
if t.ResolvedBy != nil {
ap.WriteItemJSONProp(&b, "resolvedBy", t.ResolvedBy)
}
if !t.Resolved.IsZero() {
ap.WriteTimeJSONProp(&b, "resolved", t.Resolved)
}
ap.Write(&b, '}')
return b, nil
}
func (t *Ticket) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
t.Dependants = ap.JSONGetItems(val, "dependants")
t.Dependencies = ap.JSONGetItems(val, "dependencies")
t.IsResolved = ap.JSONGetBoolean(val, "isResolved")
t.ResolvedBy = ap.JSONGetItem(val, "resolvedBy")
t.Resolved = ap.JSONGetTime(val, "resolved")
return ap.OnObject(&t.Object, func(a *ap.Object) error {
return ap.LoadObject(val, a)
})
}

View File

@ -137,7 +137,7 @@ func PersonOutbox(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
feed, err := models.GetFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctx.ContextUser,
@ -145,19 +145,19 @@ func PersonOutbox(ctx *context.APIContext) {
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 := ap.OrderedCollectionNew(ap.IRI(link + "/outbox"))
for _, action := range feed {
/*if action.OpType == ExampleType {
activity := ap.ExampleNew()
if action.OpType == models.ActionCommentIssue {
log.Debug("action", action)
activity := ap.Note{Type: ap.NoteType, Content: ap.NaturalLanguageValuesNew()}
activity.Content.Set("en", ap.Content(action.Content))
outbox.OrderedItems.Append(activity)
}*/
log.Debug(action.Content)
}
}
outbox.TotalItems = uint(len(outbox.OrderedItems))
@ -181,7 +181,7 @@ func PersonFollowing(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
users, err := user_model.GetUserFollowing(ctx.ContextUser, utils.GetListOptions(ctx))
if err != nil {
@ -189,7 +189,7 @@ func PersonFollowing(ctx *context.APIContext) {
return
}
following := ap.OrderedCollectionNew(ap.IRI(link))
following := ap.OrderedCollectionNew(ap.IRI(link + "/following"))
following.TotalItems = uint(len(users))
for _, user := range users {
@ -218,7 +218,7 @@ func PersonFollowers(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
users, err := user_model.GetUserFollowers(ctx.ContextUser, utils.GetListOptions(ctx))
if err != nil {
@ -226,7 +226,7 @@ func PersonFollowers(ctx *context.APIContext) {
return
}
followers := ap.OrderedCollectionNew(ap.IRI(link))
followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers"))
followers.TotalItems = uint(len(users))
for _, user := range users {
@ -254,7 +254,7 @@ func PersonLiked(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name
repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{
Actor: ctx.Doer,
@ -266,7 +266,7 @@ func PersonLiked(ctx *context.APIContext) {
return
}
liked := ap.OrderedCollectionNew(ap.IRI(link))
liked := ap.OrderedCollectionNew(ap.IRI(link + "/liked"))
liked.TotalItems = uint(count)
for _, repo := range repos {

View File

@ -42,7 +42,7 @@ func Repo(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name
repo := forgefed.RepositoryNew(ap.IRI(link))
repo.Name = ap.NaturalLanguageValuesNew()
@ -52,7 +52,7 @@ func Repo(ctx *context.APIContext) {
return
}
repo.AttributedTo = ap.IRI(strings.TrimSuffix(link, "/"+ctx.Repo.Repository.Name))
repo.AttributedTo = ap.IRI(strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/user/" + ctx.ContextUser.Name)
repo.Summary = ap.NaturalLanguageValuesNew()
err = repo.Summary.Set("en", ap.Content(ctx.Repo.Repository.Description))
@ -129,7 +129,7 @@ func RepoOutbox(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
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,
@ -143,7 +143,7 @@ func RepoOutbox(ctx *context.APIContext) {
ctx.ServerError("Couldn't fetch outbox", err)
}
outbox := ap.OrderedCollectionNew(ap.IRI(link))
outbox := ap.OrderedCollectionNew(ap.IRI(link + "/outbox"))
for _, action := range feed {
/*if action.OpType == ExampleType {
activity := ap.ExampleNew()
@ -178,7 +178,7 @@ func RepoFollowers(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ActivityPub"
link := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ctx.Req.URL.EscapedPath(), "/")
link := strings.TrimSuffix(setting.AppURL, "/") + "/api/v1/activitypub/repo/" + ctx.ContextUser.Name + "/" + ctx.Repo.Repository.Name
users, err := user_model.GetUserFollowers(ctx.ContextUser, utils.GetListOptions(ctx))
if err != nil {
@ -186,7 +186,7 @@ func RepoFollowers(ctx *context.APIContext) {
return
}
followers := ap.OrderedCollectionNew(ap.IRI(link))
followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers"))
followers.TotalItems = uint(len(users))
for _, user := range users {