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
|
@ -589,9 +589,15 @@ type IUserToUserRelationDo interface {
|
||||||
UnderlyingDB() *gorm.DB
|
UnderlyingDB() *gorm.DB
|
||||||
schema.Tabler
|
schema.Tabler
|
||||||
|
|
||||||
GetFollowersForId(id string) (result []string, err error)
|
GetFollowerInboxesForId(id string) (result []string, err error)
|
||||||
|
GetFollowerApLinksPagedForId(id string, page int) (result []string, err error)
|
||||||
|
GetFollowingApLinksPagedForId(id string, page int) (result []string, err error)
|
||||||
|
CountFollowersForId(id string) (result int, err error)
|
||||||
|
CountFollowingForId(id string) (result int, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all inbox links for accounts following the user with the specified id
|
||||||
|
//
|
||||||
// SELECT u.inbox_link
|
// SELECT u.inbox_link
|
||||||
// FROM user_to_user_relations r
|
// FROM user_to_user_relations r
|
||||||
// LEFT JOIN user_remote_links u
|
// LEFT JOIN user_remote_links u
|
||||||
|
@ -600,7 +606,7 @@ type IUserToUserRelationDo interface {
|
||||||
//
|
//
|
||||||
// r.target_user_id = @id AND
|
// r.target_user_id = @id AND
|
||||||
// r.relation = 'follow'
|
// r.relation = 'follow'
|
||||||
func (u userToUserRelationDo) GetFollowersForId(id string) (result []string, err error) {
|
func (u userToUserRelationDo) GetFollowerInboxesForId(id string) (result []string, err error) {
|
||||||
var params []interface{}
|
var params []interface{}
|
||||||
|
|
||||||
var generateSQL strings.Builder
|
var generateSQL strings.Builder
|
||||||
|
@ -614,6 +620,106 @@ func (u userToUserRelationDo) GetFollowersForId(id string) (result []string, err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all Ids of the accounts following the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT u.ap_link
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// LEFT JOIN user_remote_links u
|
||||||
|
// ON r.user_id = u.user_id
|
||||||
|
// WHERE
|
||||||
|
//
|
||||||
|
// r.target_user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
//
|
||||||
|
// LIMIT 50
|
||||||
|
// OFFSET @page * 50
|
||||||
|
func (u userToUserRelationDo) GetFollowerApLinksPagedForId(id string, page int) (result []string, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
params = append(params, page)
|
||||||
|
generateSQL.WriteString("SELECT u.ap_link FROM user_to_user_relations r LEFT JOIN user_remote_links u ON r.user_id = u.user_id WHERE r.target_user_id = ? AND r.relation = 'follow' LIMIT 50 OFFSET ? * 50 ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Find(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all Ids of the accounts followed by the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT u.ap_link
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// LEFT JOIN user_remote_links u
|
||||||
|
// ON r.user_id = u.user_id
|
||||||
|
// WHERE
|
||||||
|
//
|
||||||
|
// r.user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
//
|
||||||
|
// LIMIT 50
|
||||||
|
// OFFSET @page * 50
|
||||||
|
func (u userToUserRelationDo) GetFollowingApLinksPagedForId(id string, page int) (result []string, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
params = append(params, page)
|
||||||
|
generateSQL.WriteString("SELECT u.ap_link FROM user_to_user_relations r LEFT JOIN user_remote_links u ON r.user_id = u.user_id WHERE r.user_id = ? AND r.relation = 'follow' LIMIT 50 OFFSET ? * 50 ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Find(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the accounts following the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT COUNT(*)
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// WHERE
|
||||||
|
//
|
||||||
|
// r.target_user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
func (u userToUserRelationDo) CountFollowersForId(id string) (result int, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
generateSQL.WriteString("SELECT COUNT(*) FROM user_to_user_relations r WHERE r.target_user_id = ? AND r.relation = 'follow' ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the accounts following the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT COUNT(*)
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// WHERE
|
||||||
|
//
|
||||||
|
// r.user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
func (u userToUserRelationDo) CountFollowingForId(id string) (result int, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
generateSQL.WriteString("SELECT COUNT(*) FROM user_to_user_relations r WHERE r.user_id = ? AND r.relation = 'follow' ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u userToUserRelationDo) Debug() IUserToUserRelationDo {
|
func (u userToUserRelationDo) Debug() IUserToUserRelationDo {
|
||||||
return u.withDO(u.DO.Debug())
|
return u.withDO(u.DO.Debug())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1660,11 +1660,13 @@ type IUserDo interface {
|
||||||
|
|
||||||
GetByUsernameUnrestricted(username string) (result *models.User, err error)
|
GetByUsernameUnrestricted(username string) (result *models.User, err error)
|
||||||
GetByUsername(username string) (result *models.User, err error)
|
GetByUsername(username string) (result *models.User, err error)
|
||||||
|
GetById(id string) (result *models.User, err error)
|
||||||
GetPagedTruePublic(pageNr uint) (result []models.User, err error)
|
GetPagedTruePublic(pageNr uint) (result []models.User, err error)
|
||||||
GetPagedAllDeleted(pageNr uint) (result []models.User, err error)
|
GetPagedAllDeleted(pageNr uint) (result []models.User, err error)
|
||||||
GetPagedAllNonDeleted(pageNr uint) (result []models.User, err error)
|
GetPagedAllNonDeleted(pageNr uint) (result []models.User, err error)
|
||||||
GdprUsers() (err error)
|
GdprUsers() (err error)
|
||||||
GetRemoteAccountByApUrl(url string) (result *models.User, err error)
|
GetRemoteAccountByApUrl(url string) (result *models.User, err error)
|
||||||
|
DoesUserWithIdExist(id string) (result bool, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a user by a username, ignoring all restrictions on that user
|
// Get a user by a username, ignoring all restrictions on that user
|
||||||
|
@ -1709,6 +1711,31 @@ func (u userDo) GetByUsername(username string) (result *models.User, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a user by the id.
|
||||||
|
// Restricted to users visible to ActivityPub
|
||||||
|
//
|
||||||
|
// SELECT * FROM @@table WHERE
|
||||||
|
//
|
||||||
|
// id = @id AND
|
||||||
|
// deleted_at IS NULL AND
|
||||||
|
// finished_registration = true AND
|
||||||
|
// verified = true
|
||||||
|
//
|
||||||
|
// LIMIT 1
|
||||||
|
func (u userDo) GetById(id string) (result *models.User, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
generateSQL.WriteString("SELECT * FROM users WHERE id = ? AND deleted_at IS NULL AND finished_registration = true AND verified = true LIMIT 1 ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get all true public accounts (verified & no restricted follow & indexable)
|
// Get all true public accounts (verified & no restricted follow & indexable)
|
||||||
// in a paged manner, sorted by date saved
|
// in a paged manner, sorted by date saved
|
||||||
//
|
//
|
||||||
|
@ -1820,6 +1847,31 @@ func (u userDo) GetRemoteAccountByApUrl(url string) (result *models.User, err er
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Does a user with the given Id exist?
|
||||||
|
// The user must be visible from AP
|
||||||
|
//
|
||||||
|
// SELECT EXISTS(
|
||||||
|
//
|
||||||
|
// SELECT * FROM @@table WHERE
|
||||||
|
// id = @id AND
|
||||||
|
// deleted_at IS NULL AND
|
||||||
|
// verified = true
|
||||||
|
//
|
||||||
|
// )
|
||||||
|
func (u userDo) DoesUserWithIdExist(id string) (result bool, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
generateSQL.WriteString("SELECT EXISTS( SELECT * FROM users WHERE id = ? AND deleted_at IS NULL AND verified = true ) ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u userDo) Debug() IUserDo {
|
func (u userDo) Debug() IUserDo {
|
||||||
return u.withDO(u.DO.Debug())
|
return u.withDO(u.DO.Debug())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@ package models
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
Id string `gorm:"primarykey"`
|
Id string `gorm:"primarykey"`
|
||||||
TargetId string
|
TargetId string
|
||||||
TargetType string `orm:"type:collection_target_type"`
|
TargetType string // `orm:"type:collection_target_type"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ const (
|
||||||
CollectionTargetPinnedNotes = CollectionTargetType("pinned")
|
CollectionTargetPinnedNotes = CollectionTargetType("pinned")
|
||||||
CollectionTargetReactions = CollectionTargetType("reactions")
|
CollectionTargetReactions = CollectionTargetType("reactions")
|
||||||
CollectionTargetBoostsAndQuotes = CollectionTargetType("boosts")
|
CollectionTargetBoostsAndQuotes = CollectionTargetType("boosts")
|
||||||
|
COllectionTargetFollows = CollectionTargetType("follows")
|
||||||
|
COllectionTargetFollowers = CollectionTargetType("followers")
|
||||||
)
|
)
|
||||||
|
|
||||||
var AllCollectionTargetTypes = []CollectionTargetType{
|
var AllCollectionTargetTypes = []CollectionTargetType{
|
||||||
|
|
|
@ -103,6 +103,17 @@ type IUser interface {
|
||||||
// LIMIT 1
|
// LIMIT 1
|
||||||
GetByUsername(username string) (*gen.T, error)
|
GetByUsername(username string) (*gen.T, error)
|
||||||
|
|
||||||
|
// Get a user by the id.
|
||||||
|
// Restricted to users visible to ActivityPub
|
||||||
|
//
|
||||||
|
// SELECT * FROM @@table WHERE
|
||||||
|
// id = @id AND
|
||||||
|
// deleted_at IS NULL AND
|
||||||
|
// finished_registration = true AND
|
||||||
|
// verified = true
|
||||||
|
// LIMIT 1
|
||||||
|
GetById(id string) (*gen.T, error)
|
||||||
|
|
||||||
// Get all true public accounts (verified & no restricted follow & indexable)
|
// Get all true public accounts (verified & no restricted follow & indexable)
|
||||||
// in a paged manner, sorted by date saved
|
// in a paged manner, sorted by date saved
|
||||||
//
|
//
|
||||||
|
@ -148,4 +159,15 @@ type IUser interface {
|
||||||
// )
|
// )
|
||||||
// LIMIT 1
|
// LIMIT 1
|
||||||
GetRemoteAccountByApUrl(url string) (*gen.T, error)
|
GetRemoteAccountByApUrl(url string) (*gen.T, error)
|
||||||
|
|
||||||
|
// Does a user with the given Id exist?
|
||||||
|
// The user must be visible from AP
|
||||||
|
//
|
||||||
|
// SELECT EXISTS(
|
||||||
|
// SELECT * FROM @@table WHERE
|
||||||
|
// id = @id AND
|
||||||
|
// deleted_at IS NULL AND
|
||||||
|
// verified = true
|
||||||
|
// )
|
||||||
|
DoesUserWithIdExist(id string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ type UserToUserRelation struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IUserToUserRelation interface {
|
type IUserToUserRelation interface {
|
||||||
|
// Get all inbox links for accounts following the user with the specified id
|
||||||
|
//
|
||||||
// SELECT u.inbox_link
|
// SELECT u.inbox_link
|
||||||
// FROM user_to_user_relations r
|
// FROM user_to_user_relations r
|
||||||
// LEFT JOIN user_remote_links u
|
// LEFT JOIN user_remote_links u
|
||||||
|
@ -20,5 +22,49 @@ type IUserToUserRelation interface {
|
||||||
// WHERE
|
// WHERE
|
||||||
// r.target_user_id = @id AND
|
// r.target_user_id = @id AND
|
||||||
// r.relation = 'follow'
|
// r.relation = 'follow'
|
||||||
GetFollowersForId(id string) ([]string, error)
|
GetFollowerInboxesForId(id string) ([]string, error)
|
||||||
|
|
||||||
|
// Get all Ids of the accounts following the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT u.ap_link
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// LEFT JOIN user_remote_links u
|
||||||
|
// ON r.user_id = u.user_id
|
||||||
|
// WHERE
|
||||||
|
// r.target_user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
// LIMIT 50
|
||||||
|
// OFFSET @page * 50
|
||||||
|
GetFollowerApLinksPagedForId(id string, page int) ([]string, error)
|
||||||
|
|
||||||
|
// Get all Ids of the accounts followed by the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT u.ap_link
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// LEFT JOIN user_remote_links u
|
||||||
|
// ON r.user_id = u.user_id
|
||||||
|
// WHERE
|
||||||
|
// r.user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
// LIMIT 50
|
||||||
|
// OFFSET @page * 50
|
||||||
|
GetFollowingApLinksPagedForId(id string, page int) ([]string, error)
|
||||||
|
|
||||||
|
// Count the accounts following the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT COUNT(*)
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// WHERE
|
||||||
|
// r.target_user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
CountFollowersForId(id string) (int, error)
|
||||||
|
|
||||||
|
// Count the accounts following the user with the specified id
|
||||||
|
//
|
||||||
|
// SELECT COUNT(*)
|
||||||
|
// FROM user_to_user_relations r
|
||||||
|
// WHERE
|
||||||
|
// r.user_id = @id AND
|
||||||
|
// r.relation = 'follow'
|
||||||
|
CountFollowingForId(id string) (int, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u2u := dbgen.UserToUserRelation
|
u2u := dbgen.UserToUserRelation
|
||||||
links, err := u2u.GetFollowersForId(user.ID)
|
links, err := u2u.GetFollowerInboxesForId(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get follower inbox links for user")
|
log.Error().Err(err).Msg("Failed to get follower inbox links for user")
|
||||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
|
|
@ -8,6 +8,8 @@ func BuildActivitypubRouter() http.Handler {
|
||||||
router := http.NewServeMux()
|
router := http.NewServeMux()
|
||||||
router.HandleFunc("/user/{id}", users)
|
router.HandleFunc("/user/{id}", users)
|
||||||
router.HandleFunc("/user/{id}/inbox", userInbox)
|
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/accept/{id}", activityAccept)
|
||||||
router.HandleFunc("/activity/create/{id}", activityCreate)
|
router.HandleFunc("/activity/create/{id}", activityCreate)
|
||||||
router.HandleFunc("/activity/delete/{id}", activityDelete)
|
router.HandleFunc("/activity/delete/{id}", activityDelete)
|
||||||
|
|
|
@ -4,23 +4,24 @@ import "net/http"
|
||||||
|
|
||||||
// Used for both unordered and ordered
|
// Used for both unordered and ordered
|
||||||
type collectionOut struct {
|
type collectionOut struct {
|
||||||
Context any
|
Context any `json:"@context,omitempty"`
|
||||||
Summary string
|
Summary string `json:"summary,omitempty"`
|
||||||
Type string
|
Type string `json:"type"`
|
||||||
Items []any
|
Items []any `json:"items,omitempty"`
|
||||||
Id string
|
Id string `json:"id"`
|
||||||
TotalItems int
|
TotalItems int `json:"totalItems"`
|
||||||
First *collectionPageOut
|
First string `json:"first"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for both unordered and ordered
|
// Used for both unordered and ordered
|
||||||
type collectionPageOut struct {
|
type collectionPageOut struct {
|
||||||
Context any
|
Context any `json:"@context,omitempty"`
|
||||||
Type string
|
Type string `json:"type"`
|
||||||
Id string
|
Id string `json:"id"`
|
||||||
PartOf string
|
PartOf string `json:"partOf"`
|
||||||
Next string
|
Next string `json:"next,omitempty"`
|
||||||
Items []any
|
Previous string `json:"prev,omitempty"`
|
||||||
|
Items []any `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unordered collections handler
|
// 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")
|
log.Error().Err(err).Msg("Failed to marshal accept")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debug().
|
|
||||||
Bytes("body", body).
|
|
||||||
Str("target", follower.RemoteInfo.InboxLink).
|
|
||||||
Msg("Sending follow accept out")
|
|
||||||
res, err := webshared.RequestSignedCavage(
|
res, err := webshared.RequestSignedCavage(
|
||||||
"POST",
|
"POST",
|
||||||
follower.RemoteInfo.InboxLink,
|
follower.RemoteInfo.InboxLink,
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
webutils "git.mstar.dev/mstar/goutils/http"
|
webutils "git.mstar.dev/mstar/goutils/http"
|
||||||
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
"github.com/rs/zerolog/hlog"
|
"github.com/rs/zerolog/hlog"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||||
"git.mstar.dev/mstar/linstrom/config"
|
"git.mstar.dev/mstar/linstrom/config"
|
||||||
|
@ -50,6 +53,8 @@ func users(w http.ResponseWriter, r *http.Request) {
|
||||||
SpeakAsCat bool `json:"speakAsCat"`
|
SpeakAsCat bool `json:"speakAsCat"`
|
||||||
IsCat bool `json:"isCat"`
|
IsCat bool `json:"isCat"`
|
||||||
RestrictedFollow bool `json:"manuallyApprovesFollowers"`
|
RestrictedFollow bool `json:"manuallyApprovesFollowers"`
|
||||||
|
Following string `json:"following"`
|
||||||
|
Followers string `json:"followers"`
|
||||||
}
|
}
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
userId := r.PathValue("id")
|
userId := r.PathValue("id")
|
||||||
|
@ -91,6 +96,8 @@ func users(w http.ResponseWriter, r *http.Request) {
|
||||||
PublicUrl: config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
|
PublicUrl: config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
|
||||||
Discoverable: user.Indexable,
|
Discoverable: user.Indexable,
|
||||||
RestrictedFollow: user.RestrictedFollow,
|
RestrictedFollow: user.RestrictedFollow,
|
||||||
|
Following: apUrl + "/following",
|
||||||
|
Followers: apUrl + "/followers",
|
||||||
}
|
}
|
||||||
if user.Description != "" {
|
if user.Description != "" {
|
||||||
data.Description = &user.Description
|
data.Description = &user.Description
|
||||||
|
@ -139,6 +146,181 @@ func users(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, string(encoded))
|
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
|
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
|
||||||
Fuck you.
|
Fuck you.
|
||||||
|
|
Loading…
Reference in a new issue