diff --git a/models/forgefed/branch.go b/models/forgefed/branch.go new file mode 100644 index 0000000000..da1b995e48 --- /dev/null +++ b/models/forgefed/branch.go @@ -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) + }) +} diff --git a/models/forgefed/commit.go b/models/forgefed/commit.go new file mode 100644 index 0000000000..70b5e2621a --- /dev/null +++ b/models/forgefed/commit.go @@ -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) + }) +} diff --git a/models/forgefed/forgefed.go b/models/forgefed/forgefed.go new file mode 100644 index 0000000000..73767b82d9 --- /dev/null +++ b/models/forgefed/forgefed.go @@ -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) +} diff --git a/models/forgefed/push.go b/models/forgefed/push.go new file mode 100644 index 0000000000..edcdf837e3 --- /dev/null +++ b/models/forgefed/push.go @@ -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) + }) +} diff --git a/models/forgefed/repository.go b/models/forgefed/repository.go index 8c8ae1d319..e85e793ae8 100644 --- a/models/forgefed/repository.go +++ b/models/forgefed/repository.go @@ -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) diff --git a/models/forgefed/ticket.go b/models/forgefed/ticket.go new file mode 100644 index 0000000000..fa988f5d41 --- /dev/null +++ b/models/forgefed/ticket.go @@ -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) + }) +} diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go index 32d1c2a706..36dbd67a32 100644 --- a/routers/api/v1/activitypub/person.go +++ b/routers/api/v1/activitypub/person.go @@ -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 { diff --git a/routers/api/v1/activitypub/repo.go b/routers/api/v1/activitypub/repo.go index a766c41ef1..9ec3063b19 100644 --- a/routers/api/v1/activitypub/repo.go +++ b/routers/api/v1/activitypub/repo.go @@ -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 {