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 } */