diff options
Diffstat (limited to 'internal/processing/account')
-rw-r--r-- | internal/processing/account/get.go | 2 | ||||
-rw-r--r-- | internal/processing/account/update.go | 178 | ||||
-rw-r--r-- | internal/processing/account/update_test.go | 80 |
3 files changed, 208 insertions, 52 deletions
diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go index d0ea96ca2..c7d271b0a 100644 --- a/internal/processing/account/get.go +++ b/internal/processing/account/get.go @@ -96,7 +96,7 @@ func (p *Processor) getFor(ctx context.Context, requestingAccount *gtsmodel.Acco } a, err := p.federator.GetAccountByURI( - gtscontext.SetFastFail(ctx), requestingAccount.Username, targetAccountURI, true, + gtscontext.SetFastFail(ctx), requestingAccount.Username, targetAccountURI, ) if err == nil { targetAccount = a diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go index e88aba637..0baeebb6a 100644 --- a/internal/processing/account/update.go +++ b/internal/processing/account/update.go @@ -36,6 +36,14 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/validate" ) +func (p *Processor) selectNoteFormatter(contentType string) text.FormatFunc { + if contentType == "text/markdown" { + return p.formatter.FromMarkdown + } + + return p.formatter.FromPlain +} + // Update processes the update of an account with the given form. func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) { if form.Discoverable != nil { @@ -46,56 +54,144 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form account.Bot = form.Bot } - reparseEmojis := false + // 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.DisplayName != nil { - if err := validate.DisplayName(*form.DisplayName); err != nil { - return nil, gtserror.NewErrorBadRequest(err) + displayName := *form.DisplayName + if err := validate.DisplayName(displayName); err != nil { + return nil, gtserror.NewErrorBadRequest(err, err.Error()) } - account.DisplayName = text.SanitizePlaintext(*form.DisplayName) - reparseEmojis = true + + // Parse new display name (always from plaintext). + account.DisplayName = text.SanitizePlaintext(displayName) + + // If display name has changed, account emojis may have also changed. + emojisChanged = true } if form.Note != nil { - if err := validate.Note(*form.Note); err != nil { - return nil, gtserror.NewErrorBadRequest(err) + note := *form.Note + if err := validate.Note(note); err != nil { + return nil, gtserror.NewErrorBadRequest(err, err.Error()) } - // Set the raw note before processing - account.NoteRaw = *form.Note - reparseEmojis = true + // Store raw version of the 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 } - if reparseEmojis { - // If either DisplayName or Note changed, reparse both, because we - // can't otherwise tell which one each emoji belongs to. - // Deduplicate emojis between the two fields. + 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 + ) + + if name == "" || value == "" { + continue + } + + // Sanitize raw field values. + fieldRaw := >smodel.Field{ + Name: text.SanitizePlaintext(name), + Value: text.SanitizePlaintext(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()) + } + + // 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 + } + + if emojisChanged { + // Use map to deduplicate emojis by their ID. emojis := make(map[string]*gtsmodel.Emoji) - formatResult := p.formatter.FromPlainEmojiOnly(ctx, p.parseMention, account.ID, "", account.DisplayName) - for _, emoji := range formatResult.Emojis { + + // Retrieve display name emojis. + for _, emoji := range p.formatter.FromPlainEmojiOnly( + ctx, + p.parseMention, + account.ID, + "", + account.DisplayName, + ).Emojis { emojis[emoji.ID] = emoji } - // Process note to generate a valid HTML representation - var f text.FormatFunc - if account.StatusContentType == "text/markdown" { - f = p.formatter.FromMarkdown - } else { - f = p.formatter.FromPlain - } - formatted := f(ctx, p.parseMention, account.ID, "", account.NoteRaw) + // Format + set note according to user prefs. + f := p.selectNoteFormatter(account.StatusContentType) + formatNoteResult := f(ctx, p.parseMention, account.ID, "", account.NoteRaw) + account.Note = formatNoteResult.HTML - // Set updated HTML-ified note - account.Note = formatted.HTML - for _, emoji := range formatted.Emojis { + // Retrieve note emojis. + for _, emoji := range formatNoteResult.Emojis { emojis[emoji.ID] = emoji } - account.Emojis = []*gtsmodel.Emoji{} - account.EmojiIDs = []string{} - for eid, emoji := range emojis { + // Process the raw fields we stored earlier. + 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, eid) + account.EmojiIDs = append(account.EmojiIDs, id) } } @@ -164,26 +260,6 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form account.EnableRSS = form.EnableRSS } - if form.FieldsAttributes != nil && len(*form.FieldsAttributes) != 0 { - if err := validate.ProfileFieldsCount(*form.FieldsAttributes); err != nil { - return nil, gtserror.NewErrorBadRequest(err) - } - - account.Fields = make([]gtsmodel.Field, 0) // reset fields - for _, f := range *form.FieldsAttributes { - if f.Name != nil && f.Value != nil { - if *f.Name != "" && *f.Value != "" { - field := gtsmodel.Field{} - - field.Name = validate.ProfileField(f.Name) - field.Value = validate.ProfileField(f.Value) - - account.Fields = append(account.Fields, field) - } - } - } - } - err := p.state.DB.UpdateAccount(ctx, account) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account %s: %s", account.ID, err)) diff --git a/internal/processing/account/update_test.go b/internal/processing/account/update_test.go index b8c245657..03a3d8703 100644 --- a/internal/processing/account/update_test.go +++ b/internal/processing/account/update_test.go @@ -148,6 +148,86 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() { suite.Equal(expectedNote, dbAccount.Note) } +func (suite *AccountUpdateTestSuite) TestAccountUpdateWithFields() { + testAccount := suite.testAccounts["local_account_1"] + + updateFields := []apimodel.UpdateField{ + { + Name: func() *string { s := "favourite emoji"; return &s }(), + Value: func() *string { s := ":rainbow:"; return &s }(), + }, + { + Name: func() *string { s := "my website"; return &s }(), + Value: func() *string { s := "https://example.org"; return &s }(), + }, + } + + form := &apimodel.UpdateCredentialsRequest{ + FieldsAttributes: &updateFields, + } + + // should get no error from the update function, and an api model account returned + apiAccount, errWithCode := suite.accountProcessor.Update(context.Background(), testAccount, form) + + // reset test account to avoid breaking other tests + testAccount.StatusContentType = "text/plain" + suite.NoError(errWithCode) + suite.NotNil(apiAccount) + suite.EqualValues([]apimodel.Field{ + { + Name: "favourite emoji", + Value: ":rainbow:", + VerifiedAt: (*string)(nil), + }, + { + Name: "my website", + Value: "<a href=\"https://example.org\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">https://example.org</a>", + VerifiedAt: (*string)(nil), + }, + }, apiAccount.Fields) + suite.EqualValues([]apimodel.Field{ + { + Name: "favourite emoji", + Value: ":rainbow:", + VerifiedAt: (*string)(nil), + }, + { + Name: "my website", + Value: "https://example.org", + VerifiedAt: (*string)(nil), + }, + }, apiAccount.Source.Fields) + suite.EqualValues([]apimodel.Emoji{ + { + Shortcode: "rainbow", + URL: "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png", + StaticURL: "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png", + VisibleInPicker: true, + Category: "reactions", + }, + }, apiAccount.Emojis) + + // we should have an update in the client api channel + msg := <-suite.fromClientAPIChan + suite.Equal(ap.ActivityUpdate, msg.APActivityType) + suite.Equal(ap.ObjectProfile, msg.APObjectType) + suite.NotNil(msg.OriginAccount) + suite.Equal(testAccount.ID, msg.OriginAccount.ID) + suite.Nil(msg.TargetAccount) + + // fields should be updated in the database as well + dbAccount, err := suite.db.GetAccountByID(context.Background(), testAccount.ID) + suite.NoError(err) + suite.Equal("favourite emoji", dbAccount.Fields[0].Name) + suite.Equal(":rainbow:", dbAccount.Fields[0].Value) + suite.Equal("my website", dbAccount.Fields[1].Name) + suite.Equal("<a href=\"https://example.org\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">https://example.org</a>", dbAccount.Fields[1].Value) + suite.Equal("favourite emoji", dbAccount.FieldsRaw[0].Name) + suite.Equal(":rainbow:", dbAccount.FieldsRaw[0].Value) + suite.Equal("my website", dbAccount.FieldsRaw[1].Name) + suite.Equal("https://example.org", dbAccount.FieldsRaw[1].Value) +} + func TestAccountUpdateTestSuite(t *testing.T) { suite.Run(t, new(AccountUpdateTestSuite)) } |