Add follower and following collections
All checks were successful
/ docker (push) Successful in 4m34s
All checks were successful
/ docker (push) Successful in 4m34s
This commit is contained in:
parent
b75db5676b
commit
af6ff2dd30
11 changed files with 431 additions and 22 deletions
|
@ -8,6 +8,8 @@ func BuildActivitypubRouter() http.Handler {
|
|||
router := http.NewServeMux()
|
||||
router.HandleFunc("/user/{id}", users)
|
||||
router.HandleFunc("/user/{id}/inbox", userInbox)
|
||||
router.HandleFunc("/user/{id}/followers", userFollowers)
|
||||
router.HandleFunc("/user/{id}/following", userFollowing)
|
||||
router.HandleFunc("/activity/accept/{id}", activityAccept)
|
||||
router.HandleFunc("/activity/create/{id}", activityCreate)
|
||||
router.HandleFunc("/activity/delete/{id}", activityDelete)
|
||||
|
|
|
@ -4,23 +4,24 @@ import "net/http"
|
|||
|
||||
// Used for both unordered and ordered
|
||||
type collectionOut struct {
|
||||
Context any
|
||||
Summary string
|
||||
Type string
|
||||
Items []any
|
||||
Id string
|
||||
TotalItems int
|
||||
First *collectionPageOut
|
||||
Context any `json:"@context,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Items []any `json:"items,omitempty"`
|
||||
Id string `json:"id"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
First string `json:"first"`
|
||||
}
|
||||
|
||||
// Used for both unordered and ordered
|
||||
type collectionPageOut struct {
|
||||
Context any
|
||||
Type string
|
||||
Id string
|
||||
PartOf string
|
||||
Next string
|
||||
Items []any
|
||||
Context any `json:"@context,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
PartOf string `json:"partOf"`
|
||||
Next string `json:"next,omitempty"`
|
||||
Previous string `json:"prev,omitempty"`
|
||||
Items []any `json:"items"`
|
||||
}
|
||||
|
||||
// Unordered collections handler
|
||||
|
|
|
@ -368,10 +368,6 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
|||
log.Error().Err(err).Msg("Failed to marshal accept")
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
Bytes("body", body).
|
||||
Str("target", follower.RemoteInfo.InboxLink).
|
||||
Msg("Sending follow accept out")
|
||||
res, err := webshared.RequestSignedCavage(
|
||||
"POST",
|
||||
follower.RemoteInfo.InboxLink,
|
||||
|
|
|
@ -4,11 +4,14 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
|
@ -50,6 +53,8 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
SpeakAsCat bool `json:"speakAsCat"`
|
||||
IsCat bool `json:"isCat"`
|
||||
RestrictedFollow bool `json:"manuallyApprovesFollowers"`
|
||||
Following string `json:"following"`
|
||||
Followers string `json:"followers"`
|
||||
}
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
|
@ -91,6 +96,8 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
PublicUrl: config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
|
||||
Discoverable: user.Indexable,
|
||||
RestrictedFollow: user.RestrictedFollow,
|
||||
Following: apUrl + "/following",
|
||||
Followers: apUrl + "/followers",
|
||||
}
|
||||
if user.Description != "" {
|
||||
data.Description = &user.Description
|
||||
|
@ -139,6 +146,181 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprint(w, string(encoded))
|
||||
}
|
||||
|
||||
func userFollowing(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
pageNrStr := r.FormValue("page")
|
||||
exists, err := dbgen.User.DoesUserWithIdExist(userId)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("id", userId).Msg("Failed to check if user exists")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
apUrl := userIdToApUrl(userId)
|
||||
var data []byte
|
||||
followingCount, err := dbgen.UserToUserRelation.CountFollowingForId(userId)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("id", userId).Msg("Failed to get following count")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if pageNrStr == "" {
|
||||
col := collectionOut{
|
||||
Context: "https://www.w3.org/ns/activitystreams",
|
||||
Type: "OrderedCollection",
|
||||
Id: apUrl + "/following",
|
||||
TotalItems: followingCount,
|
||||
First: apUrl + "/following?page=0",
|
||||
}
|
||||
data, err = json.Marshal(col)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Any("raw", data).Msg("Failed to marshal following collection page")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
pageNr, err := strconv.Atoi(pageNrStr)
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-page",
|
||||
"bad page number",
|
||||
other.IntoPointer("page number must be an uint"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
hasNextPage := followingCount-(pageNr+1)*50 > 0
|
||||
hasPreviousPage := pageNr > 0
|
||||
links, err := dbgen.UserToUserRelation.GetFollowingApLinksPagedForId(userId, pageNr)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
return
|
||||
case nil:
|
||||
default:
|
||||
log.Error().Err(err).Str("id", userId).Msg("Failed to get account via id")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
page := collectionPageOut{
|
||||
Context: "https://www.w3.org/ns/activitystreams",
|
||||
Type: "OrderedCollectionPage",
|
||||
Id: fmt.Sprintf("%s/following?page=%d", apUrl, pageNr),
|
||||
PartOf: userIdToApUrl(userId) + "/following",
|
||||
Items: sliceutils.Map(links, func(t string) any { return t }),
|
||||
}
|
||||
if hasNextPage {
|
||||
page.Next = fmt.Sprintf("%s/following?page=%d", apUrl, pageNr+1)
|
||||
}
|
||||
if hasPreviousPage {
|
||||
page.Next = fmt.Sprintf("%s/following?page=%d", apUrl, pageNr-1)
|
||||
}
|
||||
data, err = json.Marshal(page)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Any("raw", page).Msg("Failed to marshal following collection page")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/activity+json")
|
||||
fmt.Fprint(w, string(data))
|
||||
}
|
||||
|
||||
func userFollowers(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
pageNrStr := r.FormValue("page")
|
||||
exists, err := dbgen.User.DoesUserWithIdExist(userId)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("id", userId).Msg("Failed to check if user exists")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
log.Debug().Str("id", userId).Msg("user not found")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
apUrl := userIdToApUrl(userId)
|
||||
var data []byte
|
||||
followersCount, err := dbgen.UserToUserRelation.CountFollowersForId(userId)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("id", userId).Msg("Failed to get followers count")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if pageNrStr == "" {
|
||||
col := collectionOut{
|
||||
Context: activitypub.BaseLdContext,
|
||||
Type: "OrderedCollection",
|
||||
Id: apUrl + "/followers",
|
||||
TotalItems: followersCount,
|
||||
First: apUrl + "/followers?page=0",
|
||||
}
|
||||
data, err = json.Marshal(col)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Any("raw", data).Msg("Failed to marshal followers collection page")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
pageNr, err := strconv.Atoi(pageNrStr)
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-page",
|
||||
"bad page number",
|
||||
other.IntoPointer("page number must be an uint"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
hasNextPage := followersCount-(pageNr+1)*50 > 0
|
||||
hasPreviousPage := pageNr > 0
|
||||
links, err := dbgen.UserToUserRelation.GetFollowerApLinksPagedForId(userId, pageNr)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
log.Debug().Msg("No followers found")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
return
|
||||
case nil:
|
||||
default:
|
||||
log.Error().Err(err).Str("id", userId).Msg("Failed to get account via id")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
page := collectionPageOut{
|
||||
Context: activitypub.BaseLdContext,
|
||||
Type: "OrderedCollectionPage",
|
||||
Id: fmt.Sprintf("%s/followers?page=%d", apUrl, pageNr),
|
||||
PartOf: userIdToApUrl(userId) + "/followers",
|
||||
Items: sliceutils.Map(links, func(t string) any { return t }),
|
||||
}
|
||||
if hasNextPage {
|
||||
page.Next = fmt.Sprintf("%s/followers?page=%d", apUrl, pageNr+1)
|
||||
}
|
||||
if hasPreviousPage {
|
||||
page.Next = fmt.Sprintf("%s/followers?page=%d", apUrl, pageNr-1)
|
||||
}
|
||||
data, err = json.Marshal(page)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Any("raw", page).Msg("Failed to marshal followers collection page")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Debug().Bytes("body", data).Msg("Sending collection(page) out")
|
||||
w.Header().Add("Content-Type", "application/activity+json")
|
||||
fmt.Fprint(w, string(data))
|
||||
}
|
||||
|
||||
/*
|
||||
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
|
||||
Fuck you.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue