diff options
Diffstat (limited to 'internal/processing')
-rw-r--r-- | internal/processing/account/rss.go | 2 | ||||
-rw-r--r-- | internal/processing/account/statuses.go | 14 | ||||
-rw-r--r-- | internal/processing/account/update.go | 356 |
3 files changed, 237 insertions, 135 deletions
diff --git a/internal/processing/account/rss.go b/internal/processing/account/rss.go index 60f93b012..22ba0fe42 100644 --- a/internal/processing/account/rss.go +++ b/internal/processing/account/rss.go @@ -116,7 +116,7 @@ func (p *Processor) GetRSSFeedForUsername(ctx context.Context, username string) feed.Updated = lastPostAt // Retrieve latest statuses as they'd be shown on the web view of the account profile. - statuses, err := p.state.DB.GetAccountWebStatuses(ctx, account.ID, rssFeedLength, "") + statuses, err := p.state.DB.GetAccountWebStatuses(ctx, account, rssFeedLength, "") if err != nil && !errors.Is(err, db.ErrNoEntries) { err = fmt.Errorf("db error getting account web statuses: %w", err) return "", gtserror.NewErrorInternalError(err) diff --git a/internal/processing/account/statuses.go b/internal/processing/account/statuses.go index 2bab812e3..8029a460b 100644 --- a/internal/processing/account/statuses.go +++ b/internal/processing/account/statuses.go @@ -159,7 +159,7 @@ func (p *Processor) WebStatusesGet( return nil, gtserror.NewErrorNotFound(err) } - statuses, err := p.state.DB.GetAccountWebStatuses(ctx, targetAccountID, 10, maxID) + statuses, err := p.state.DB.GetAccountWebStatuses(ctx, account, 10, maxID) if err != nil && !errors.Is(err, db.ErrNoEntries) { return nil, gtserror.NewErrorInternalError(err) } @@ -206,9 +206,15 @@ func (p *Processor) WebStatusesGetPinned( webStatuses := make([]*apimodel.WebStatus, 0, len(statuses)) for _, status := range statuses { - if status.Visibility != gtsmodel.VisibilityPublic { - // Skip non-public - // pinned status. + // Ensure visible via the web. + visible, err := p.visFilter.StatusVisible(ctx, nil, status) + if err != nil { + log.Errorf(ctx, "error checking status visibility: %v", err) + continue + } + + if !visible { + // Don't serve. continue } diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go index fda871bd5..58e52a992 100644 --- a/internal/processing/account/update.go +++ b/internal/processing/account/update.go @@ -54,21 +54,44 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form log.Errorf(ctx, "error(s) populating account, will continue: %s", err) } + var ( + // Indicates that the account's + // note, display name, and/or fields + // have changed, and so emojis should + // be re-parsed and updated as well. + textChanged bool + + // DB columns on the account + // that need to be updated. + acctColumns []string + + // DB columns on the settings + // that need to be updated. + settingsColumns []string + ) + + // Account flags. + if form.Discoverable != nil { account.Discoverable = form.Discoverable + acctColumns = append(acctColumns, "discoverable") } if form.Bot != nil { account.Bot = form.Bot + acctColumns = append(acctColumns, "bot") } - // Via the process of updating the account, - // it is possible that the emojis used by - // that account in note/display name/fields - // may change; we need to keep track of this. - var emojisChanged bool + if form.Locked != nil { + account.Locked = form.Locked + acctColumns = append(acctColumns, "locked") + } if form.DisplayName != nil { + // Display name text + // is changing. + textChanged = true + displayName := *form.DisplayName if err := validate.DisplayName(displayName); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) @@ -76,137 +99,54 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form // Parse new display name (always from plaintext). account.DisplayName = text.SanitizeToPlaintext(displayName) - - // If display name has changed, account emojis may have also changed. - emojisChanged = true + acctColumns = append(acctColumns, "display_name") } if form.Note != nil { + // Note text is changing. + textChanged = true + note := *form.Note if err := validate.Note(note); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } - // Store raw version of the note for now, - // we'll process the proper version later. + // Store raw version of note + // for now, we'll process + // the proper version later. account.NoteRaw = note - - // If note has changed, account emojis may have also changed. - emojisChanged = true + acctColumns = append(acctColumns, []string{ + "note", + "note_raw", + }...) } if form.FieldsAttributes != nil { - var ( - fieldsAttributes = *form.FieldsAttributes - fieldsLen = len(fieldsAttributes) - fieldsRaw = make([]*gtsmodel.Field, 0, fieldsLen) - ) - - for _, updateField := range fieldsAttributes { - if updateField.Name == nil || updateField.Value == nil { - continue - } - - var ( - name string = *updateField.Name - value string = *updateField.Value - ) + // Field text is changing. + textChanged = true - if name == "" || value == "" { - continue - } - - // Sanitize raw field values. - fieldRaw := >smodel.Field{ - Name: text.SanitizeToPlaintext(name), - Value: text.SanitizeToPlaintext(value), - } - fieldsRaw = append(fieldsRaw, fieldRaw) - } - - // Check length of parsed raw fields. - if err := validate.ProfileFields(fieldsRaw); err != nil { - return nil, gtserror.NewErrorBadRequest(err, err.Error()) + if err := p.updateFields( + account, + *form.FieldsAttributes, + ); err != nil { + return nil, err } - - // OK, new raw fields are valid. - account.FieldsRaw = fieldsRaw - account.Fields = make([]*gtsmodel.Field, 0, fieldsLen) // process these in a sec - - // If fields have changed, account emojis may also have changed. - emojisChanged = true + acctColumns = append(acctColumns, []string{ + "fields", + "fields_raw", + }...) } - if emojisChanged { - // Use map to deduplicate emojis by their ID. - emojis := make(map[string]*gtsmodel.Emoji) - - // Retrieve display name emojis. - for _, emoji := range p.formatter.FromPlainEmojiOnly( - ctx, - p.parseMention, - account.ID, - "", - account.DisplayName, - ).Emojis { - emojis[emoji.ID] = emoji - } - - // Format + set note according to user prefs. - f := p.selectNoteFormatter(account.Settings.StatusContentType) - formatNoteResult := f(ctx, p.parseMention, account.ID, "", account.NoteRaw) - account.Note = formatNoteResult.HTML - - // Retrieve note emojis. - for _, emoji := range formatNoteResult.Emojis { - emojis[emoji.ID] = emoji - } - - // Process the raw fields we stored earlier. - account.Fields = make([]*gtsmodel.Field, 0, len(account.FieldsRaw)) - for _, fieldRaw := range account.FieldsRaw { - field := >smodel.Field{} - - // Name stays plain, but we still need to - // see if there are any emojis set in it. - field.Name = fieldRaw.Name - for _, emoji := range p.formatter.FromPlainEmojiOnly( - ctx, - p.parseMention, - account.ID, - "", - fieldRaw.Name, - ).Emojis { - emojis[emoji.ID] = emoji - } - - // Value can be HTML, but we don't want - // to wrap the result in <p> tags. - fieldFormatValueResult := p.formatter.FromPlainNoParagraph(ctx, p.parseMention, account.ID, "", fieldRaw.Value) - field.Value = fieldFormatValueResult.HTML - - // Retrieve field emojis. - for _, emoji := range fieldFormatValueResult.Emojis { - emojis[emoji.ID] = emoji - } - - // We're done, append the shiny new field. - account.Fields = append(account.Fields, field) - } - - emojisCount := len(emojis) - account.Emojis = make([]*gtsmodel.Emoji, 0, emojisCount) - account.EmojiIDs = make([]string, 0, emojisCount) - - for id, emoji := range emojis { - account.Emojis = append(account.Emojis, emoji) - account.EmojiIDs = append(account.EmojiIDs, id) - } + if textChanged { + // Process display name, note, fields, + // and any concomitant emoji changes. + p.processAccountText(ctx, account) + acctColumns = append(acctColumns, "emojis") } if form.AvatarDescription != nil { desc := text.SanitizeToPlaintext(*form.AvatarDescription) - form.AvatarDescription = util.Ptr(desc) + form.AvatarDescription = &desc } if form.Avatar != nil && form.Avatar.Size != 0 { @@ -220,7 +160,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form } account.AvatarMediaAttachmentID = avatarInfo.ID account.AvatarMediaAttachment = avatarInfo - log.Tracef(ctx, "new avatar info for account %s is %+v", account.ID, avatarInfo) + acctColumns = append(acctColumns, "avatar_media_attachment_id") } else if form.AvatarDescription != nil && account.AvatarMediaAttachment != nil { // Update just existing description if possible. account.AvatarMediaAttachment.Description = *form.AvatarDescription @@ -250,7 +190,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form } account.HeaderMediaAttachmentID = headerInfo.ID account.HeaderMediaAttachment = headerInfo - log.Tracef(ctx, "new header info for account %s is %+v", account.ID, headerInfo) + acctColumns = append(acctColumns, "header_media_attachment_id") } else if form.HeaderDescription != nil && account.HeaderMediaAttachment != nil { // Update just existing description if possible. account.HeaderMediaAttachment.Description = *form.HeaderDescription @@ -264,29 +204,32 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form } } - if form.Locked != nil { - account.Locked = form.Locked - } + // Account settings flags. if form.Source != nil { if form.Source.Language != nil { language, err := validate.Language(*form.Source.Language) if err != nil { - return nil, gtserror.NewErrorBadRequest(err) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) } + account.Settings.Language = language + settingsColumns = append(settingsColumns, "language") } if form.Source.Sensitive != nil { account.Settings.Sensitive = form.Source.Sensitive + settingsColumns = append(settingsColumns, "sensitive") } if form.Source.Privacy != nil { if err := validate.Privacy(*form.Source.Privacy); err != nil { - return nil, gtserror.NewErrorBadRequest(err) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) } - privacy := typeutils.APIVisToVis(apimodel.Visibility(*form.Source.Privacy)) - account.Settings.Privacy = privacy + + priv := apimodel.Visibility(*form.Source.Privacy) + account.Settings.Privacy = typeutils.APIVisToVis(priv) + settingsColumns = append(settingsColumns, "privacy") } if form.Source.StatusContentType != nil { @@ -295,6 +238,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form } account.Settings.StatusContentType = *form.Source.StatusContentType + settingsColumns = append(settingsColumns, "status_content_type") } } @@ -312,6 +256,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form } account.Settings.Theme = theme } + settingsColumns = append(settingsColumns, "theme") } if form.CustomCSS != nil { @@ -319,25 +264,54 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form if err := validate.CustomCSS(customCSS); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } + account.Settings.CustomCSS = text.SanitizeToPlaintext(customCSS) + settingsColumns = append(settingsColumns, "custom_css") } if form.EnableRSS != nil { account.Settings.EnableRSS = form.EnableRSS + settingsColumns = append(settingsColumns, "enable_rss") } if form.HideCollections != nil { account.Settings.HideCollections = form.HideCollections + settingsColumns = append(settingsColumns, "hide_collections") + } + + if form.WebVisibility != nil { + apiVis := apimodel.Visibility(*form.WebVisibility) + webVisibility := typeutils.APIVisToVis(apiVis) + if webVisibility != gtsmodel.VisibilityPublic && + webVisibility != gtsmodel.VisibilityUnlocked && + webVisibility != gtsmodel.VisibilityNone { + const text = "web_visibility must be one of public, unlocked, or none" + err := errors.New(text) + return nil, gtserror.NewErrorBadRequest(err, text) + } + + account.Settings.WebVisibility = webVisibility + settingsColumns = append(settingsColumns, "web_visibility") } - if err := p.state.DB.UpdateAccount(ctx, account); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account %s: %s", account.ID, err)) + // We've parsed + set everything, do + // necessary database updates now. + + if len(acctColumns) > 0 { + if err := p.state.DB.UpdateAccount(ctx, account, acctColumns...); err != nil { + err := gtserror.Newf("db error updating account %s: %w", account.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } } - if err := p.state.DB.UpdateAccountSettings(ctx, account.Settings); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account settings %s: %s", account.ID, err)) + if len(settingsColumns) > 0 { + if err := p.state.DB.UpdateAccountSettings(ctx, account.Settings, settingsColumns...); err != nil { + err := gtserror.Newf("db error updating account settings %s: %w", account.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } } + // Send out Update message over the s2s (fedi) API. p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ APObjectType: ap.ActorPerson, APActivityType: ap.ActivityUpdate, @@ -347,11 +321,133 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form acctSensitive, err := p.converter.AccountToAPIAccountSensitive(ctx, account) if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not convert account into apisensitive account: %s", err)) + err := gtserror.Newf("error converting account: %w", err) + return nil, gtserror.NewErrorInternalError(err) } + return acctSensitive, nil } +// updateFields sets FieldsRaw on the given +// account, and resets account.Fields to an +// empty slice, ready for further processing. +func (p *Processor) updateFields( + account *gtsmodel.Account, + fieldsAttributes []apimodel.UpdateField, +) gtserror.WithCode { + var ( + fieldsLen = len(fieldsAttributes) + fieldsRaw = make([]*gtsmodel.Field, 0, fieldsLen) + ) + + for _, updateField := range fieldsAttributes { + if updateField.Name == nil || updateField.Value == nil { + continue + } + + var ( + name string = *updateField.Name + value string = *updateField.Value + ) + + if name == "" || value == "" { + continue + } + + // Sanitize raw field values. + fieldRaw := >smodel.Field{ + Name: text.SanitizeToPlaintext(name), + Value: text.SanitizeToPlaintext(value), + } + fieldsRaw = append(fieldsRaw, fieldRaw) + } + + // Check length of parsed raw fields. + if err := validate.ProfileFields(fieldsRaw); err != nil { + return gtserror.NewErrorBadRequest(err, err.Error()) + } + + // OK, new raw fields are valid. + account.FieldsRaw = fieldsRaw + account.Fields = make([]*gtsmodel.Field, 0, fieldsLen) + return nil +} + +// processAccountText processes the raw versions of the given +// account's display name, note, and fields, and sets those +// processed versions on the account, while also updating the +// account's emojis entry based on the results of the processing. +func (p *Processor) processAccountText( + ctx context.Context, + account *gtsmodel.Account, +) { + // Use map to deduplicate emojis by their ID. + emojis := make(map[string]*gtsmodel.Emoji) + + // Retrieve display name emojis. + for _, emoji := range p.formatter.FromPlainEmojiOnly( + ctx, + p.parseMention, + account.ID, + "", + account.DisplayName, + ).Emojis { + emojis[emoji.ID] = emoji + } + + // Format + set note according to user prefs. + f := p.selectNoteFormatter(account.Settings.StatusContentType) + formatNoteResult := f(ctx, p.parseMention, account.ID, "", account.NoteRaw) + account.Note = formatNoteResult.HTML + + // Retrieve note emojis. + for _, emoji := range formatNoteResult.Emojis { + emojis[emoji.ID] = emoji + } + + // Process raw fields. + account.Fields = make([]*gtsmodel.Field, 0, len(account.FieldsRaw)) + for _, fieldRaw := range account.FieldsRaw { + field := >smodel.Field{} + + // Name stays plain, but we still need to + // see if there are any emojis set in it. + field.Name = fieldRaw.Name + for _, emoji := range p.formatter.FromPlainEmojiOnly( + ctx, + p.parseMention, + account.ID, + "", + fieldRaw.Name, + ).Emojis { + emojis[emoji.ID] = emoji + } + + // Value can be HTML, but we don't want + // to wrap the result in <p> tags. + fieldFormatValueResult := p.formatter.FromPlainNoParagraph(ctx, p.parseMention, account.ID, "", fieldRaw.Value) + field.Value = fieldFormatValueResult.HTML + + // Retrieve field emojis. + for _, emoji := range fieldFormatValueResult.Emojis { + emojis[emoji.ID] = emoji + } + + // We're done, append the shiny new field. + account.Fields = append(account.Fields, field) + } + + // Update the account's emojis. + emojisCount := len(emojis) + account.Emojis = make([]*gtsmodel.Emoji, 0, emojisCount) + account.EmojiIDs = make([]string, 0, emojisCount) + + for id, emoji := range emojis { + account.Emojis = append(account.Emojis, emoji) + account.EmojiIDs = append(account.EmojiIDs, id) + } +} + // UpdateAvatar does the dirty work of checking the avatar // part of an account update form, parsing and checking the // media, and doing the necessary updates in the database |