297 lines
8.4 KiB
Go
297 lines
8.4 KiB
Go
// 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 (
|
|
"io"
|
|
"net/http"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
"code.gitea.io/gitea/models/db"
|
|
"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/activitypub"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"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"
|
|
)
|
|
|
|
// Person function returns the Person actor for a user
|
|
func Person(ctx *context.APIContext) {
|
|
// swagger:operation GET /activitypub/user/{username} activitypub activitypubPerson
|
|
// ---
|
|
// summary: Returns the Person actor for a user
|
|
// produces:
|
|
// - application/activity+json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/ActivityPub"
|
|
|
|
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
|
person := ap.PersonNew(ap.IRI(link))
|
|
|
|
person.Name = ap.NaturalLanguageValuesNew()
|
|
err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName))
|
|
if err != nil {
|
|
ctx.ServerError("Set Name", err)
|
|
return
|
|
}
|
|
|
|
person.PreferredUsername = ap.NaturalLanguageValuesNew()
|
|
err = person.PreferredUsername.Set("en", ap.Content(ctx.ContextUser.Name))
|
|
if err != nil {
|
|
ctx.ServerError("Set PreferredUsername", err)
|
|
return
|
|
}
|
|
|
|
person.URL = ap.IRI(ctx.ContextUser.HTMLURL())
|
|
|
|
person.Icon = ap.Image{
|
|
Type: ap.ImageType,
|
|
MediaType: "image/png",
|
|
URL: ap.IRI(ctx.ContextUser.AvatarLink()),
|
|
}
|
|
|
|
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)
|
|
return
|
|
}
|
|
person.PublicKey.PublicKeyPem = publicKeyPem
|
|
|
|
response(ctx, person)
|
|
}
|
|
|
|
// PersonInbox function handles the incoming data for a user inbox
|
|
func PersonInbox(ctx *context.APIContext) {
|
|
// swagger:operation POST /activitypub/user/{username}/inbox activitypub activitypubPersonInbox
|
|
// ---
|
|
// summary: Send to the inbox
|
|
// produces:
|
|
// - application/activity+json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// "$ref": "#/responses/empty"
|
|
|
|
body, err := io.ReadAll(io.LimitReader(ctx.Req.Body, setting.Federation.MaxSize))
|
|
if err != nil {
|
|
ctx.ServerError("Error reading request body", err)
|
|
}
|
|
|
|
var activity ap.Activity
|
|
activity.UnmarshalJSON(body)
|
|
switch activity.Type {
|
|
case ap.FollowType:
|
|
activitypub.Follow(ctx, 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
|
|
}
|
|
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// PersonOutbox function returns the user's Outbox OrderedCollection
|
|
func PersonOutbox(ctx *context.APIContext) {
|
|
// swagger:operation GET /activitypub/user/{username}/outbox activitypub activitypubPersonOutbox
|
|
// ---
|
|
// summary: Returns the Outbox OrderedCollection
|
|
// produces:
|
|
// - application/activity+json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/ActivityPub"
|
|
|
|
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
|
|
|
outbox := ap.OrderedCollectionNew(ap.IRI(link + "/outbox"))
|
|
|
|
feed, err := models.GetFeeds(ctx, models.GetFeedsOptions{
|
|
RequestedUser: ctx.ContextUser,
|
|
Actor: ctx.ContextUser,
|
|
IncludePrivate: false,
|
|
IncludeDeleted: false,
|
|
ListOptions: db.ListOptions{Page: 1, PageSize: 1000000},
|
|
})
|
|
if err != nil {
|
|
ctx.ServerError("Couldn't fetch feed", err)
|
|
return
|
|
}
|
|
|
|
for _, action := range feed {
|
|
if action.OpType == models.ActionCreateRepo {
|
|
// Created a repo
|
|
object := ap.Note{Type: ap.NoteType, Content: ap.NaturalLanguageValuesNew()}
|
|
object.Content.Set("en", ap.Content(action.GetRepoName()))
|
|
create := ap.Create{Type: ap.CreateType, Object: object}
|
|
outbox.OrderedItems.Append(create)
|
|
}
|
|
}
|
|
|
|
stars, err := repo_model.GetStarredRepos(ctx.ContextUser.ID, false, db.ListOptions{Page: 1, PageSize: 1000000})
|
|
if err != nil {
|
|
ctx.ServerError("Couldn't fetch stars", err)
|
|
return
|
|
}
|
|
|
|
for _, star := range stars {
|
|
object := ap.Note{Type: ap.NoteType, Content: ap.NaturalLanguageValuesNew()}
|
|
object.Content.Set("en", ap.Content("Starred " + star.Name))
|
|
create := ap.Create{Type: ap.CreateType, Object: object}
|
|
outbox.OrderedItems.Append(create)
|
|
}
|
|
|
|
outbox.TotalItems = uint(len(outbox.OrderedItems))
|
|
|
|
response(ctx, outbox)
|
|
}
|
|
|
|
// PersonFollowing function returns the user's Following Collection
|
|
func PersonFollowing(ctx *context.APIContext) {
|
|
// swagger:operation GET /activitypub/user/{username}/following activitypub activitypubPersonFollowing
|
|
// ---
|
|
// summary: Returns the Following Collection
|
|
// produces:
|
|
// - application/activity+json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/ActivityPub"
|
|
|
|
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 {
|
|
ctx.ServerError("GetUserFollowing", err)
|
|
return
|
|
}
|
|
|
|
following := ap.OrderedCollectionNew(ap.IRI(link + "/following"))
|
|
following.TotalItems = uint(len(users))
|
|
|
|
for _, user := range users {
|
|
// TODO: handle non-Federated users
|
|
person := ap.PersonNew(ap.IRI(user.Website))
|
|
following.OrderedItems.Append(person)
|
|
}
|
|
|
|
response(ctx, following)
|
|
}
|
|
|
|
// PersonFollowers function returns the user's Followers Collection
|
|
func PersonFollowers(ctx *context.APIContext) {
|
|
// swagger:operation GET /activitypub/user/{username}/followers activitypub activitypubPersonFollowers
|
|
// ---
|
|
// summary: Returns the Followers Collection
|
|
// produces:
|
|
// - application/activity+json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/ActivityPub"
|
|
|
|
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 {
|
|
ctx.ServerError("GetUserFollowers", err)
|
|
return
|
|
}
|
|
|
|
followers := ap.OrderedCollectionNew(ap.IRI(link + "/followers"))
|
|
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)
|
|
}
|
|
|
|
response(ctx, followers)
|
|
}
|
|
|
|
// PersonLiked function returns the user's Liked Collection
|
|
func PersonLiked(ctx *context.APIContext) {
|
|
// swagger:operation GET /activitypub/user/{username}/followers activitypub activitypubPersonLiked
|
|
// ---
|
|
// summary: Returns the Liked Collection
|
|
// produces:
|
|
// - application/activity+json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/ActivityPub"
|
|
|
|
link := setting.AppURL + "api/v1/activitypub/user/" + ctx.ContextUser.Name
|
|
|
|
repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{
|
|
Actor: ctx.Doer,
|
|
Private: ctx.IsSigned,
|
|
StarredByID: ctx.ContextUser.ID,
|
|
})
|
|
if err != nil {
|
|
ctx.ServerError("GetUserStarred", err)
|
|
return
|
|
}
|
|
|
|
liked := ap.OrderedCollectionNew(ap.IRI(link + "/liked"))
|
|
liked.TotalItems = uint(count)
|
|
|
|
for _, repo := range repos {
|
|
// TODO: Handle remote starred repos
|
|
repo := forgefed.RepositoryNew(ap.IRI(setting.AppURL + "api/v1/activitypub/repo/" + repo.OwnerName + "/" + repo.Name))
|
|
liked.OrderedItems.Append(repo)
|
|
}
|
|
|
|
response(ctx, liked)
|
|
}
|