diff --git a/model/responses_impl.go b/model/responses_impl.go index e134f4ff..93e070d4 100755 --- a/model/responses_impl.go +++ b/model/responses_impl.go @@ -3,9 +3,10 @@ package model import ( "context" "fmt" + "time" + "gopkg.in/guregu/null.v4" "gorm.io/gorm" - "time" ) // Response ResponseRepositoryの実装 @@ -16,7 +17,7 @@ func NewResponse() *Response { return new(Response) } -//Responses responseテーブルの構造体 +// Responses responseテーブルの構造体 type Responses struct { ResponseID int `json:"-" gorm:"type:int(11);not null"` QuestionID int `json:"-" gorm:"type:int(11);not null"` @@ -25,21 +26,21 @@ type Responses struct { DeletedAt gorm.DeletedAt `json:"-" gorm:"type:TIMESTAMP NULL;default:NULL"` } -//BeforeCreate insert時に自動でmodifiedAt更新 +// BeforeCreate insert時に自動でmodifiedAt更新 func (r *Responses) BeforeCreate(tx *gorm.DB) error { r.ModifiedAt = time.Now() return nil } -//BeforeUpdate Update時に自動でmodified_atを現在時刻に +// BeforeUpdate Update時に自動でmodified_atを現在時刻に func (r *Responses) BeforeUpdate(tx *gorm.DB) error { r.ModifiedAt = time.Now() return nil } -//TableName テーブル名が単数形なのでその対応 +// TableName テーブル名が単数形なのでその対応 func (*Responses) TableName() string { return "response" } @@ -49,7 +50,7 @@ type ResponseBody struct { QuestionID int `json:"questionID" gorm:"column:id" validate:"min=0"` QuestionType string `json:"question_type" gorm:"column:type" validate:"required,oneof=Text TextArea Number MultipleChoice Checkbox LinearScale"` Body null.String `json:"response" validate:"required"` - OptionResponse []string `json:"option_response" validate:"required_if=QuestionType Checkbox,required_if=QuestionType MultipleChoice,dive,max=50"` + OptionResponse []string `json:"option_response" validate:"required_if=QuestionType Checkbox,required_if=QuestionType MultipleChoice,dive,max=1000"` } // ResponseMeta 質問に対する回答の構造体 diff --git a/router/questions.go b/router/questions.go index a0349040..1b446686 100644 --- a/router/questions.go +++ b/router/questions.go @@ -3,10 +3,11 @@ package router import ( "errors" "fmt" - "github.com/labstack/echo/v4" "net/http" "regexp" + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/model" ) @@ -35,7 +36,7 @@ type PostAndEditQuestionRequest struct { PageNum int `json:"page_num" validate:"min=0"` Body string `json:"body" validate:"required"` IsRequired bool `json:"is_required"` - Options []string `json:"options" validate:"required_if=QuestionType Checkbox,required_if=QuestionType MultipleChoice,dive,max=50"` + Options []string `json:"options" validate:"required_if=QuestionType Checkbox,required_if=QuestionType MultipleChoice,dive,max=1000"` ScaleLabelRight string `json:"scale_label_right" validate:"max=50"` ScaleLabelLeft string `json:"scale_label_left" validate:"max=50"` ScaleMin int `json:"scale_min"` @@ -60,6 +61,18 @@ func (q *Question) EditQuestion(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest) } + validate, err := getValidator(c) + if err != nil { + c.Logger().Errorf("failed to get validator: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + + err = validate.Struct(req) + if err != nil { + c.Logger().Infof("validation failed: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, err) + } + switch req.QuestionType { case "Text": //正規表現のチェック diff --git a/router/questions_test.go b/router/questions_test.go index 5e8fc7fe..009f695d 100644 --- a/router/questions_test.go +++ b/router/questions_test.go @@ -1,6 +1,7 @@ package router import ( + "strings" "testing" "github.com/go-playground/validator/v10" @@ -163,7 +164,7 @@ func TestPostQuestionValidate(t *testing.T) { }, }, { - description: "Textタイプでoptionが50文字以上でもエラー", + description: "Textタイプでoptionが1000文字以上でもエラー", request: &PostAndEditQuestionRequest{ QuestionnaireID: 1, QuestionType: "Text", @@ -171,7 +172,7 @@ func TestPostQuestionValidate(t *testing.T) { PageNum: 1, Body: "発表タイトル", IsRequired: true, - Options: []string{"012345678901234567890123456789012345678901234567890"}, + Options: []string{"0" + strings.Repeat("1234567890", 100)}, ScaleLabelRight: "", ScaleLabelLeft: "", ScaleMin: 0, @@ -446,7 +447,7 @@ func TestPostQuestionValidate(t *testing.T) { }, }, { - description: "TextAreaタイプでoptionが50文字以上でもエラー", + description: "TextAreaタイプでoptionが1000文字以上でもエラー", request: &PostAndEditQuestionRequest{ QuestionnaireID: 1, QuestionType: "TextArea", @@ -454,7 +455,7 @@ func TestPostQuestionValidate(t *testing.T) { PageNum: 1, Body: "発表タイトル", IsRequired: true, - Options: []string{"012345678901234567890123456789012345678901234567890"}, + Options: []string{"0" + strings.Repeat("1234567890", 100)}, ScaleLabelRight: "", ScaleLabelLeft: "", ScaleMin: 0, @@ -642,7 +643,7 @@ func TestPostQuestionValidate(t *testing.T) { }, }, { - description: "Numberタイプでoptionが50文字以上でもエラー", + description: "Numberタイプでoptionが1000文字以上でもエラー", request: &PostAndEditQuestionRequest{ QuestionnaireID: 1, QuestionType: "Number", @@ -650,7 +651,7 @@ func TestPostQuestionValidate(t *testing.T) { PageNum: 1, Body: "発表タイトル", IsRequired: true, - Options: []string{"012345678901234567890123456789012345678901234567890"}, + Options: []string{"0" + strings.Repeat("1234567890", 100)}, ScaleLabelRight: "", ScaleLabelLeft: "", ScaleMin: 0, @@ -839,7 +840,7 @@ func TestPostQuestionValidate(t *testing.T) { isErr: true, }, { - description: "Checkboxタイプでoptionが50文字以上でエラー", + description: "Checkboxタイプでoptionが1000文字以上でエラー", request: &PostAndEditQuestionRequest{ QuestionnaireID: 1, QuestionType: "Checkbox", @@ -847,7 +848,7 @@ func TestPostQuestionValidate(t *testing.T) { PageNum: 1, Body: "発表タイトル", IsRequired: true, - Options: []string{"012345678901234567890123456789012345678901234567890"}, + Options: []string{"0" + strings.Repeat("1234567890", 100)}, ScaleLabelRight: "", ScaleLabelLeft: "", ScaleMin: 0, @@ -1036,7 +1037,7 @@ func TestPostQuestionValidate(t *testing.T) { isErr: true, }, { - description: "MultipleChoiceタイプでoptionが50文字以上でエラー", + description: "MultipleChoiceタイプでoptionが1000文字以上でエラー", request: &PostAndEditQuestionRequest{ QuestionnaireID: 1, QuestionType: "MultipleChoice", @@ -1044,7 +1045,7 @@ func TestPostQuestionValidate(t *testing.T) { PageNum: 1, Body: "発表タイトル", IsRequired: true, - Options: []string{"012345678901234567890123456789012345678901234567890"}, + Options: []string{"0" + strings.Repeat("1234567890", 100)}, ScaleLabelRight: "", ScaleLabelLeft: "", ScaleMin: 0, @@ -1232,7 +1233,7 @@ func TestPostQuestionValidate(t *testing.T) { }, }, { - description: "LinearScaleタイプでoptionが50文字以上でもエラー", + description: "LinearScaleタイプでoptionが1000文字以上でもエラー", request: &PostAndEditQuestionRequest{ QuestionnaireID: 1, QuestionType: "LinearScale", @@ -1240,7 +1241,7 @@ func TestPostQuestionValidate(t *testing.T) { PageNum: 1, Body: "発表タイトル", IsRequired: true, - Options: []string{"012345678901234567890123456789012345678901234567890"}, + Options: []string{"0" + strings.Repeat("1234567890", 100)}, ScaleLabelRight: "右", ScaleLabelLeft: "左", ScaleMin: 0, diff --git a/router/responses.go b/router/responses.go index 56d9bb10..81d11d64 100644 --- a/router/responses.go +++ b/router/responses.go @@ -242,6 +242,18 @@ func (r *Response) EditResponse(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest) } + validate, err := getValidator(c) + if err != nil { + c.Logger().Errorf("failed to get validator: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + + err = validate.Struct(req) + if err != nil { + c.Logger().Infof("validation failed: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, err) + } + limit, err := r.GetQuestionnaireLimit(c.Request().Context(), req.ID) if err != nil { if errors.Is(err, model.ErrRecordNotFound) { diff --git a/router/responses_test.go b/router/responses_test.go index 1ea9845b..2754bfbb 100644 --- a/router/responses_test.go +++ b/router/responses_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "github.com/go-playground/validator/v10" @@ -27,7 +28,7 @@ type responseBody struct { QuestionID int `json:"questionID" validate:"min=0"` QuestionType string `json:"question_type" validate:"required,oneof=Text TextArea Number MultipleChoice Checkbox LinearScale"` Body null.String `json:"response" validate:"required"` - OptionResponse []string `json:"option_response" validate:"required_if=QuestionType Checkbox,required_if=QuestionType MultipleChoice,dive,max=50"` + OptionResponse []string `json:"option_response" validate:"required_if=QuestionType Checkbox,required_if=QuestionType MultipleChoice,dive,max=1000"` } func TestPostResponseValidate(t *testing.T) { @@ -141,7 +142,7 @@ func TestPostResponseValidate(t *testing.T) { isErr: true, }, { - description: "TextタイプでoptionResponseが50文字以上でエラー", + description: "TextタイプでoptionResponseが1000文字以上でエラー", request: &Responses{ ID: 1, Temporarily: false, @@ -150,14 +151,14 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "Text", Body: null.String{}, - OptionResponse: []string{"012345678901234567890123456789012345678901234567890"}, + OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, }, }, }, isErr: true, }, { - description: "TextタイプでoptionResponseが50文字ピッタリはエラーなし", + description: "TextタイプでoptionResponseが1000文字ピッタリはエラーなし", request: &Responses{ ID: 1, Temporarily: false, @@ -166,7 +167,7 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "Text", Body: null.String{}, - OptionResponse: []string{"01234567890123456789012345678901234567890123456789"}, + OptionResponse: []string{strings.Repeat("1234567890", 100)}, }, }, }, @@ -187,7 +188,7 @@ func TestPostResponseValidate(t *testing.T) { }, }, { - description: "TextAreaタイプでoptionResponseが50文字以上でもエラー", + description: "TextAreaタイプでoptionResponseが1000文字以上でもエラー", request: &Responses{ ID: 1, Temporarily: false, @@ -196,14 +197,14 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "TextArea", Body: null.String{}, - OptionResponse: []string{"012345678901234567890123456789012345678901234567890"}, + OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, }, }, }, isErr: true, }, { - description: "TextAreaタイプでoptionResponseが50文字ピッタリはエラーなし", + description: "TextAreaタイプでoptionResponseが1000文字ピッタリはエラーなし", request: &Responses{ ID: 1, Temporarily: false, @@ -212,7 +213,7 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "TextArea", Body: null.String{}, - OptionResponse: []string{"01234567890123456789012345678901234567890123456789"}, + OptionResponse: []string{strings.Repeat("1234567890", 100)}, }, }, }, @@ -233,7 +234,7 @@ func TestPostResponseValidate(t *testing.T) { }, }, { - description: "NumberタイプでoptionResponseが50文字以上でもエラー", + description: "NumberタイプでoptionResponseが1000文字以上でもエラー", request: &Responses{ ID: 1, Temporarily: false, @@ -242,14 +243,14 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "Number", Body: null.String{}, - OptionResponse: []string{"012345678901234567890123456789012345678901234567890"}, + OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, }, }, }, isErr: true, }, { - description: "NumberタイプでoptionResponseが50文字ピッタリでエラーなし", + description: "NumberタイプでoptionResponseが1000文字ピッタリでエラーなし", request: &Responses{ ID: 1, Temporarily: false, @@ -258,7 +259,7 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "Number", Body: null.String{}, - OptionResponse: []string{"01234567890123456789012345678901234567890123456789"}, + OptionResponse: []string{strings.Repeat("1234567890", 100)}, }, }, }, @@ -310,7 +311,7 @@ func TestPostResponseValidate(t *testing.T) { isErr: true, }, { - description: "CheckboxタイプでOptionResponseが50文字以上な回答なのでエラー", + description: "CheckboxタイプでOptionResponseが1000文字以上な回答なのでエラー", request: &Responses{ ID: 1, Temporarily: false, @@ -319,14 +320,14 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "Checkbox", Body: null.String{}, - OptionResponse: []string{"012345678901234567890123456789012345678901234567890"}, + OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, }, }, }, isErr: true, }, { - description: "CheckboxタイプでOptionResponseが50文字ピッタリな回答なのでエラーなし", + description: "CheckboxタイプでOptionResponseが1000文字ピッタリな回答なのでエラーなし", request: &Responses{ ID: 1, Temporarily: false, @@ -335,7 +336,7 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "Checkbox", Body: null.String{}, - OptionResponse: []string{"01234567890123456789012345678901234567890123456789"}, + OptionResponse: []string{strings.Repeat("1234567890", 100)}, }, }, }, @@ -372,7 +373,7 @@ func TestPostResponseValidate(t *testing.T) { isErr: true, }, { - description: "MultipleChoiceタイプでOptionResponseが50文字以上な回答なのでエラー", + description: "MultipleChoiceタイプでOptionResponseが1000文字以上な回答なのでエラー", request: &Responses{ ID: 1, Temporarily: false, @@ -381,14 +382,14 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "MultipleChoice", Body: null.String{}, - OptionResponse: []string{"012345678901234567890123456789012345678901234567890"}, + OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, }, }, }, isErr: true, }, { - description: "MultipleChoiceタイプでOptionResponseが50文字ピッタリな回答なのでエラーなし", + description: "MultipleChoiceタイプでOptionResponseが1000文字ピッタリな回答なのでエラーなし", request: &Responses{ ID: 1, Temporarily: false, @@ -397,7 +398,7 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "MultipleChoice", Body: null.String{}, - OptionResponse: []string{"01234567890123456789012345678901234567890123456789"}, + OptionResponse: []string{strings.Repeat("1234567890", 100)}, }, }, }, @@ -418,7 +419,7 @@ func TestPostResponseValidate(t *testing.T) { }, }, { - description: "LinearScaleタイプでoptionResponseが50文字以上でもエラー", + description: "LinearScaleタイプでoptionResponseが1000文字以上でもエラー", request: &Responses{ ID: 1, Temporarily: false, @@ -427,14 +428,14 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "LinearScale", Body: null.String{}, - OptionResponse: []string{"012345678901234567890123456789012345678901234567890"}, + OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, }, }, }, isErr: true, }, { - description: "LinearScaleタイプでoptionResponseが50文字ピッタリなのでエラーなし", + description: "LinearScaleタイプでoptionResponseが1000文字ピッタリなのでエラーなし", request: &Responses{ ID: 1, Temporarily: false, @@ -443,7 +444,7 @@ func TestPostResponseValidate(t *testing.T) { QuestionID: 1, QuestionType: "LinearScale", Body: null.String{}, - OptionResponse: []string{"01234567890123456789012345678901234567890123456789"}, + OptionResponse: []string{strings.Repeat("1234567890", 100)}, }, }, }, @@ -1683,7 +1684,7 @@ func TestEditResponse(t *testing.T) { } e := echo.New() - e.PATCH("/api/responses/:responseID", r.EditResponse, m.SetUserIDMiddleware, m.TraPMemberAuthenticate, func(next echo.HandlerFunc) echo.HandlerFunc { + e.PATCH("/api/responses/:responseID", r.EditResponse, m.SetUserIDMiddleware, m.SetValidatorMiddleware, m.TraPMemberAuthenticate, func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { responseID, err := strconv.Atoi(c.Param("responseID")) if err != nil {