diff options
author | 2023-05-25 10:37:38 +0200 | |
---|---|---|
committer | 2023-05-25 10:37:38 +0200 | |
commit | f5c004d67d4ed66b6c6df100afec47174aa14ae0 (patch) | |
tree | 45b72a6e90450d711e10571d844138186fe023c9 /internal/processing/list/updateentries.go | |
parent | [docs] local docs hacking howto (#1816) (diff) | |
download | gotosocial-f5c004d67d4ed66b6c6df100afec47174aa14ae0.tar.xz |
[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
Diffstat (limited to 'internal/processing/list/updateentries.go')
-rw-r--r-- | internal/processing/list/updateentries.go | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/internal/processing/list/updateentries.go b/internal/processing/list/updateentries.go new file mode 100644 index 000000000..6dcb951a7 --- /dev/null +++ b/internal/processing/list/updateentries.go @@ -0,0 +1,151 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 list + +import ( + "context" + "errors" + "fmt" + + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" +) + +// AddToList adds targetAccountIDs to the given list, if valid. +func (p *Processor) AddToList(ctx context.Context, account *gtsmodel.Account, listID string, targetAccountIDs []string) gtserror.WithCode { + // Ensure this list exists + account owns it. + list, errWithCode := p.getList(ctx, account.ID, listID) + if errWithCode != nil { + return errWithCode + } + + // Pre-assemble list of entries to add. We *could* add these + // one by one as we iterate through accountIDs, but according + // to the Mastodon API we should only add them all once we know + // they're all valid, no partial updates. + listEntries := make([]*gtsmodel.ListEntry, 0, len(targetAccountIDs)) + + // Check each targetAccountID is valid. + // - Follow must exist. + // - Follow must not already be in the given list. + for _, targetAccountID := range targetAccountIDs { + // Ensure follow exists. + follow, err := p.state.DB.GetFollow(ctx, account.ID, targetAccountID) + if err != nil { + if errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("you do not follow account %s", targetAccountID) + return gtserror.NewErrorNotFound(err, err.Error()) + } + return gtserror.NewErrorInternalError(err) + } + + // Ensure followID not already in list. + // This particular call to isInList will + // never error, so just check entryID. + entryID, _ := isInList( + list, + follow.ID, + func(listEntry *gtsmodel.ListEntry) (string, error) { + // Looking for the listEntry follow ID. + return listEntry.FollowID, nil + }, + ) + + // Empty entryID means entry with given + // followID wasn't found in the list. + if entryID != "" { + err = fmt.Errorf("account with id %s is already in list %s with entryID %s", targetAccountID, listID, entryID) + return gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + // Entry wasn't in the list, we can add it. + listEntries = append(listEntries, >smodel.ListEntry{ + ID: id.NewULID(), + ListID: listID, + FollowID: follow.ID, + }) + } + + // If we get to here we can assume all + // entries are valid, so try to add them. + if err := p.state.DB.PutListEntries(ctx, listEntries); err != nil { + if errors.Is(err, db.ErrAlreadyExists) { + err = fmt.Errorf("one or more errors inserting list entries: %w", err) + return gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + return gtserror.NewErrorInternalError(err) + } + + return nil +} + +// RemoveFromList removes targetAccountIDs from the given list, if valid. +func (p *Processor) RemoveFromList(ctx context.Context, account *gtsmodel.Account, listID string, targetAccountIDs []string) gtserror.WithCode { + // Ensure this list exists + account owns it. + list, errWithCode := p.getList(ctx, account.ID, listID) + if errWithCode != nil { + return errWithCode + } + + // For each targetAccountID, we want to check if + // a follow with that targetAccountID is in the + // given list. If it is in there, we want to remove + // it from the list. + for _, targetAccountID := range targetAccountIDs { + // Check if targetAccountID is + // on a follow in the list. + entryID, err := isInList( + list, + targetAccountID, + func(listEntry *gtsmodel.ListEntry) (string, error) { + // We need the follow so populate this + // entry, if it's not already populated. + if err := p.state.DB.PopulateListEntry(ctx, listEntry); err != nil { + return "", err + } + + // Looking for the list entry targetAccountID. + return listEntry.Follow.TargetAccountID, nil + }, + ) + + // Error may be returned here if there was an issue + // populating the list entry. We only return on proper + // DB errors, we can just skip no entry errors. + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("error checking if targetAccountID %s was in list %s: %w", targetAccountID, listID, err) + return gtserror.NewErrorInternalError(err) + } + + if entryID == "" { + // There was an errNoEntries or targetAccount + // wasn't in this list anyway, so we can skip it. + continue + } + + // TargetAccount was in the list, remove the entry. + if err := p.state.DB.DeleteListEntry(ctx, entryID); err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("error removing list entry %s from list %s: %w", entryID, listID, err) + return gtserror.NewErrorInternalError(err) + } + } + + return nil +} |