2023-07-22 18:34:17 +00:00
|
|
|
package web
|
|
|
|
|
|
|
|
import (
|
2023-07-26 19:28:57 +00:00
|
|
|
"net/url"
|
|
|
|
"owl-blogs/app"
|
2023-07-22 18:34:17 +00:00
|
|
|
"owl-blogs/app/repository"
|
|
|
|
"owl-blogs/config"
|
|
|
|
"owl-blogs/domain/model"
|
2024-02-21 19:03:18 +00:00
|
|
|
"owl-blogs/render"
|
2023-07-22 18:34:17 +00:00
|
|
|
|
2023-07-26 19:28:57 +00:00
|
|
|
vocab "github.com/go-ap/activitypub"
|
|
|
|
|
2023-07-22 18:34:17 +00:00
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const ACT_PUB_CONF_NAME = "activity_pub"
|
|
|
|
|
|
|
|
type ActivityPubServer struct {
|
2023-07-26 19:28:57 +00:00
|
|
|
configRepo repository.ConfigRepository
|
|
|
|
entryService *app.EntryService
|
2023-07-22 18:34:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ActivityPubConfig struct {
|
|
|
|
PreferredUsername string `owl:"inputType=text"`
|
|
|
|
PublicKeyPem string `owl:"inputType=text widget=textarea"`
|
|
|
|
PrivateKeyPem string `owl:"inputType=text widget=textarea"`
|
|
|
|
}
|
|
|
|
|
2024-02-21 19:03:18 +00:00
|
|
|
// Form implements app.AppConfig.
|
|
|
|
func (cfg *ActivityPubConfig) Form(binSvc model.BinaryStorageInterface) string {
|
|
|
|
f, _ := render.RenderTemplateToString("forms/ActivityPubConfig", cfg)
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseFormData implements app.AppConfig.
|
|
|
|
func (*ActivityPubConfig) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (app.AppConfig, error) {
|
|
|
|
return &ActivityPubConfig{
|
|
|
|
PreferredUsername: data.FormValue("PreferredUsername"),
|
|
|
|
PublicKeyPem: data.FormValue("PublicKeyPem"),
|
|
|
|
PrivateKeyPem: data.FormValue("PrivateKeyPem"),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-07-22 18:34:17 +00:00
|
|
|
type WebfingerResponse struct {
|
2023-07-26 19:28:57 +00:00
|
|
|
Subject string `json:"subject"`
|
|
|
|
Links []WebfingerLink `json:"links"`
|
2023-07-22 18:34:17 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 19:28:57 +00:00
|
|
|
type WebfingerLink struct {
|
2023-07-22 18:34:17 +00:00
|
|
|
Rel string `json:"rel"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Href string `json:"href"`
|
|
|
|
}
|
|
|
|
|
2023-07-26 19:28:57 +00:00
|
|
|
func NewActivityPubServer(configRepo repository.ConfigRepository, entryService *app.EntryService) *ActivityPubServer {
|
2023-07-22 18:34:17 +00:00
|
|
|
return &ActivityPubServer{
|
2023-07-26 19:28:57 +00:00
|
|
|
configRepo: configRepo,
|
|
|
|
entryService: entryService,
|
2023-07-22 18:34:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ActivityPubServer) HandleWebfinger(ctx *fiber.Ctx) error {
|
|
|
|
siteConfig := model.SiteConfig{}
|
|
|
|
apConfig := ActivityPubConfig{}
|
|
|
|
s.configRepo.Get(ACT_PUB_CONF_NAME, &apConfig)
|
|
|
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
|
|
|
|
|
|
|
webfinger := WebfingerResponse{
|
|
|
|
Subject: ctx.Query("resource"),
|
|
|
|
|
2023-07-26 19:28:57 +00:00
|
|
|
Links: []WebfingerLink{
|
2023-07-22 18:34:17 +00:00
|
|
|
{
|
|
|
|
Rel: "self",
|
|
|
|
Type: "application/activity+json",
|
|
|
|
Href: siteConfig.FullUrl + "/activitypub/actor",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctx.JSON(webfinger)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ActivityPubServer) Router(router fiber.Router) {
|
|
|
|
router.Get("/actor", s.HandleActor)
|
2023-07-26 19:28:57 +00:00
|
|
|
router.Get("/outbox", s.HandleOutbox)
|
2023-07-22 18:34:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ActivityPubServer) HandleActor(ctx *fiber.Ctx) error {
|
|
|
|
siteConfig := model.SiteConfig{}
|
|
|
|
apConfig := ActivityPubConfig{}
|
|
|
|
s.configRepo.Get(ACT_PUB_CONF_NAME, &apConfig)
|
|
|
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
|
|
|
|
2023-07-26 19:28:57 +00:00
|
|
|
actor := vocab.PersonNew(vocab.IRI(siteConfig.FullUrl + "/activitypub/actor"))
|
|
|
|
actor.PreferredUsername = vocab.NaturalLanguageValues{{Value: vocab.Content(apConfig.PreferredUsername)}}
|
|
|
|
actor.Inbox = vocab.IRI(siteConfig.FullUrl + "/activitypub/inbox")
|
|
|
|
actor.Outbox = vocab.IRI(siteConfig.FullUrl + "/activitypub/outbox")
|
|
|
|
actor.Followers = vocab.IRI(siteConfig.FullUrl + "/activitypub/followers")
|
|
|
|
actor.PublicKey = vocab.PublicKey{
|
|
|
|
ID: vocab.ID(siteConfig.FullUrl + "/activitypub/actor#main-key"),
|
|
|
|
Owner: vocab.IRI(siteConfig.FullUrl + "/activitypub/actor"),
|
|
|
|
PublicKeyPem: apConfig.PublicKeyPem,
|
|
|
|
}
|
2023-07-22 18:34:17 +00:00
|
|
|
|
2023-07-26 19:28:57 +00:00
|
|
|
data, err := actor.MarshalJSON()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ctx.Set("Content-Type", "application/activity+json")
|
|
|
|
return ctx.Send(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ActivityPubServer) HandleOutbox(ctx *fiber.Ctx) error {
|
|
|
|
siteConfig := model.SiteConfig{}
|
|
|
|
apConfig := ActivityPubConfig{}
|
|
|
|
s.configRepo.Get(ACT_PUB_CONF_NAME, &apConfig)
|
|
|
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
|
|
|
|
|
|
|
entries, err := s.entryService.FindAllByType(nil, true, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
items := make([]vocab.Item, len(entries))
|
|
|
|
for i, entry := range entries {
|
|
|
|
url, _ := url.JoinPath(siteConfig.FullUrl, "/posts/"+entry.ID()+"/")
|
|
|
|
items[i] = *vocab.ActivityNew(vocab.IRI(url), vocab.CreateType, vocab.Object{
|
|
|
|
ID: vocab.ID(url),
|
|
|
|
Type: vocab.ArticleType,
|
|
|
|
Content: vocab.NaturalLanguageValues{
|
|
|
|
{Value: vocab.Content(entry.Content())},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
outbox := vocab.OrderedCollectionNew(vocab.IRI(siteConfig.FullUrl + "/activitypub/outbox"))
|
|
|
|
outbox.TotalItems = uint(len(items))
|
|
|
|
outbox.OrderedItems = items
|
|
|
|
|
|
|
|
data, err := outbox.MarshalJSON()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-07-22 18:34:17 +00:00
|
|
|
}
|
2023-07-26 19:28:57 +00:00
|
|
|
ctx.Set("Content-Type", "application/activity+json")
|
|
|
|
return ctx.Send(data)
|
2023-07-22 18:34:17 +00:00
|
|
|
|
|
|
|
}
|