diff options
Diffstat (limited to 'internal/processing')
-rw-r--r-- | internal/processing/account/interactionpolicies.go | 208 | ||||
-rw-r--r-- | internal/processing/status/create.go | 85 | ||||
-rw-r--r-- | internal/processing/stream/statusupdate_test.go | 22 |
3 files changed, 301 insertions, 14 deletions
diff --git a/internal/processing/account/interactionpolicies.go b/internal/processing/account/interactionpolicies.go new file mode 100644 index 000000000..e02b43e9e --- /dev/null +++ b/internal/processing/account/interactionpolicies.go @@ -0,0 +1,208 @@ +// 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 account + +import ( + "cmp" + "context" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" +) + +func (p *Processor) DefaultInteractionPoliciesGet( + ctx context.Context, + requester *gtsmodel.Account, +) (*apimodel.DefaultPolicies, gtserror.WithCode) { + // Ensure account settings populated. + if err := p.populateAccountSettings(ctx, requester); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + // Take set "direct" policy + // or global default. + direct := cmp.Or( + requester.Settings.InteractionPolicyDirect, + gtsmodel.DefaultInteractionPolicyDirect(), + ) + + directAPI, err := p.converter.InteractionPolicyToAPIInteractionPolicy(ctx, direct, nil, nil) + if err != nil { + err := gtserror.Newf("error converting interaction policy direct: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + // Take set "private" policy + // or global default. + private := cmp.Or( + requester.Settings.InteractionPolicyFollowersOnly, + gtsmodel.DefaultInteractionPolicyFollowersOnly(), + ) + + privateAPI, err := p.converter.InteractionPolicyToAPIInteractionPolicy(ctx, private, nil, nil) + if err != nil { + err := gtserror.Newf("error converting interaction policy private: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + // Take set "unlisted" policy + // or global default. + unlisted := cmp.Or( + requester.Settings.InteractionPolicyUnlocked, + gtsmodel.DefaultInteractionPolicyUnlocked(), + ) + + unlistedAPI, err := p.converter.InteractionPolicyToAPIInteractionPolicy(ctx, unlisted, nil, nil) + if err != nil { + err := gtserror.Newf("error converting interaction policy unlisted: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + // Take set "public" policy + // or global default. + public := cmp.Or( + requester.Settings.InteractionPolicyPublic, + gtsmodel.DefaultInteractionPolicyPublic(), + ) + + publicAPI, err := p.converter.InteractionPolicyToAPIInteractionPolicy(ctx, public, nil, nil) + if err != nil { + err := gtserror.Newf("error converting interaction policy public: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + return &apimodel.DefaultPolicies{ + Direct: *directAPI, + Private: *privateAPI, + Unlisted: *unlistedAPI, + Public: *publicAPI, + }, nil +} + +func (p *Processor) DefaultInteractionPoliciesUpdate( + ctx context.Context, + requester *gtsmodel.Account, + form *apimodel.UpdateInteractionPoliciesRequest, +) (*apimodel.DefaultPolicies, gtserror.WithCode) { + // Lock on this account as we're modifying its Settings. + unlock := p.state.ProcessingLocks.Lock(requester.URI) + defer unlock() + + // Ensure account settings populated. + if err := p.populateAccountSettings(ctx, requester); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + if form.Direct == nil { + // Unset/return to global default. + requester.Settings.InteractionPolicyDirect = nil + } else { + policy, err := typeutils.APIInteractionPolicyToInteractionPolicy( + form.Direct, + apimodel.VisibilityDirect, + ) + if err != nil { + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + // Set new default policy. + requester.Settings.InteractionPolicyDirect = policy + } + + if form.Private == nil { + // Unset/return to global default. + requester.Settings.InteractionPolicyFollowersOnly = nil + } else { + policy, err := typeutils.APIInteractionPolicyToInteractionPolicy( + form.Private, + apimodel.VisibilityPrivate, + ) + if err != nil { + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + // Set new default policy. + requester.Settings.InteractionPolicyFollowersOnly = policy + } + + if form.Unlisted == nil { + // Unset/return to global default. + requester.Settings.InteractionPolicyUnlocked = nil + } else { + policy, err := typeutils.APIInteractionPolicyToInteractionPolicy( + form.Unlisted, + apimodel.VisibilityUnlisted, + ) + if err != nil { + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + // Set new default policy. + requester.Settings.InteractionPolicyUnlocked = policy + } + + if form.Public == nil { + // Unset/return to global default. + requester.Settings.InteractionPolicyPublic = nil + } else { + policy, err := typeutils.APIInteractionPolicyToInteractionPolicy( + form.Public, + apimodel.VisibilityPublic, + ) + if err != nil { + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + // Set new default policy. + requester.Settings.InteractionPolicyPublic = policy + } + + if err := p.state.DB.UpdateAccountSettings(ctx, requester.Settings); err != nil { + err := gtserror.Newf("db error updating setttings: %w", err) + return nil, gtserror.NewErrorInternalError(err, err.Error()) + } + + return p.DefaultInteractionPoliciesGet(ctx, requester) +} + +// populateAccountSettings just ensures that +// Settings is populated on the given account. +func (p *Processor) populateAccountSettings( + ctx context.Context, + acct *gtsmodel.Account, +) error { + if acct.Settings != nil { + // Already populated. + return nil + } + + // Not populated, + // get from db. + var err error + acct.Settings, err = p.state.DB.GetAccountSettings(ctx, acct.ID) + if err != nil { + return gtserror.Newf( + "db error getting settings for account %s: %w", + acct.ID, err, + ) + } + + return nil +} diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index 8898181ae..a5978a999 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -121,6 +121,12 @@ func (p *Processor) Create( return nil, gtserror.NewErrorInternalError(err) } + // Process policy AFTER visibility as it + // relies on status.Visibility being set. + if err := processInteractionPolicy(form, requester.Settings, status); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + if err := processLanguage(form, requester.Settings.Language, status); err != nil { return nil, gtserror.NewErrorInternalError(err) } @@ -281,26 +287,79 @@ func (p *Processor) processMediaIDs(ctx context.Context, form *apimodel.Advanced return nil } -func processVisibility(form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error { - // by default all flags are set to true - federated := true - - // If visibility isn't set on the form, then just take the account default. - // If that's also not set, take the default for the whole instance. - var vis gtsmodel.Visibility +func processVisibility( + form *apimodel.AdvancedStatusCreateForm, + accountDefaultVis gtsmodel.Visibility, + status *gtsmodel.Status, +) error { switch { + // Visibility set on form, use that. case form.Visibility != "": - vis = typeutils.APIVisToVis(form.Visibility) + status.Visibility = typeutils.APIVisToVis(form.Visibility) + + // Fall back to account default. case accountDefaultVis != "": - vis = accountDefaultVis + status.Visibility = accountDefaultVis + + // What? Fall back to global default. default: - vis = gtsmodel.VisibilityDefault + status.Visibility = gtsmodel.VisibilityDefault } - // Todo: sort out likeable/replyable/boostable in next PR. - - status.Visibility = vis + // Set federated flag to form value + // if provided, or default to true. + federated := util.PtrValueOr(form.Federated, true) status.Federated = &federated + + return nil +} + +func processInteractionPolicy( + _ *apimodel.AdvancedStatusCreateForm, + settings *gtsmodel.AccountSettings, + status *gtsmodel.Status, +) error { + // TODO: parse policy for this + // status from form and prefer this. + + // TODO: prevent scope widening by + // limiting interaction policy if + // inReplyTo status has a stricter + // interaction policy than this one. + + switch status.Visibility { + + case gtsmodel.VisibilityPublic: + // Take account's default "public" policy if set. + if p := settings.InteractionPolicyPublic; p != nil { + status.InteractionPolicy = p + } + + case gtsmodel.VisibilityUnlocked: + // Take account's default "unlisted" policy if set. + if p := settings.InteractionPolicyUnlocked; p != nil { + status.InteractionPolicy = p + } + + case gtsmodel.VisibilityFollowersOnly, + gtsmodel.VisibilityMutualsOnly: + // Take account's default followers-only policy if set. + // TODO: separate policy for mutuals-only vis. + if p := settings.InteractionPolicyFollowersOnly; p != nil { + status.InteractionPolicy = p + } + + case gtsmodel.VisibilityDirect: + // Take account's default direct policy if set. + if p := settings.InteractionPolicyDirect; p != nil { + status.InteractionPolicy = p + } + } + + // If no policy set by now, status interaction + // policy will be stored as nil, which just means + // "fall back to global default policy". We avoid + // setting it explicitly to save space. return nil } diff --git a/internal/processing/stream/statusupdate_test.go b/internal/processing/stream/statusupdate_test.go index 359212ee6..38be9ea5e 100644 --- a/internal/processing/stream/statusupdate_test.go +++ b/internal/processing/stream/statusupdate_test.go @@ -129,7 +129,27 @@ func (suite *StatusUpdateTestSuite) TestStreamNotification() { "tags": [], "emojis": [], "card": null, - "poll": null + "poll": null, + "interaction_policy": { + "can_favourite": { + "always": [ + "public" + ], + "with_approval": [] + }, + "can_reply": { + "always": [ + "public" + ], + "with_approval": [] + }, + "can_reblog": { + "always": [ + "public" + ], + "with_approval": [] + } + } }`, dst.String()) suite.Equal(msg.Event, "status.update") } |