summaryrefslogtreecommitdiff
path: root/internal/processing/filters/v1
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processing/filters/v1')
-rw-r--r--internal/processing/filters/v1/convert.go38
-rw-r--r--internal/processing/filters/v1/create.go82
-rw-r--r--internal/processing/filters/v1/delete.go54
-rw-r--r--internal/processing/filters/v1/filters.go8
-rw-r--r--internal/processing/filters/v1/get.go61
-rw-r--r--internal/processing/filters/v1/update.go189
6 files changed, 207 insertions, 225 deletions
diff --git a/internal/processing/filters/v1/convert.go b/internal/processing/filters/v1/convert.go
deleted file mode 100644
index 417cf7b7d..000000000
--- a/internal/processing/filters/v1/convert.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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 v1
-
-import (
- "context"
- "fmt"
-
- apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
- "code.superseriousbusiness.org/gotosocial/internal/gtserror"
- "code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
-)
-
-// apiFilter is a shortcut to return the API v1 filter version of the given
-// filter keyword, or return an appropriate error if conversion fails.
-func (p *Processor) apiFilter(ctx context.Context, filterKeyword *gtsmodel.FilterKeyword) (*apimodel.FilterV1, gtserror.WithCode) {
- apiFilter, err := p.converter.FilterKeywordToAPIFilterV1(ctx, filterKeyword)
- if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting filter keyword to API v1 filter: %w", err))
- }
-
- return apiFilter, nil
-}
diff --git a/internal/processing/filters/v1/create.go b/internal/processing/filters/v1/create.go
index 24517dd7b..b2ec69442 100644
--- a/internal/processing/filters/v1/create.go
+++ b/internal/processing/filters/v1/create.go
@@ -20,7 +20,7 @@ package v1
import (
"context"
"errors"
- "fmt"
+ "net/http"
"time"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
@@ -28,68 +28,72 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/id"
+ "code.superseriousbusiness.org/gotosocial/internal/processing/filters/common"
+ "code.superseriousbusiness.org/gotosocial/internal/typeutils"
"code.superseriousbusiness.org/gotosocial/internal/util"
)
// Create a new filter and filter keyword for the given account, using the provided parameters.
// These params should have already been validated by the time they reach this function.
-func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.FilterCreateUpdateRequestV1) (*apimodel.FilterV1, gtserror.WithCode) {
+func (p *Processor) Create(ctx context.Context, requester *gtsmodel.Account, form *apimodel.FilterCreateUpdateRequestV1) (*apimodel.FilterV1, gtserror.WithCode) {
+ var errWithCode gtserror.WithCode
+
+ // Create new wrapping filter.
filter := &gtsmodel.Filter{
ID: id.NewULID(),
- AccountID: account.ID,
+ AccountID: requester.ID,
Title: form.Phrase,
- Action: gtsmodel.FilterActionWarn,
}
+
if *form.Irreversible {
+ // Irreversible = action hide.
filter.Action = gtsmodel.FilterActionHide
+ } else {
+ // Default action = action warn.
+ filter.Action = gtsmodel.FilterActionWarn
}
- if form.ExpiresIn != nil && *form.ExpiresIn != 0 {
- filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn))
+
+ // Check form for valid expiry and set on filter.
+ if form.ExpiresIn != nil && *form.ExpiresIn > 0 {
+ expiresIn := time.Duration(*form.ExpiresIn) * time.Second
+ filter.ExpiresAt = time.Now().Add(expiresIn)
}
- for _, context := range form.Context {
- switch context {
- case apimodel.FilterContextHome:
- filter.ContextHome = util.Ptr(true)
- case apimodel.FilterContextNotifications:
- filter.ContextNotifications = util.Ptr(true)
- case apimodel.FilterContextPublic:
- filter.ContextPublic = util.Ptr(true)
- case apimodel.FilterContextThread:
- filter.ContextThread = util.Ptr(true)
- case apimodel.FilterContextAccount:
- filter.ContextAccount = util.Ptr(true)
- default:
- return nil, gtserror.NewErrorUnprocessableEntity(
- fmt.Errorf("unsupported filter context '%s'", context),
- )
- }
+
+ // Parse contexts filter applies in from incoming request form data.
+ filter.Contexts, errWithCode = common.FromAPIContexts(form.Context)
+ if errWithCode != nil {
+ return nil, errWithCode
}
+ // Create new keyword attached to filter.
filterKeyword := &gtsmodel.FilterKeyword{
ID: id.NewULID(),
- AccountID: account.ID,
FilterID: filter.ID,
- Filter: filter,
Keyword: form.Phrase,
WholeWord: util.Ptr(util.PtrOrValue(form.WholeWord, false)),
}
- filter.Keywords = []*gtsmodel.FilterKeyword{filterKeyword}
- if err := p.state.DB.PutFilter(ctx, filter); err != nil {
- if errors.Is(err, db.ErrAlreadyExists) {
- err = errors.New("you already have a filter with this title")
- return nil, gtserror.NewErrorConflict(err, err.Error())
- }
- return nil, gtserror.NewErrorInternalError(err)
- }
+ // Attach the new keyword to filter before insert.
+ filter.Keywords = append(filter.Keywords, filterKeyword)
+ filter.KeywordIDs = append(filter.KeywordIDs, filterKeyword.ID)
- apiFilter, errWithCode := p.apiFilter(ctx, filterKeyword)
- if errWithCode != nil {
- return nil, errWithCode
+ // Insert newly created filter into the database.
+ switch err := p.state.DB.PutFilter(ctx, filter); {
+ case err == nil:
+ // no issue
+
+ case errors.Is(err, db.ErrAlreadyExists):
+ const text = "duplicate title"
+ return nil, gtserror.NewWithCode(http.StatusConflict, text)
+
+ default:
+ err := gtserror.Newf("error inserting filter: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- // Send a filters changed event.
- p.stream.FiltersChanged(ctx, account)
+ // Stream a filters changed event to WS.
+ p.stream.FiltersChanged(ctx, requester)
- return apiFilter, nil
+ // Return as converted frontend filter keyword model.
+ return typeutils.FilterKeywordToAPIFilterV1(filter, filterKeyword), nil
}
diff --git a/internal/processing/filters/v1/delete.go b/internal/processing/filters/v1/delete.go
index 6a081ff04..cab8b185d 100644
--- a/internal/processing/filters/v1/delete.go
+++ b/internal/processing/filters/v1/delete.go
@@ -19,52 +19,52 @@ package v1
import (
"context"
- "errors"
+ "slices"
- "code.superseriousbusiness.org/gotosocial/internal/db"
- "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
)
-// Delete an existing filter keyword and (if empty afterwards) filter for the given account.
+// Delete an existing filter keyword and (if empty
+// afterwards) filter for the given account.
func (p *Processor) Delete(
ctx context.Context,
- account *gtsmodel.Account,
+ requester *gtsmodel.Account,
filterKeywordID string,
) gtserror.WithCode {
- // Get enough of the filter keyword that we can look up its filter ID.
- filterKeyword, err := p.state.DB.GetFilterKeywordByID(gtscontext.SetBarebones(ctx), filterKeywordID)
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return gtserror.NewErrorNotFound(err)
- }
- return gtserror.NewErrorInternalError(err)
- }
- if filterKeyword.AccountID != account.ID {
- return gtserror.NewErrorNotFound(nil)
- }
-
- // Get the filter for this keyword.
- filter, err := p.state.DB.GetFilterByID(ctx, filterKeyword.FilterID)
- if err != nil {
- return gtserror.NewErrorNotFound(err)
+ // Get the filter keyword with given ID, and associated filter, also checking ownership.
+ filterKeyword, filter, errWithCode := p.c.GetFilterKeyword(ctx, requester, filterKeywordID)
+ if errWithCode != nil {
+ return errWithCode
}
if len(filter.Keywords) > 1 || len(filter.Statuses) > 0 {
- // The filter has other keywords or statuses. Delete only the requested filter keyword.
- if err := p.state.DB.DeleteFilterKeywordByID(ctx, filterKeyword.ID); err != nil {
+ // The filter has other keywords or statuses, just delete the one filter keyword.
+ if err := p.state.DB.DeleteFilterKeywordsByIDs(ctx, filterKeyword.ID); err != nil {
+ err := gtserror.Newf("error deleting filter keyword: %w", err)
+ return gtserror.NewErrorInternalError(err)
+ }
+
+ // Delete this filter keyword from the slice of IDs attached to filter.
+ filter.KeywordIDs = slices.DeleteFunc(filter.KeywordIDs, func(id string) bool {
+ return filterKeyword.ID == id
+ })
+
+ // Update filter in the database now the keyword has been unattached.
+ if err := p.state.DB.UpdateFilter(ctx, filter, "keywords"); err != nil {
+ err := gtserror.Newf("error updating filter: %w", err)
return gtserror.NewErrorInternalError(err)
}
} else {
- // Delete the entire filter.
- if err := p.state.DB.DeleteFilterByID(ctx, filter.ID); err != nil {
+ // Delete the filter and this keyword that is attached to it.
+ if err := p.state.DB.DeleteFilter(ctx, filter); err != nil {
+ err := gtserror.Newf("error deleting filter: %w", err)
return gtserror.NewErrorInternalError(err)
}
}
- // Send a filters changed event.
- p.stream.FiltersChanged(ctx, account)
+ // Stream a filters changed event to WS.
+ p.stream.FiltersChanged(ctx, requester)
return nil
}
diff --git a/internal/processing/filters/v1/filters.go b/internal/processing/filters/v1/filters.go
index 89b509912..bcbbd70c0 100644
--- a/internal/processing/filters/v1/filters.go
+++ b/internal/processing/filters/v1/filters.go
@@ -18,19 +18,25 @@
package v1
import (
+ "code.superseriousbusiness.org/gotosocial/internal/processing/filters/common"
"code.superseriousbusiness.org/gotosocial/internal/processing/stream"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
)
type Processor struct {
+ // embedded common logic
+ c *common.Processor
+
state *state.State
converter *typeutils.Converter
stream *stream.Processor
}
-func New(state *state.State, converter *typeutils.Converter, stream *stream.Processor) Processor {
+func New(state *state.State, converter *typeutils.Converter, common *common.Processor, stream *stream.Processor) Processor {
return Processor{
+ c: common,
+
state: state,
converter: converter,
stream: stream,
diff --git a/internal/processing/filters/v1/get.go b/internal/processing/filters/v1/get.go
index ad35e6272..bdde123e9 100644
--- a/internal/processing/filters/v1/get.go
+++ b/internal/processing/filters/v1/get.go
@@ -25,47 +25,58 @@ import (
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/db"
+ "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
+ "code.superseriousbusiness.org/gotosocial/internal/typeutils"
)
// Get looks up a filter keyword by ID and returns it as a v1 filter.
-func (p *Processor) Get(ctx context.Context, account *gtsmodel.Account, filterKeywordID string) (*apimodel.FilterV1, gtserror.WithCode) {
- filterKeyword, err := p.state.DB.GetFilterKeywordByID(ctx, filterKeywordID)
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return nil, gtserror.NewErrorNotFound(err)
- }
- return nil, gtserror.NewErrorInternalError(err)
- }
- if filterKeyword.AccountID != account.ID {
- return nil, gtserror.NewErrorNotFound(nil)
+func (p *Processor) Get(ctx context.Context, requester *gtsmodel.Account, filterKeywordID string) (*apimodel.FilterV1, gtserror.WithCode) {
+ filterKeyword, filter, errWithCode := p.c.GetFilterKeyword(ctx, requester, filterKeywordID)
+ if errWithCode != nil {
+ return nil, errWithCode
}
-
- return p.apiFilter(ctx, filterKeyword)
+ return typeutils.FilterKeywordToAPIFilterV1(filter, filterKeyword), nil
}
// GetAll looks up all filter keywords for the current account and returns them as v1 filters.
-func (p *Processor) GetAll(ctx context.Context, account *gtsmodel.Account) ([]*apimodel.FilterV1, gtserror.WithCode) {
- filters, err := p.state.DB.GetFilterKeywordsForAccountID(
- ctx,
- account.ID,
+func (p *Processor) GetAll(ctx context.Context, requester *gtsmodel.Account) ([]*apimodel.FilterV1, gtserror.WithCode) {
+ var totalKeywords int
+
+ // Get a list of all filters owned by this account,
+ // (without any sub-models attached, done later).
+ filters, err := p.state.DB.GetFiltersByAccountID(
+ gtscontext.SetBarebones(ctx),
+ requester.ID,
)
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return nil, nil
- }
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("error getting filters: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
- apiFilters := make([]*apimodel.FilterV1, 0, len(filters))
+ // Get a total count of all expected
+ // keywords for slice preallocation.
for _, filter := range filters {
- apiFilter, errWithCode := p.apiFilter(ctx, filter)
- if errWithCode != nil {
- return nil, errWithCode
+ totalKeywords += len(filter.KeywordIDs)
+ }
+
+ // Create a slice to store converted V1 frontend models.
+ apiFilters := make([]*apimodel.FilterV1, 0, totalKeywords)
+
+ for _, filter := range filters {
+ // For each of the fetched filters, fetch all of their associated keywords.
+ keywords, err := p.state.DB.GetFilterKeywordsByIDs(ctx, filter.KeywordIDs)
+ if err != nil {
+ err := gtserror.Newf("error getting filter keywords: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- apiFilters = append(apiFilters, apiFilter)
+ // Convert each keyword to frontend.
+ for _, keyword := range keywords {
+ apiFilter := typeutils.FilterKeywordToAPIFilterV1(filter, keyword)
+ apiFilters = append(apiFilters, apiFilter)
+ }
}
// Sort them by ID so that they're in a stable order.
diff --git a/internal/processing/filters/v1/update.go b/internal/processing/filters/v1/update.go
index 8b50c3fcf..7e25e6fde 100644
--- a/internal/processing/filters/v1/update.go
+++ b/internal/processing/filters/v1/update.go
@@ -21,77 +21,59 @@ import (
"context"
"errors"
"fmt"
+ "net/http"
"strings"
"time"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/db"
- "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
- "code.superseriousbusiness.org/gotosocial/internal/util"
+ "code.superseriousbusiness.org/gotosocial/internal/processing/filters/common"
+ "code.superseriousbusiness.org/gotosocial/internal/typeutils"
)
// Update an existing filter and filter keyword for the given account, using the provided parameters.
// These params should have already been validated by the time they reach this function.
func (p *Processor) Update(
ctx context.Context,
- account *gtsmodel.Account,
+ requester *gtsmodel.Account,
filterKeywordID string,
form *apimodel.FilterCreateUpdateRequestV1,
) (*apimodel.FilterV1, gtserror.WithCode) {
- // Get enough of the filter keyword that we can look up its filter ID.
- filterKeyword, err := p.state.DB.GetFilterKeywordByID(gtscontext.SetBarebones(ctx), filterKeywordID)
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return nil, gtserror.NewErrorNotFound(err)
- }
- return nil, gtserror.NewErrorInternalError(err)
- }
- if filterKeyword.AccountID != account.ID {
- return nil, gtserror.NewErrorNotFound(nil)
+ // Get the filter keyword with given ID, and associated filter, also checking ownership.
+ filterKeyword, filter, errWithCode := p.c.GetFilterKeyword(ctx, requester, filterKeywordID)
+ if errWithCode != nil {
+ return nil, errWithCode
}
- // Get the filter for this keyword.
- filter, err := p.state.DB.GetFilterByID(ctx, filterKeyword.FilterID)
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- return nil, gtserror.NewErrorNotFound(err)
- }
- return nil, gtserror.NewErrorInternalError(err)
- }
+ var title string
+ var action gtsmodel.FilterAction
+ var contexts gtsmodel.FilterContexts
+ var expiresAt time.Time
+ var wholeword bool
+
+ // Get filter title.
+ title = form.Phrase
- title := form.Phrase
- action := gtsmodel.FilterActionWarn
if *form.Irreversible {
+ // Irreversible = action hide.
action = gtsmodel.FilterActionHide
+ } else {
+ // Default action = action warn.
+ action = gtsmodel.FilterActionWarn
}
- expiresAt := time.Time{}
- if form.ExpiresIn != nil && *form.ExpiresIn != 0 {
- expiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn))
+
+ // Check form for valid expiry and set on filter.
+ if form.ExpiresIn != nil && *form.ExpiresIn > 0 {
+ expiresIn := time.Duration(*form.ExpiresIn) * time.Second
+ expiresAt = time.Now().Add(expiresIn)
}
- contextHome := false
- contextNotifications := false
- contextPublic := false
- contextThread := false
- contextAccount := false
- for _, context := range form.Context {
- switch context {
- case apimodel.FilterContextHome:
- contextHome = true
- case apimodel.FilterContextNotifications:
- contextNotifications = true
- case apimodel.FilterContextPublic:
- contextPublic = true
- case apimodel.FilterContextThread:
- contextThread = true
- case apimodel.FilterContextAccount:
- contextAccount = true
- default:
- return nil, gtserror.NewErrorUnprocessableEntity(
- fmt.Errorf("unsupported filter context '%s'", context),
- )
- }
+
+ // Parse contexts filter applies in from incoming form data.
+ contexts, errWithCode = common.FromAPIContexts(form.Context)
+ if errWithCode != nil {
+ return nil, errWithCode
}
// v1 filter APIs can't change certain fields for a filter with multiple keywords or any statuses,
@@ -108,11 +90,7 @@ func (p *Processor) Update(
if expiresAt != filter.ExpiresAt {
forbiddenFields = append(forbiddenFields, "expires_in")
}
- if contextHome != util.PtrOrValue(filter.ContextHome, false) ||
- contextNotifications != util.PtrOrValue(filter.ContextNotifications, false) ||
- contextPublic != util.PtrOrValue(filter.ContextPublic, false) ||
- contextThread != util.PtrOrValue(filter.ContextThread, false) ||
- contextAccount != util.PtrOrValue(filter.ContextAccount, false) {
+ if contexts != filter.Contexts {
forbiddenFields = append(forbiddenFields, "context")
}
if len(forbiddenFields) > 0 {
@@ -122,54 +100,75 @@ func (p *Processor) Update(
}
}
- // Now that we've checked that the changes are legal, apply them to the filter and keyword.
- filter.Title = title
- filter.Action = action
- filter.ExpiresAt = expiresAt
- filter.ContextHome = &contextHome
- filter.ContextNotifications = &contextNotifications
- filter.ContextPublic = &contextPublic
- filter.ContextThread = &contextThread
- filter.ContextAccount = &contextAccount
- filterKeyword.Keyword = form.Phrase
- filterKeyword.WholeWord = util.Ptr(util.PtrOrValue(form.WholeWord, false))
-
- // We only want to update the relevant filter keyword.
- filter.Keywords = []*gtsmodel.FilterKeyword{filterKeyword}
- filter.Statuses = nil
- filterKeyword.Filter = filter
-
- filterColumns := []string{
- "title",
- "action",
- "expires_at",
- "context_home",
- "context_notifications",
- "context_public",
- "context_thread",
- "context_account",
+ // Filter columns that
+ // we're going to update.
+ var filterCols []string
+ var keywordCols []string
+
+ // Check for changed filter title / filter keyword phrase.
+ if title != filter.Title || title != filterKeyword.Keyword {
+ keywordCols = append(keywordCols, "keyword")
+ filterCols = append(filterCols, "title")
+ filterKeyword.Keyword = title
+ filter.Title = title
}
- filterKeywordColumns := [][]string{
- {
- "keyword",
- "whole_word",
- },
+
+ // Check for changed action.
+ if action != filter.Action {
+ filterCols = append(filterCols, "action")
+ filter.Action = action
}
- if err := p.state.DB.UpdateFilter(ctx, filter, filterColumns, filterKeywordColumns, nil, nil); err != nil {
- if errors.Is(err, db.ErrAlreadyExists) {
- err = errors.New("you already have a filter with this title")
- return nil, gtserror.NewErrorConflict(err, err.Error())
- }
+
+ // Check for changed filter expiry time.
+ if !expiresAt.Equal(filter.ExpiresAt) {
+ filterCols = append(filterCols, "expires_at")
+ filter.ExpiresAt = expiresAt
+ }
+
+ // Check for changed filter context.
+ if contexts != filter.Contexts {
+ filterCols = append(filterCols, "contexts")
+ filter.Contexts = contexts
+ }
+
+ // Check for changed wholeword flag.
+ if form.WholeWord != nil &&
+ *form.WholeWord != *filterKeyword.WholeWord {
+ keywordCols = append(keywordCols, "whole_word")
+ filterKeyword.WholeWord = &wholeword
+ }
+
+ // Update filter keyword model in the database with determined changed cols.
+ switch err := p.state.DB.UpdateFilterKeyword(ctx, filterKeyword, keywordCols...); {
+ case err == nil:
+ // no issue
+
+ case errors.Is(err, db.ErrAlreadyExists):
+ const text = "duplicate keyword"
+ return nil, gtserror.NewWithCode(http.StatusConflict, text)
+
+ default:
+ err := gtserror.Newf("error updating filter: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
- apiFilter, errWithCode := p.apiFilter(ctx, filterKeyword)
- if errWithCode != nil {
- return nil, errWithCode
+ // Update filter model in the database with determined changed cols.
+ switch err := p.state.DB.UpdateFilter(ctx, filter, filterCols...); {
+ case err == nil:
+ // no issue
+
+ case errors.Is(err, db.ErrAlreadyExists):
+ const text = "duplicate title"
+ return nil, gtserror.NewWithCode(http.StatusConflict, text)
+
+ default:
+ err := gtserror.Newf("error updating filter: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- // Send a filters changed event.
- p.stream.FiltersChanged(ctx, account)
+ // Stream a filters changed event to WS.
+ p.stream.FiltersChanged(ctx, requester)
- return apiFilter, nil
+ // Return as converted frontend filter keyword model.
+ return typeutils.FilterKeywordToAPIFilterV1(filter, filterKeyword), nil
}