linstrom/web/public/api/activitypub/user.go

244 lines
7.8 KiB
Go

package activitypub
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
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/activitypub/translators"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
)
func users(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
userId := r.PathValue("id")
user, err := translators.UserFromStorage(r.Context(), userId)
if err != nil {
log.Error().Err(err).Msg("Failed to get user from db")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
encoded, err := json.Marshal(user)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal response")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/activity+json")
_, _ = 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 := activitypub.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 := translators.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 := translators.CollectionPageOut{
Context: "https://www.w3.org/ns/activitystreams",
Type: "OrderedCollectionPage",
Id: fmt.Sprintf("%s/following?page=%d", apUrl, pageNr),
PartOf: activitypub.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 := activitypub.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 := translators.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 := translators.CollectionPageOut{
Context: activitypub.BaseLdContext,
Type: "OrderedCollectionPage",
Id: fmt.Sprintf("%s/followers?page=%d", apUrl, pageNr),
PartOf: activitypub.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.
If anyone wants to get this shit working *the propper way* with JsonLD, here's the
original code
var chain goap.BaseApChain = &goap.EmptyBaseObject{}
chain = goap.AppendUDIdData(chain, apUrl)
chain = goap.AppendUDTypeData(chain, "Person")
chain = goap.AppendASPreferredNameData(chain, goap.ValueValue[string]{Value: user.DisplayName})
chain = goap.AppendW3SecurityPublicKeyData(
chain,
apUrl+"#main-key",
apUrl,
keyBytesToPem(user.PublicKey),
)
chainMap := chain.MarshalToMap()
proc := ld.NewJsonLdProcessor()
options := ld.NewJsonLdOptions("")
tmp, tmperr := json.Marshal(chainMap)
fmt.Println(string(tmp), tmperr)
data, err := proc.Compact(chainMap, baseLdContext, options)
// data, err := goap.Compact(chain, baseLdContext)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal ap chain")
_ = webutils.ProblemDetailsStatusOnly(w, 500)
return
}
*/