gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

list.go (5817B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package timeline
     19 
     20 import (
     21 	"context"
     22 	"errors"
     23 
     24 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     25 	"github.com/superseriousbusiness/gotosocial/internal/db"
     26 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     27 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     28 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
     29 	"github.com/superseriousbusiness/gotosocial/internal/state"
     30 	"github.com/superseriousbusiness/gotosocial/internal/timeline"
     31 	"github.com/superseriousbusiness/gotosocial/internal/typeutils"
     32 	"github.com/superseriousbusiness/gotosocial/internal/util"
     33 	"github.com/superseriousbusiness/gotosocial/internal/visibility"
     34 )
     35 
     36 // ListTimelineGrab returns a function that satisfies GrabFunction for list timelines.
     37 func ListTimelineGrab(state *state.State) timeline.GrabFunction {
     38 	return func(ctx context.Context, listID string, maxID string, sinceID string, minID string, limit int) ([]timeline.Timelineable, bool, error) {
     39 		statuses, err := state.DB.GetListTimeline(ctx, listID, maxID, sinceID, minID, limit)
     40 		if err != nil && !errors.Is(err, db.ErrNoEntries) {
     41 			err = gtserror.Newf("error getting statuses from db: %w", err)
     42 			return nil, false, err
     43 		}
     44 
     45 		count := len(statuses)
     46 		if count == 0 {
     47 			// We just don't have enough statuses
     48 			// left in the db so return stop = true.
     49 			return nil, true, nil
     50 		}
     51 
     52 		items := make([]timeline.Timelineable, count)
     53 		for i, s := range statuses {
     54 			items[i] = s
     55 		}
     56 
     57 		return items, false, nil
     58 	}
     59 }
     60 
     61 // ListTimelineFilter returns a function that satisfies FilterFunction for list timelines.
     62 func ListTimelineFilter(state *state.State, filter *visibility.Filter) timeline.FilterFunction {
     63 	return func(ctx context.Context, listID string, item timeline.Timelineable) (shouldIndex bool, err error) {
     64 		status, ok := item.(*gtsmodel.Status)
     65 		if !ok {
     66 			err = gtserror.New("could not convert item to *gtsmodel.Status")
     67 			return false, err
     68 		}
     69 
     70 		list, err := state.DB.GetListByID(ctx, listID)
     71 		if err != nil {
     72 			err = gtserror.Newf("error getting list with id %s: %w", listID, err)
     73 			return false, err
     74 		}
     75 
     76 		requestingAccount, err := state.DB.GetAccountByID(ctx, list.AccountID)
     77 		if err != nil {
     78 			err = gtserror.Newf("error getting account with id %s: %w", list.AccountID, err)
     79 			return false, err
     80 		}
     81 
     82 		timelineable, err := filter.StatusHomeTimelineable(ctx, requestingAccount, status)
     83 		if err != nil {
     84 			err = gtserror.Newf("error checking hometimelineability of status %s for account %s: %w", status.ID, list.AccountID, err)
     85 			return false, err
     86 		}
     87 
     88 		return timelineable, nil
     89 	}
     90 }
     91 
     92 // ListTimelineStatusPrepare returns a function that satisfies PrepareFunction for list timelines.
     93 func ListTimelineStatusPrepare(state *state.State, tc typeutils.TypeConverter) timeline.PrepareFunction {
     94 	return func(ctx context.Context, listID string, itemID string) (timeline.Preparable, error) {
     95 		status, err := state.DB.GetStatusByID(ctx, itemID)
     96 		if err != nil {
     97 			err = gtserror.Newf("error getting status with id %s: %w", itemID, err)
     98 			return nil, err
     99 		}
    100 
    101 		list, err := state.DB.GetListByID(ctx, listID)
    102 		if err != nil {
    103 			err = gtserror.Newf("error getting list with id %s: %w", listID, err)
    104 			return nil, err
    105 		}
    106 
    107 		requestingAccount, err := state.DB.GetAccountByID(ctx, list.AccountID)
    108 		if err != nil {
    109 			err = gtserror.Newf("error getting account with id %s: %w", list.AccountID, err)
    110 			return nil, err
    111 		}
    112 
    113 		return tc.StatusToAPIStatus(ctx, status, requestingAccount)
    114 	}
    115 }
    116 
    117 func (p *Processor) ListTimelineGet(ctx context.Context, authed *oauth.Auth, listID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
    118 	// Ensure list exists + is owned by this account.
    119 	list, err := p.state.DB.GetListByID(ctx, listID)
    120 	if err != nil {
    121 		if errors.Is(err, db.ErrNoEntries) {
    122 			return nil, gtserror.NewErrorNotFound(err)
    123 		}
    124 		return nil, gtserror.NewErrorInternalError(err)
    125 	}
    126 
    127 	if list.AccountID != authed.Account.ID {
    128 		err = gtserror.Newf("list with id %s does not belong to account %s", list.ID, authed.Account.ID)
    129 		return nil, gtserror.NewErrorNotFound(err)
    130 	}
    131 
    132 	statuses, err := p.state.Timelines.List.GetTimeline(ctx, listID, maxID, sinceID, minID, limit, false)
    133 	if err != nil && !errors.Is(err, db.ErrNoEntries) {
    134 		err = gtserror.Newf("error getting statuses: %w", err)
    135 		return nil, gtserror.NewErrorInternalError(err)
    136 	}
    137 
    138 	count := len(statuses)
    139 	if count == 0 {
    140 		return util.EmptyPageableResponse(), nil
    141 	}
    142 
    143 	var (
    144 		items          = make([]interface{}, count)
    145 		nextMaxIDValue string
    146 		prevMinIDValue string
    147 	)
    148 
    149 	for i, item := range statuses {
    150 		if i == count-1 {
    151 			nextMaxIDValue = item.GetID()
    152 		}
    153 
    154 		if i == 0 {
    155 			prevMinIDValue = item.GetID()
    156 		}
    157 
    158 		items[i] = item
    159 	}
    160 
    161 	return util.PackagePageableResponse(util.PageableResponseParams{
    162 		Items:          items,
    163 		Path:           "api/v1/timelines/list/" + listID,
    164 		NextMaxIDValue: nextMaxIDValue,
    165 		PrevMinIDValue: prevMinIDValue,
    166 		Limit:          limit,
    167 	})
    168 }