mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-25 00:08:55 +01:00
favourites GET implementation (#95)
This commit is contained in:
parent
c5180b3860
commit
c7da64922f
@ -67,8 +67,8 @@ Things are moving on the project! As of July 2021 you can now:
|
|||||||
* [ ] /api/v1/accounts/search GET (Search for an account)
|
* [ ] /api/v1/accounts/search GET (Search for an account)
|
||||||
* [ ] Bookmarks
|
* [ ] Bookmarks
|
||||||
* [ ] /api/v1/bookmarks GET (See bookmarked statuses)
|
* [ ] /api/v1/bookmarks GET (See bookmarked statuses)
|
||||||
* [ ] Favourites
|
* [x] Favourites
|
||||||
* [ ] /api/v1/favourites GET (See faved statuses)
|
* [x] /api/v1/favourites GET (See faved statuses)
|
||||||
* [ ] Mutes
|
* [ ] Mutes
|
||||||
* [ ] /api/v1/mutes GET (See list of muted accounts)
|
* [ ] /api/v1/mutes GET (See list of muted accounts)
|
||||||
* [ ] Blocks
|
* [ ] Blocks
|
||||||
|
67
internal/api/client/favourites/favourites.go
Normal file
67
internal/api/client/favourites/favourites.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package favourites
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BasePath is the base URI path for serving favourites
|
||||||
|
BasePath = "/api/v1/favourites"
|
||||||
|
|
||||||
|
// MaxIDKey is the url query for setting a max status ID to return
|
||||||
|
MaxIDKey = "max_id"
|
||||||
|
// SinceIDKey is the url query for returning results newer than the given ID
|
||||||
|
SinceIDKey = "since_id"
|
||||||
|
// MinIDKey is the url query for returning results immediately newer than the given ID
|
||||||
|
MinIDKey = "min_id"
|
||||||
|
// LimitKey is for specifying maximum number of results to return.
|
||||||
|
LimitKey = "limit"
|
||||||
|
// LocalKey is for specifying whether only local statuses should be returned
|
||||||
|
LocalKey = "local"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module implements the ClientAPIModule interface for everything relating to viewing favourites
|
||||||
|
type Module struct {
|
||||||
|
config *config.Config
|
||||||
|
processor processing.Processor
|
||||||
|
log *logrus.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new favourites module
|
||||||
|
func New(config *config.Config, processor processing.Processor, log *logrus.Logger) api.ClientModule {
|
||||||
|
return &Module{
|
||||||
|
config: config,
|
||||||
|
processor: processor,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route attaches all routes from this module to the given router
|
||||||
|
func (m *Module) Route(r router.Router) error {
|
||||||
|
r.AttachHandler(http.MethodGet, BasePath, m.FavouritesGETHandler)
|
||||||
|
return nil
|
||||||
|
}
|
57
internal/api/client/favourites/favouritesget.go
Normal file
57
internal/api/client/favourites/favouritesget.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package favourites
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FavouritesGETHandler handles GETting favourites.
|
||||||
|
func (m *Module) FavouritesGETHandler(c *gin.Context) {
|
||||||
|
l := m.log.WithField("func", "PublicTimelineGETHandler")
|
||||||
|
|
||||||
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error authing: %s", err)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxID := ""
|
||||||
|
maxIDString := c.Query(MaxIDKey)
|
||||||
|
if maxIDString != "" {
|
||||||
|
maxID = maxIDString
|
||||||
|
}
|
||||||
|
|
||||||
|
minID := ""
|
||||||
|
minIDString := c.Query(MinIDKey)
|
||||||
|
if minIDString != "" {
|
||||||
|
minID = minIDString
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 20
|
||||||
|
limitString := c.Query(LimitKey)
|
||||||
|
if limitString != "" {
|
||||||
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error parsing limit string: %s", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit = int(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, errWithCode := m.processor.FavedTimelineGet(authed, maxID, minID, limit)
|
||||||
|
if errWithCode != nil {
|
||||||
|
l.Debugf("error from processor FavedTimelineGet: %s", errWithCode)
|
||||||
|
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.LinkHeader != "" {
|
||||||
|
c.Header("Link", resp.LinkHeader)
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, resp.Statuses)
|
||||||
|
}
|
@ -94,6 +94,8 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("Link", resp.LinkHeader)
|
if resp.LinkHeader != "" {
|
||||||
|
c.Header("Link", resp.LinkHeader)
|
||||||
|
}
|
||||||
c.JSON(http.StatusOK, resp.Statuses)
|
c.JSON(http.StatusOK, resp.Statuses)
|
||||||
}
|
}
|
||||||
|
@ -81,12 +81,15 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
|
|||||||
local = i
|
local = i
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses, errWithCode := m.processor.PublicTimelineGet(authed, maxID, sinceID, minID, limit, local)
|
resp, errWithCode := m.processor.PublicTimelineGet(authed, maxID, sinceID, minID, limit, local)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error from processor account statuses get: %s", errWithCode)
|
l.Debugf("error from processor PublicTimelineGet: %s", errWithCode)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, statuses)
|
if resp.LinkHeader != "" {
|
||||||
|
c.Header("Link", resp.LinkHeader)
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, resp.Statuses)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/app"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/app"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/emoji"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/emoji"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/favourites"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/filter"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/filter"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest"
|
||||||
@ -141,6 +142,7 @@
|
|||||||
statusModule := status.New(c, processor, log)
|
statusModule := status.New(c, processor, log)
|
||||||
securityModule := security.New(c, dbService, log)
|
securityModule := security.New(c, dbService, log)
|
||||||
streamingModule := streaming.New(c, processor, log)
|
streamingModule := streaming.New(c, processor, log)
|
||||||
|
favouritesModule := favourites.New(c, processor, log)
|
||||||
|
|
||||||
apis := []api.ClientModule{
|
apis := []api.ClientModule{
|
||||||
// modules with middleware go first
|
// modules with middleware go first
|
||||||
@ -167,6 +169,7 @@
|
|||||||
emojiModule,
|
emojiModule,
|
||||||
listsModule,
|
listsModule,
|
||||||
streamingModule,
|
streamingModule,
|
||||||
|
favouritesModule,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range apis {
|
for _, m := range apis {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/app"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/app"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/emoji"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/emoji"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/favourites"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/filter"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/filter"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest"
|
||||||
@ -86,6 +87,7 @@
|
|||||||
statusModule := status.New(c, processor, log)
|
statusModule := status.New(c, processor, log)
|
||||||
securityModule := security.New(c, dbService, log)
|
securityModule := security.New(c, dbService, log)
|
||||||
streamingModule := streaming.New(c, processor, log)
|
streamingModule := streaming.New(c, processor, log)
|
||||||
|
favouritesModule := favourites.New(c, processor, log)
|
||||||
|
|
||||||
apis := []api.ClientModule{
|
apis := []api.ClientModule{
|
||||||
// modules with middleware go first
|
// modules with middleware go first
|
||||||
@ -112,6 +114,7 @@
|
|||||||
emojiModule,
|
emojiModule,
|
||||||
listsModule,
|
listsModule,
|
||||||
streamingModule,
|
streamingModule,
|
||||||
|
favouritesModule,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range apis {
|
for _, m := range apis {
|
||||||
|
@ -241,13 +241,26 @@ type DB interface {
|
|||||||
// This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user.
|
// This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user.
|
||||||
WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error)
|
WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error)
|
||||||
|
|
||||||
// GetStatusesWhereFollowing returns a slice of statuses from accounts that are followed by the given account id.
|
// GetHomeTimelineForAccount returns a slice of statuses from accounts that are followed by the given account id.
|
||||||
GetStatusesWhereFollowing(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error)
|
//
|
||||||
|
// Statuses should be returned in descending order of when they were created (newest first).
|
||||||
|
GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error)
|
||||||
|
|
||||||
// GetPublicTimelineForAccount fetches the account's PUBLIC timline -- ie., posts and replies that are public.
|
// GetPublicTimelineForAccount fetches the account's PUBLIC timeline -- ie., posts and replies that are public.
|
||||||
// It will use the given filters and try to return as many statuses as possible up to the limit.
|
// It will use the given filters and try to return as many statuses as possible up to the limit.
|
||||||
|
//
|
||||||
|
// Statuses should be returned in descending order of when they were created (newest first).
|
||||||
GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error)
|
GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error)
|
||||||
|
|
||||||
|
// GetFavedTimelineForAccount fetches the account's FAVED timeline -- ie., posts and replies that the requesting account has faved.
|
||||||
|
// It will use the given filters and try to return as many statuses as possible up to the limit.
|
||||||
|
//
|
||||||
|
// Note that unlike the other GetTimeline functions, the returned statuses will be arranged by their FAVE id, not the STATUS id.
|
||||||
|
// In other words, they'll be returned in descending order of when they were faved by the requesting user, not when they were created.
|
||||||
|
//
|
||||||
|
// Also note the extra return values, which correspond to the nextMaxID and prevMinID for building Link headers.
|
||||||
|
GetFavedTimelineForAccount(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, error)
|
||||||
|
|
||||||
// GetNotificationsForAccount returns a list of notifications that pertain to the given accountID.
|
// GetNotificationsForAccount returns a list of notifications that pertain to the given accountID.
|
||||||
GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error)
|
GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error)
|
||||||
|
|
||||||
|
@ -814,92 +814,6 @@ func (ps *postgresService) WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmode
|
|||||||
return accounts, nil
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *postgresService) GetStatusesWhereFollowing(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) {
|
|
||||||
statuses := []*gtsmodel.Status{}
|
|
||||||
|
|
||||||
q := ps.conn.Model(&statuses)
|
|
||||||
|
|
||||||
q = q.ColumnExpr("status.*").
|
|
||||||
Join("JOIN follows AS f ON f.target_account_id = status.account_id").
|
|
||||||
Where("f.account_id = ?", accountID).
|
|
||||||
Order("status.id DESC")
|
|
||||||
|
|
||||||
if maxID != "" {
|
|
||||||
q = q.Where("status.id < ?", maxID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sinceID != "" {
|
|
||||||
q = q.Where("status.id > ?", sinceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if minID != "" {
|
|
||||||
q = q.Where("status.id > ?", minID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if local {
|
|
||||||
q = q.Where("status.local = ?", local)
|
|
||||||
}
|
|
||||||
|
|
||||||
if limit > 0 {
|
|
||||||
q = q.Limit(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := q.Select()
|
|
||||||
if err != nil {
|
|
||||||
if err == pg.ErrNoRows {
|
|
||||||
return nil, db.ErrNoEntries{}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(statuses) == 0 {
|
|
||||||
return nil, db.ErrNoEntries{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return statuses, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) {
|
|
||||||
statuses := []*gtsmodel.Status{}
|
|
||||||
|
|
||||||
q := ps.conn.Model(&statuses).
|
|
||||||
Where("visibility = ?", gtsmodel.VisibilityPublic).
|
|
||||||
Where("? IS NULL", pg.Ident("in_reply_to_id")).
|
|
||||||
Where("? IS NULL", pg.Ident("in_reply_to_uri")).
|
|
||||||
Where("? IS NULL", pg.Ident("boost_of_id")).
|
|
||||||
Order("status.id DESC")
|
|
||||||
|
|
||||||
if maxID != "" {
|
|
||||||
q = q.Where("status.id < ?", maxID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sinceID != "" {
|
|
||||||
q = q.Where("status.id > ?", sinceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if minID != "" {
|
|
||||||
q = q.Where("status.id > ?", minID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if local {
|
|
||||||
q = q.Where("status.local = ?", local)
|
|
||||||
}
|
|
||||||
|
|
||||||
if limit > 0 {
|
|
||||||
q = q.Limit(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := q.Select()
|
|
||||||
if err != nil {
|
|
||||||
if err == pg.ErrNoRows {
|
|
||||||
return nil, db.ErrNoEntries{}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return statuses, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *postgresService) GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) {
|
func (ps *postgresService) GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) {
|
||||||
notifications := []*gtsmodel.Notification{}
|
notifications := []*gtsmodel.Notification{}
|
||||||
|
|
||||||
|
185
internal/db/pg/timeline.go
Normal file
185
internal/db/pg/timeline.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/go-pg/pg/v10"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ps *postgresService) GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) {
|
||||||
|
statuses := []*gtsmodel.Status{}
|
||||||
|
|
||||||
|
q := ps.conn.Model(&statuses)
|
||||||
|
|
||||||
|
q = q.ColumnExpr("status.*").
|
||||||
|
Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id").
|
||||||
|
Where("f.account_id = ?", accountID).
|
||||||
|
Order("status.id DESC")
|
||||||
|
|
||||||
|
if maxID != "" {
|
||||||
|
q = q.Where("status.id < ?", maxID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sinceID != "" {
|
||||||
|
q = q.Where("status.id > ?", sinceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if minID != "" {
|
||||||
|
q = q.Where("status.id > ?", minID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if local {
|
||||||
|
q = q.Where("status.local = ?", local)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 {
|
||||||
|
q = q.Limit(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := q.Select()
|
||||||
|
if err != nil {
|
||||||
|
if err == pg.ErrNoRows {
|
||||||
|
return nil, db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
return nil, db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) {
|
||||||
|
statuses := []*gtsmodel.Status{}
|
||||||
|
|
||||||
|
q := ps.conn.Model(&statuses).
|
||||||
|
Where("visibility = ?", gtsmodel.VisibilityPublic).
|
||||||
|
Where("? IS NULL", pg.Ident("in_reply_to_id")).
|
||||||
|
Where("? IS NULL", pg.Ident("in_reply_to_uri")).
|
||||||
|
Where("? IS NULL", pg.Ident("boost_of_id")).
|
||||||
|
Order("status.id DESC")
|
||||||
|
|
||||||
|
if maxID != "" {
|
||||||
|
q = q.Where("status.id < ?", maxID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sinceID != "" {
|
||||||
|
q = q.Where("status.id > ?", sinceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if minID != "" {
|
||||||
|
q = q.Where("status.id > ?", minID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if local {
|
||||||
|
q = q.Where("status.local = ?", local)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 {
|
||||||
|
q = q.Limit(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := q.Select()
|
||||||
|
if err != nil {
|
||||||
|
if err == pg.ErrNoRows {
|
||||||
|
return nil, db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
return nil, db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO optimize this query and the logic here, because it's slow as balls -- it takes like a literal second to return with a limit of 20!
|
||||||
|
// It might be worth serving it through a timeline instead of raw DB queries, like we do for Home feeds.
|
||||||
|
func (ps *postgresService) GetFavedTimelineForAccount(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, error) {
|
||||||
|
|
||||||
|
faves := []*gtsmodel.StatusFave{}
|
||||||
|
|
||||||
|
fq := ps.conn.Model(&faves).
|
||||||
|
Where("account_id = ?", accountID).
|
||||||
|
Order("id DESC")
|
||||||
|
|
||||||
|
if maxID != "" {
|
||||||
|
fq = fq.Where("id < ?", maxID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if minID != "" {
|
||||||
|
fq = fq.Where("id > ?", minID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 {
|
||||||
|
fq = fq.Limit(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := fq.Select()
|
||||||
|
if err != nil {
|
||||||
|
if err == pg.ErrNoRows {
|
||||||
|
return nil, "", "", db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
return nil, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(faves) == 0 {
|
||||||
|
return nil, "", "", db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map[statusID]faveID -- we need this to sort statuses by fave ID rather than their own ID
|
||||||
|
statusesFavesMap := map[string]string{}
|
||||||
|
|
||||||
|
in := []string{}
|
||||||
|
for _, f := range faves {
|
||||||
|
statusesFavesMap[f.StatusID] = f.ID
|
||||||
|
in = append(in, f.StatusID)
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses := []*gtsmodel.Status{}
|
||||||
|
err = ps.conn.Model(&statuses).Where("id IN (?)", pg.In(in)).Select()
|
||||||
|
if err != nil {
|
||||||
|
if err == pg.ErrNoRows {
|
||||||
|
return nil, "", "", db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
return nil, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
return nil, "", "", db.ErrNoEntries{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrange statuses by fave ID
|
||||||
|
sort.Slice(statuses, func(i int, j int) bool {
|
||||||
|
statusI := statuses[i]
|
||||||
|
statusJ := statuses[j]
|
||||||
|
return statusesFavesMap[statusI.ID] < statusesFavesMap[statusJ.ID]
|
||||||
|
})
|
||||||
|
|
||||||
|
nextMaxID := faves[len(faves)-1].ID
|
||||||
|
prevMinID := faves[0].ID
|
||||||
|
return statuses, nextMaxID, prevMinID, nil
|
||||||
|
}
|
@ -151,7 +151,9 @@ type Processor interface {
|
|||||||
// HomeTimelineGet returns statuses from the home timeline, with the given filters/parameters.
|
// HomeTimelineGet returns statuses from the home timeline, with the given filters/parameters.
|
||||||
HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode)
|
HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode)
|
||||||
// PublicTimelineGet returns statuses from the public/local timeline, with the given filters/parameters.
|
// PublicTimelineGet returns statuses from the public/local timeline, with the given filters/parameters.
|
||||||
PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, gtserror.WithCode)
|
PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode)
|
||||||
|
// FavedTimelineGet returns faved statuses, with the given filters/parameters.
|
||||||
|
FavedTimelineGet(authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.StatusTimelineResponse, gtserror.WithCode)
|
||||||
|
|
||||||
// AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid.
|
// AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid.
|
||||||
AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Account, error)
|
AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Account, error)
|
||||||
|
@ -60,7 +60,7 @@ func (p *processor) Fave(account *gtsmodel.Account, targetStatusID string) (*api
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newFave {
|
if newFave {
|
||||||
thisFaveID, err := id.NewRandomULID()
|
thisFaveID, err := id.NewULID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
@ -31,33 +31,27 @@
|
|||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
|
func (p *processor) packageStatusResponse(statuses []*apimodel.Status, path string, nextMaxID string, prevMinID string, limit int) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
|
||||||
resp := &apimodel.StatusTimelineResponse{
|
resp := &apimodel.StatusTimelineResponse{
|
||||||
Statuses: []*apimodel.Status{},
|
Statuses: []*apimodel.Status{},
|
||||||
}
|
}
|
||||||
|
resp.Statuses = statuses
|
||||||
apiStatuses, err := p.timelineManager.HomeTimeline(authed.Account.ID, maxID, sinceID, minID, limit, local)
|
|
||||||
if err != nil {
|
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
|
||||||
}
|
|
||||||
resp.Statuses = apiStatuses
|
|
||||||
|
|
||||||
// prepare the next and previous links
|
// prepare the next and previous links
|
||||||
if len(apiStatuses) != 0 {
|
if len(statuses) != 0 {
|
||||||
nextLink := &url.URL{
|
nextLink := &url.URL{
|
||||||
Scheme: p.config.Protocol,
|
Scheme: p.config.Protocol,
|
||||||
Host: p.config.Host,
|
Host: p.config.Host,
|
||||||
Path: "/api/v1/timelines/home",
|
Path: path,
|
||||||
RawPath: url.PathEscape("api/v1/timelines/home"),
|
RawQuery: fmt.Sprintf("limit=%d&max_id=%s", limit, nextMaxID),
|
||||||
RawQuery: fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID),
|
|
||||||
}
|
}
|
||||||
next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String())
|
next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String())
|
||||||
|
|
||||||
prevLink := &url.URL{
|
prevLink := &url.URL{
|
||||||
Scheme: p.config.Protocol,
|
Scheme: p.config.Protocol,
|
||||||
Host: p.config.Host,
|
Host: p.config.Host,
|
||||||
Path: "/api/v1/timelines/home",
|
Path: path,
|
||||||
RawQuery: fmt.Sprintf("limit=%d&min_id=%s", limit, apiStatuses[0].ID),
|
RawQuery: fmt.Sprintf("limit=%d&min_id=%s", limit, prevMinID),
|
||||||
}
|
}
|
||||||
prev := fmt.Sprintf("<%s>; rel=\"prev\"", prevLink.String())
|
prev := fmt.Sprintf("<%s>; rel=\"prev\"", prevLink.String())
|
||||||
resp.LinkHeader = fmt.Sprintf("%s, %s", next, prev)
|
resp.LinkHeader = fmt.Sprintf("%s, %s", next, prev)
|
||||||
@ -66,37 +60,81 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, gtserror.WithCode) {
|
func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
|
||||||
statuses, err := p.db.GetPublicTimelineForAccount(authed.Account.ID, maxID, sinceID, minID, limit, local)
|
statuses, err := p.timelineManager.HomeTimeline(authed.Account.ID, maxID, sinceID, minID, limit, local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := p.filterStatuses(authed, statuses)
|
if len(statuses) == 0 {
|
||||||
if err != nil {
|
return &apimodel.StatusTimelineResponse{
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
Statuses: []*apimodel.Status{},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return p.packageStatusResponse(statuses, "api/v1/timelines/home", statuses[len(statuses)-1].ID, statuses[0].ID, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
|
func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
|
||||||
l := p.log.WithField("func", "filterStatuses")
|
statuses, err := p.db.GetPublicTimelineForAccount(authed.Account.ID, maxID, sinceID, minID, limit, local)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
|
// there are just no entries left
|
||||||
|
return &apimodel.StatusTimelineResponse{
|
||||||
|
Statuses: []*apimodel.Status{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// there's an actual error
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := p.filterPublicStatuses(authed, statuses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.packageStatusResponse(s, "api/v1/timelines/public", s[len(s)-1].ID, s[0].ID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *processor) FavedTimelineGet(authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
|
||||||
|
statuses, nextMaxID, prevMinID, err := p.db.GetFavedTimelineForAccount(authed.Account.ID, maxID, minID, limit)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
|
// there are just no entries left
|
||||||
|
return &apimodel.StatusTimelineResponse{
|
||||||
|
Statuses: []*apimodel.Status{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// there's an actual error
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := p.filterFavedStatuses(authed, statuses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.packageStatusResponse(s, "api/v1/favourites", nextMaxID, prevMinID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *processor) filterPublicStatuses(authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
|
||||||
|
l := p.log.WithField("func", "filterPublicStatuses")
|
||||||
|
|
||||||
apiStatuses := []*apimodel.Status{}
|
apiStatuses := []*apimodel.Status{}
|
||||||
for _, s := range statuses {
|
for _, s := range statuses {
|
||||||
targetAccount := >smodel.Account{}
|
targetAccount := >smodel.Account{}
|
||||||
if err := p.db.GetByID(s.AccountID, targetAccount); err != nil {
|
if err := p.db.GetByID(s.AccountID, targetAccount); err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); ok {
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
l.Debugf("skipping status %s because account %s can't be found in the db", s.ID, s.AccountID)
|
l.Debugf("filterPublicStatuses: skipping status %s because account %s can't be found in the db", s.ID, s.AccountID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error getting status author: %s", err))
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("filterPublicStatuses: error getting status author: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineable, err := p.filter.StatusHometimelineable(s, authed.Account)
|
timelineable, err := p.filter.StatusPublictimelineable(s, authed.Account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking status visibility: %s", err))
|
l.Debugf("filterPublicStatuses: skipping status %s because of an error checking status visibility: %s", s.ID, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if !timelineable {
|
if !timelineable {
|
||||||
continue
|
continue
|
||||||
@ -104,7 +142,42 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat
|
|||||||
|
|
||||||
apiStatus, err := p.tc.StatusToMasto(s, authed.Account)
|
apiStatus, err := p.tc.StatusToMasto(s, authed.Account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("skipping status %s because it couldn't be converted to its mastodon representation: %s", s.ID, err)
|
l.Debugf("filterPublicStatuses: skipping status %s because it couldn't be converted to its mastodon representation: %s", s.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
apiStatuses = append(apiStatuses, apiStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiStatuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *processor) filterFavedStatuses(authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
|
||||||
|
l := p.log.WithField("func", "filterFavedStatuses")
|
||||||
|
|
||||||
|
apiStatuses := []*apimodel.Status{}
|
||||||
|
for _, s := range statuses {
|
||||||
|
targetAccount := >smodel.Account{}
|
||||||
|
if err := p.db.GetByID(s.AccountID, targetAccount); err != nil {
|
||||||
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
|
l.Debugf("filterFavedStatuses: skipping status %s because account %s can't be found in the db", s.ID, s.AccountID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("filterPublicStatuses: error getting status author: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
timelineable, err := p.filter.StatusVisible(s, authed.Account)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("filterFavedStatuses: skipping status %s because of an error checking status visibility: %s", s.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !timelineable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
apiStatus, err := p.tc.StatusToMasto(s, authed.Account)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("filterFavedStatuses: skipping status %s because it couldn't be converted to its mastodon representation: %s", s.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +230,7 @@ func (p *processor) initTimelineFor(account *gtsmodel.Account, wg *sync.WaitGrou
|
|||||||
|
|
||||||
desiredIndexLength := p.timelineManager.GetDesiredIndexLength()
|
desiredIndexLength := p.timelineManager.GetDesiredIndexLength()
|
||||||
|
|
||||||
statuses, err := p.db.GetStatusesWhereFollowing(account.ID, "", "", "", desiredIndexLength, false)
|
statuses, err := p.db.GetHomeTimelineForAccount(account.ID, "", "", "", desiredIndexLength, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||||
l.Error(fmt.Errorf("initTimelineFor: error getting statuses: %s", err))
|
l.Error(fmt.Errorf("initTimelineFor: error getting statuses: %s", err))
|
||||||
@ -176,7 +249,7 @@ func (p *processor) initTimelineFor(account *gtsmodel.Account, wg *sync.WaitGrou
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rearmostStatusID != "" {
|
if rearmostStatusID != "" {
|
||||||
moreStatuses, err := p.db.GetStatusesWhereFollowing(account.ID, rearmostStatusID, "", "", desiredIndexLength/2, false)
|
moreStatuses, err := p.db.GetHomeTimelineForAccount(account.ID, rearmostStatusID, "", "", desiredIndexLength/2, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error(fmt.Errorf("initTimelineFor: error getting more statuses: %s", err))
|
l.Error(fmt.Errorf("initTimelineFor: error getting more statuses: %s", err))
|
||||||
return
|
return
|
||||||
|
@ -23,7 +23,7 @@ func (t *timeline) IndexBefore(statusID string, include bool, amount int) error
|
|||||||
|
|
||||||
grabloop:
|
grabloop:
|
||||||
for len(filtered) < amount {
|
for len(filtered) < amount {
|
||||||
statuses, err := t.db.GetStatusesWhereFollowing(t.accountID, "", offsetStatus, "", amount, false)
|
statuses, err := t.db.GetHomeTimelineForAccount(t.accountID, "", offsetStatus, "", amount, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); ok {
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
break grabloop // we just don't have enough statuses left in the db so index what we've got and then bail
|
break grabloop // we just don't have enough statuses left in the db so index what we've got and then bail
|
||||||
@ -58,7 +58,7 @@ func (t *timeline) IndexBehind(statusID string, amount int) error {
|
|||||||
|
|
||||||
grabloop:
|
grabloop:
|
||||||
for len(filtered) < amount {
|
for len(filtered) < amount {
|
||||||
statuses, err := t.db.GetStatusesWhereFollowing(t.accountID, offsetStatus, "", "", amount, false)
|
statuses, err := t.db.GetHomeTimelineForAccount(t.accountID, offsetStatus, "", "", amount, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); ok {
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
break grabloop // we just don't have enough statuses left in the db so index what we've got and then bail
|
break grabloop // we just don't have enough statuses left in the db so index what we've got and then bail
|
||||||
|
@ -17,6 +17,11 @@ type Filter interface {
|
|||||||
//
|
//
|
||||||
// This function will call StatusVisible internally, so it's not necessary to call it beforehand.
|
// This function will call StatusVisible internally, so it's not necessary to call it beforehand.
|
||||||
StatusHometimelineable(targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account) (bool, error)
|
StatusHometimelineable(targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account) (bool, error)
|
||||||
|
|
||||||
|
// StatusPublictimelineable returns true if targetStatus should be in the public timeline of the requesting account.
|
||||||
|
//
|
||||||
|
// This function will call StatusVisible internally, so it's not necessary to call it beforehand.
|
||||||
|
StatusPublictimelineable(targetStatus *gtsmodel.Status, timelineOwnerAccount *gtsmodel.Account) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type filter struct {
|
type filter struct {
|
||||||
|
37
internal/visibility/statuspublictimelineable.go
Normal file
37
internal/visibility/statuspublictimelineable.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package visibility
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *filter) StatusPublictimelineable(targetStatus *gtsmodel.Status, timelineOwnerAccount *gtsmodel.Account) (bool, error) {
|
||||||
|
l := f.log.WithFields(logrus.Fields{
|
||||||
|
"func": "StatusPublictimelineable",
|
||||||
|
"statusID": targetStatus.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Don't timeline a reply
|
||||||
|
if targetStatus.InReplyToURI != "" || targetStatus.InReplyToID != "" || targetStatus.InReplyToAccountID != "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// status owner should always be able to see their own status in their timeline so we can return early if this is the case
|
||||||
|
if timelineOwnerAccount != nil && targetStatus.AccountID == timelineOwnerAccount.ID {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := f.StatusVisible(targetStatus, timelineOwnerAccount)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("StatusPublictimelineable: error checking visibility of status with id %s: %s", targetStatus.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v {
|
||||||
|
l.Debug("status is not publicTimelineable because it's not visible to the requester")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user