mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Add List functionality (#1802)
* start working on lists * further list work * test list db functions nicely * more work on lists * peepoopeepoo * poke * start list timeline func * we're getting there lads * couldn't be me working on stuff... could it? * hook up handlers * fiddling * weeee * woah * screaming, pissing * fix streaming being a whiny baby * lint, small test fix, swagger * tidying up, testing * fucked! by the linter * move timelines to state like a boss * add timeline start to tests using state * invalidate lists
This commit is contained in:
@ -30,12 +30,14 @@ import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
)
|
||||
|
||||
// timelineAndNotifyStatus processes the given new status and inserts it into
|
||||
// the HOME timelines of accounts that follow the status author. It will also
|
||||
// handle notifications for any mentions attached to the account, and also
|
||||
// notifications for any local accounts that want a notif when this account posts.
|
||||
// the HOME and LIST timelines of accounts that follow the status author.
|
||||
//
|
||||
// It will also handle notifications for any mentions attached to the account, and
|
||||
// also notifications for any local accounts that want to know when this account posts.
|
||||
func (p *Processor) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error {
|
||||
// Ensure status fully populated; including account, mentions, etc.
|
||||
if err := p.state.DB.PopulateStatus(ctx, status); err != nil {
|
||||
@ -89,10 +91,43 @@ func (p *Processor) timelineAndNotifyStatusForFollowers(ctx context.Context, sta
|
||||
continue
|
||||
}
|
||||
|
||||
// Add status to each list that this follow
|
||||
// is included in, and stream it if applicable.
|
||||
listEntries, err := p.state.DB.GetListEntriesForFollowID(
|
||||
// We only need the list IDs.
|
||||
gtscontext.SetBarebones(ctx),
|
||||
follow.ID,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error list timelining status: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, listEntry := range listEntries {
|
||||
if _, err := p.timelineStatus(
|
||||
ctx,
|
||||
p.state.Timelines.List.IngestOne,
|
||||
listEntry.ListID, // list timelines are keyed by list ID
|
||||
follow.Account,
|
||||
status,
|
||||
stream.TimelineList+":"+listEntry.ListID, // key streamType to this specific list
|
||||
); err != nil {
|
||||
errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error list timelining status: %w", err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Add status to home timeline for this
|
||||
// follower, and stream it if applicable.
|
||||
if timelined, err := p.timelineStatusForAccount(ctx, follow.Account, status); err != nil {
|
||||
errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error timelining status: %w", err))
|
||||
if timelined, err := p.timelineStatus(
|
||||
ctx,
|
||||
p.state.Timelines.Home.IngestOne,
|
||||
follow.AccountID, // home timelines are keyed by account ID
|
||||
follow.Account,
|
||||
status,
|
||||
stream.TimelineHome,
|
||||
); err != nil {
|
||||
errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error home timelining status: %w", err))
|
||||
continue
|
||||
} else if !timelined {
|
||||
// Status wasn't added to home tomeline,
|
||||
@ -133,13 +168,21 @@ func (p *Processor) timelineAndNotifyStatusForFollowers(ctx context.Context, sta
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
// timelineStatusForAccount puts the given status in the HOME timeline
|
||||
// of the account with given accountID, if it's HomeTimelineable.
|
||||
// timelineStatus uses the provided ingest function to put the given
|
||||
// status in a timeline with the given ID, if it's timelineable.
|
||||
//
|
||||
// If the status was inserted into the home timeline of the given account,
|
||||
// true will be returned + it will also be streamed via websockets to the user.
|
||||
func (p *Processor) timelineStatusForAccount(ctx context.Context, account *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
|
||||
// If the status was inserted into the timeline, true will be returned
|
||||
// + it will also be streamed to the user using the given streamType.
|
||||
func (p *Processor) timelineStatus(
|
||||
ctx context.Context,
|
||||
ingest func(context.Context, string, timeline.Timelineable) (bool, error),
|
||||
timelineID string,
|
||||
account *gtsmodel.Account,
|
||||
status *gtsmodel.Status,
|
||||
streamType string,
|
||||
) (bool, error) {
|
||||
// Make sure the status is timelineable.
|
||||
// This works for both home and list timelines.
|
||||
if timelineable, err := p.filter.StatusHomeTimelineable(ctx, account, status); err != nil {
|
||||
err = fmt.Errorf("timelineStatusForAccount: error getting timelineability for status for timeline with id %s: %w", account.ID, err)
|
||||
return false, err
|
||||
@ -148,8 +191,8 @@ func (p *Processor) timelineStatusForAccount(ctx context.Context, account *gtsmo
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Insert status in the home timeline of account.
|
||||
if inserted, err := p.statusTimelines.IngestOne(ctx, account.ID, status); err != nil {
|
||||
// Ingest status into given timeline using provided function.
|
||||
if inserted, err := ingest(ctx, timelineID, status); err != nil {
|
||||
err = fmt.Errorf("timelineStatusForAccount: error ingesting status %s: %w", status.ID, err)
|
||||
return false, err
|
||||
} else if !inserted {
|
||||
@ -164,7 +207,7 @@ func (p *Processor) timelineStatusForAccount(ctx context.Context, account *gtsmo
|
||||
return true, err
|
||||
}
|
||||
|
||||
if err := p.stream.Update(apiStatus, account, stream.TimelineHome); err != nil {
|
||||
if err := p.stream.Update(apiStatus, account, []string{streamType}); err != nil {
|
||||
err = fmt.Errorf("timelineStatusForAccount: error streaming update for status %s: %w", status.ID, err)
|
||||
return true, err
|
||||
}
|
||||
@ -401,7 +444,7 @@ func (p *Processor) wipeStatus(ctx context.Context, statusToDelete *gtsmodel.Sta
|
||||
// deleteStatusFromTimelines completely removes the given status from all timelines.
|
||||
// It will also stream deletion of the status to all open streams.
|
||||
func (p *Processor) deleteStatusFromTimelines(ctx context.Context, status *gtsmodel.Status) error {
|
||||
if err := p.statusTimelines.WipeItemFromAllTimelines(ctx, status.ID); err != nil {
|
||||
if err := p.state.Timelines.Home.WipeItemFromAllTimelines(ctx, status.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user