summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLibravatar zowhoey <11893985+zowhoey@users.noreply.github.com>2023-03-06 09:30:19 +0000
committerLibravatar GitHub <noreply@github.com>2023-03-06 10:30:19 +0100
commitf518f649f800e52d32fe53580c528ffa042f7154 (patch)
treec14bb8b513008d1c1e7bab0908cfc99fe18f1507 /internal
parent[chore]: Bump github.com/jackc/pgx/v4 from 4.17.2 to 4.18.1 (#1595) (diff)
downloadgotosocial-f518f649f800e52d32fe53580c528ffa042f7154.tar.xz
[feature] Add support for profile fields (#1483)
* Add go-playground/form pkg * [feature] Add support for profile fields * Add field attributes test * Validate profile fields form * Add profile field validation tests * Add Field Attributes definition to swagger --------- Co-authored-by: tobi <31960611+tsmethurst@users.noreply.github.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/api/client/accounts/accountupdate.go35
-rw-r--r--internal/api/client/accounts/accountupdate_test.go7
-rw-r--r--internal/processing/account/update.go20
-rw-r--r--internal/validate/formvalidation.go19
-rw-r--r--internal/validate/formvalidation_test.go34
5 files changed, 113 insertions, 2 deletions
diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go
index b5d0dd5f9..55e544298 100644
--- a/internal/api/client/accounts/accountupdate.go
+++ b/internal/api/client/accounts/accountupdate.go
@@ -25,6 +25,7 @@ import (
"strconv"
"github.com/gin-gonic/gin"
+ "github.com/go-playground/form/v4"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
@@ -115,6 +116,13 @@ import (
// in: formData
// description: Enable RSS feed for this account's Public posts at `/[username]/feed.rss`
// type: boolean
+// -
+// name: fields_attributes
+// in: formData
+// description: Profile fields to be added to this account's profile
+// type: array
+// items:
+// type: object
//
// security:
// - OAuth2 Bearer:
@@ -162,6 +170,28 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusOK, acctSensitive)
}
+type fieldAttributesBinding struct{}
+
+func (fieldAttributesBinding) Name() string {
+ return "FieldAttributes"
+}
+
+func (fieldAttributesBinding) Bind(req *http.Request, obj any) error {
+ if err := req.ParseForm(); err != nil {
+ return err
+ }
+
+ decoder := form.NewDecoder()
+ // change default namespace prefix and suffix to allow correct parsing of the field attributes
+ decoder.SetNamespacePrefix("[")
+ decoder.SetNamespaceSuffix("]")
+ if err := decoder.Decode(obj, req.Form); err != nil {
+ return err
+ }
+
+ return nil
+}
+
func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, error) {
form := &apimodel.UpdateCredentialsRequest{
Source: &apimodel.UpdateSource{},
@@ -171,6 +201,11 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
return nil, fmt.Errorf("could not parse form from request: %s", err)
}
+ // use custom form binding to support field attributes in the form data
+ if err := c.ShouldBindWith(&form, fieldAttributesBinding{}); err != nil {
+ return nil, fmt.Errorf("could not parse form from request: %s", err)
+ }
+
// parse source field-by-field
sourceMap := c.PostFormMap("source")
diff --git a/internal/api/client/accounts/accountupdate_test.go b/internal/api/client/accounts/accountupdate_test.go
index f898e64da..4e4e03fa4 100644
--- a/internal/api/client/accounts/accountupdate_test.go
+++ b/internal/api/client/accounts/accountupdate_test.go
@@ -38,12 +38,14 @@ type AccountUpdateTestSuite struct {
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler() {
// set up the request
- // we're updating the note of zork
+ // we're updating the note and profile fields of zork
newBio := "this is my new bio read it and weep"
requestBody, w, err := testrig.CreateMultipartFormData(
"", "",
map[string]string{
- "note": newBio,
+ "note": newBio,
+ "fields_attributes[0][name]": "pronouns",
+ "fields_attributes[0][value]": "they/them",
})
if err != nil {
panic(err)
@@ -74,6 +76,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler()
// check the returned api model account
// fields should be updated
suite.Equal("<p>this is my new bio read it and weep</p>", apimodelAccount.Note)
+ suite.Equal("they/them", apimodelAccount.Fields[0].Value)
suite.Equal(newBio, apimodelAccount.Source.Note)
}
diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go
index a96c17eeb..76f7a5a54 100644
--- a/internal/processing/account/update.go
+++ b/internal/processing/account/update.go
@@ -165,6 +165,26 @@ 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/validate/formvalidation.go b/internal/validate/formvalidation.go
index 32aa1fd1e..e7839d1a3 100644
--- a/internal/validate/formvalidation.go
+++ b/internal/validate/formvalidation.go
@@ -43,6 +43,8 @@ const (
maximumUsernameLength = 64
maximumCustomCSSLength = 5000
maximumEmojiCategoryLength = 64
+ maximumProfileFieldLength = 255
+ maximumProfileFields = 4
)
// NewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok.
@@ -231,3 +233,20 @@ func SiteTerms(t string) error {
func ULID(i string) bool {
return regexes.ULID.MatchString(i)
}
+
+func ProfileFieldsCount(fields []apimodel.UpdateField) error {
+ if length := len(fields); length > maximumProfileFields {
+ return fmt.Errorf("cannot have more than %d profile fields", maximumProfileFields)
+ }
+
+ return nil
+}
+
+func ProfileField(f *string) string {
+ s := []rune(*f)
+ if len(s) > maximumProfileFieldLength {
+ return string(s[:maximumProfileFieldLength]) // trim profile field to maximum allowed length
+ }
+
+ return string(*f)
+}
diff --git a/internal/validate/formvalidation_test.go b/internal/validate/formvalidation_test.go
index 61f505412..f59bbf753 100644
--- a/internal/validate/formvalidation_test.go
+++ b/internal/validate/formvalidation_test.go
@@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/validate"
)
@@ -284,6 +285,39 @@ func (suite *ValidationTestSuite) TestValidateReason() {
}
}
+func (suite *ValidationTestSuite) TestValidateProfileFieldsCount() {
+ noFields := []model.UpdateField{}
+ fewFields := []model.UpdateField{{}, {}}
+ tooManyFields := []model.UpdateField{{}, {}, {}, {}, {}}
+ err := validate.ProfileFieldsCount(tooManyFields)
+ if assert.Error(suite.T(), err) {
+ assert.Equal(suite.T(), errors.New("cannot have more than 4 profile fields"), err)
+ }
+
+ err = validate.ProfileFieldsCount(noFields)
+ assert.NoError(suite.T(), err)
+
+ err = validate.ProfileFieldsCount(fewFields)
+ assert.NoError(suite.T(), err)
+}
+
+func (suite *ValidationTestSuite) TestValidateProfileField() {
+ shortProfileField := "pronouns"
+ tooLongProfileField := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer eu bibendum elit. Sed ac interdum nisi. Vestibulum vulputate eros quis euismod imperdiet. Nulla sit amet dui sit amet lorem consectetur iaculis. Mauris eget lacinia metus. Curabitur nec dui eleifend massa nunc."
+ trimmedProfileField := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer eu bibendum elit. Sed ac interdum nisi. Vestibulum vulputate eros quis euismod imperdiet. Nulla sit amet dui sit amet lorem consectetur iaculis. Mauris eget lacinia metus. Curabitur nec dui "
+
+ validated := validate.ProfileField(&shortProfileField)
+ assert.Equal(suite.T(), shortProfileField, validated)
+
+ validated = validate.ProfileField(&tooLongProfileField)
+ assert.Len(suite.T(), validated, 255)
+ assert.Equal(suite.T(), trimmedProfileField, validated)
+
+ validated = validate.ProfileField(&trimmedProfileField)
+ assert.Len(suite.T(), validated, 255)
+ assert.Equal(suite.T(), trimmedProfileField, validated)
+}
+
func TestValidationTestSuite(t *testing.T) {
suite.Run(t, new(ValidationTestSuite))
}