From 91b0006dc703bfcfa9c898c6c8dbce602a936b58 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Fri, 27 Sep 2024 20:07:00 +0900 Subject: [PATCH 1/5] impl: insert validation when post questionnaire --- controller/questionnaire.go | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 3c583503..c3b18ab2 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -176,6 +177,99 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o c.Logger().Errorf("failed to create a questionnaire: %+v", err) return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to create a questionnaire") } + + questions, err := q.IQuestion.GetQuestions(c.Request().Context(), questionnaireID) + for i, question := range questions { + switch question.Type { + case "SingleChoice": + b, err := params.Questions[i].AsQuestionSettingsSingleChoice() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + for i, v := range b.Options { + err := q.IOption.InsertOption(c.Request().Context(), question.ID, i+1, v) + if err != nil { + c.Logger().Errorf("failed to insert option: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to insert option") + } + } + case "MultipleChoice": + b, err := params.Questions[i].AsQuestionSettingsMultipleChoice() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + for i, v := range b.Options { + err := q.IOption.InsertOption(c.Request().Context(), question.ID, i+1, v) + if err != nil { + c.Logger().Errorf("failed to insert option: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to insert option") + } + } + case "Scale": + b, err := params.Questions[i].AsQuestionSettingsScale() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IScaleLabel.InsertScaleLabel(c.Request().Context(), question.ID, + model.ScaleLabels{ + ScaleLabelLeft: *b.MinLabel, + ScaleLabelRight: *b.MaxLabel, + ScaleMax: b.MaxValue, + ScaleMin: b.MinValue, + }) + if err != nil { + c.Logger().Errorf("failed to insert scale label: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to insert scale label") + } + case "Text": + b, err := params.Questions[i].AsQuestionSettingsText() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IValidation.InsertValidation(c.Request().Context(), question.ID, + model.Validations{ + MaxBound: strconv.Itoa(*b.MaxLength), + }) + if err != nil { + c.Logger().Errorf("failed to insert validation: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to insert validation") + } + case "TextLong": + b, err := params.Questions[i].AsQuestionSettingsTextLong() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IValidation.InsertValidation(c.Request().Context(), question.ID, + model.Validations{ + MaxBound: strconv.FormatFloat(float64(*b.MaxLength), 'f', -1, 64), + }) + if err != nil { + c.Logger().Errorf("failed to insert validation: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to insert validation") + } + case "Number": + b, err := params.Questions[i].AsQuestionSettingsNumber() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IValidation.InsertValidation(c.Request().Context(), question.ID, + model.Validations{ + MinBound: strconv.Itoa(*b.MinValue), + MaxBound: strconv.Itoa(*b.MaxValue), + }) + if err != nil { + c.Logger().Errorf("failed to insert validation: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to insert validation") + } + } + } + questionnaireInfo, targets, targetGroups, admins, adminGroups, respondents, err := q.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) if err != nil { c.Logger().Errorf("failed to get questionnaire info: %+v", err) From 438b8554aa8da82b600bfe6ce69aacb3471f14b2 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Fri, 27 Sep 2024 20:20:22 +0900 Subject: [PATCH 2/5] impl: update validations when edit questionnaire --- controller/questionnaire.go | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index c3b18ab2..debb081b 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -258,6 +258,12 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o c.Logger().Errorf("failed to get question settings: %+v", err) return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") } + // 数字かどうか,min<=maxになっているかどうか + err = q.IValidation.CheckNumberValid(strconv.Itoa(*b.MinValue), strconv.Itoa(*b.MaxValue)) + if err != nil { + c.Logger().Errorf("invalid number: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusBadRequest, "invalid number") + } err = q.IValidation.InsertValidation(c.Request().Context(), question.ID, model.Validations{ MinBound: strconv.Itoa(*b.MinValue), @@ -358,6 +364,100 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa return echo.NewHTTPError(http.StatusInternalServerError, "failed to update a questionnaire") } + questions, err := q.IQuestion.GetQuestions(c.Request().Context(), questionnaireID) + for i, question := range questions { + switch question.Type { + case "SingleChoice": + b, err := params.Questions[i].AsQuestionSettingsSingleChoice() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IOption.UpdateOptions(c.Request().Context(), b.Options, question.ID) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to update options: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to update options") + } + case "MultipleChoice": + b, err := params.Questions[i].AsQuestionSettingsMultipleChoice() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IOption.UpdateOptions(c.Request().Context(), b.Options, question.ID) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to update options: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to update options") + } + case "Scale": + b, err := params.Questions[i].AsQuestionSettingsScale() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IScaleLabel.UpdateScaleLabel(c.Request().Context(), question.ID, + model.ScaleLabels{ + ScaleLabelLeft: *b.MinLabel, + ScaleLabelRight: *b.MaxLabel, + ScaleMax: b.MaxValue, + ScaleMin: b.MinValue, + }) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to insert scale label: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to insert scale label") + } + case "Text": + b, err := params.Questions[i].AsQuestionSettingsText() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IValidation.UpdateValidation(c.Request().Context(), question.ID, + model.Validations{ + MaxBound: strconv.Itoa(*b.MaxLength), + }) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to insert validation: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to insert validation") + } + case "TextLong": + b, err := params.Questions[i].AsQuestionSettingsTextLong() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + err = q.IValidation.UpdateValidation(c.Request().Context(), question.ID, + model.Validations{ + MaxBound: strconv.FormatFloat(float64(*b.MaxLength), 'f', -1, 64), + }) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to insert validation: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to insert validation") + } + case "Number": + b, err := params.Questions[i].AsQuestionSettingsNumber() + if err != nil { + c.Logger().Errorf("failed to get question settings: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get question settings") + } + // 数字かどうか,min<=maxになっているかどうか + err = q.IValidation.CheckNumberValid(strconv.Itoa(*b.MinValue), strconv.Itoa(*b.MaxValue)) + if err != nil { + c.Logger().Errorf("invalid number: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid number") + } + err = q.IValidation.UpdateValidation(c.Request().Context(), question.ID, + model.Validations{ + MinBound: strconv.Itoa(*b.MinValue), + MaxBound: strconv.Itoa(*b.MaxValue), + }) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to insert validation: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to insert validation") + } + } + } + return nil } From ae23e2ffa473c40def6c3ce5de445c1073c1b750 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Sat, 28 Sep 2024 13:19:34 +0900 Subject: [PATCH 3/5] fix: regex pattern validation for text --- controller/questionnaire.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index debb081b..3dc33239 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -178,6 +178,7 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to create a questionnaire") } + // insert validations questions, err := q.IQuestion.GetQuestions(c.Request().Context(), questionnaireID) for i, question := range questions { switch question.Type { @@ -232,7 +233,7 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o } err = q.IValidation.InsertValidation(c.Request().Context(), question.ID, model.Validations{ - MaxBound: strconv.Itoa(*b.MaxLength), + RegexPattern: ".{," + strconv.Itoa(*b.MaxLength) + "}", }) if err != nil { c.Logger().Errorf("failed to insert validation: %+v", err) @@ -246,7 +247,7 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o } err = q.IValidation.InsertValidation(c.Request().Context(), question.ID, model.Validations{ - MaxBound: strconv.FormatFloat(float64(*b.MaxLength), 'f', -1, 64), + RegexPattern: ".{," + fmt.Sprintf("%.0f", *b.MaxLength) + "}", }) if err != nil { c.Logger().Errorf("failed to insert validation: %+v", err) @@ -364,6 +365,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa return echo.NewHTTPError(http.StatusInternalServerError, "failed to update a questionnaire") } + // update validations questions, err := q.IQuestion.GetQuestions(c.Request().Context(), questionnaireID) for i, question := range questions { switch question.Type { @@ -414,7 +416,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa } err = q.IValidation.UpdateValidation(c.Request().Context(), question.ID, model.Validations{ - MaxBound: strconv.Itoa(*b.MaxLength), + RegexPattern: ".{," + strconv.Itoa(*b.MaxLength) + "}", }) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to insert validation: %+v", err) @@ -428,7 +430,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa } err = q.IValidation.UpdateValidation(c.Request().Context(), question.ID, model.Validations{ - MaxBound: strconv.FormatFloat(float64(*b.MaxLength), 'f', -1, 64), + RegexPattern: ".{," + fmt.Sprintf("%.0f", *b.MaxLength) + "}", }) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to insert validation: %+v", err) From 88041f4bf0fca2003e2cfd38572e3d83c05be0f3 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Sat, 28 Sep 2024 13:34:56 +0900 Subject: [PATCH 4/5] impl: check validation when post questionnaire response --- controller/questionnaire.go | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 3dc33239..b15fc1cb 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -546,6 +546,89 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID return res, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + questions, err := q.IQuestion.GetQuestions(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to get questions: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) + } + + responseMetas, err := responseBody2ResponseMetas(params.Body, questions) + if err != nil { + c.Logger().Errorf("failed to convert response body to response metas: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) + } + + // validationでチェック + questionIDs := make([]int, len(questions)) + questionTypes := make(map[int]string, len(questions)) + for i, question := range questions { + questionIDs[i] = question.ID + questionTypes[question.ID] = question.Type + } + + validations, err := q.IValidation.GetValidations(c.Request().Context(), questionIDs) + if err != nil { + c.Logger().Errorf("failed to get validations: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) + } + + for i, validation := range validations { + switch questionTypes[validation.QuestionID] { + case "Text", "TextLong": + err := q.IValidation.CheckTextValidation(validation, responseMetas[i].Data) + if err != nil { + if errors.Is(err, model.ErrTextMatching) { + c.Logger().Errorf("invalid text: %+v", err) + return res, echo.NewHTTPError(http.StatusBadRequest, err) + } + c.Logger().Errorf("invalid text: %+v", err) + return res, echo.NewHTTPError(http.StatusBadRequest, err) + } + case "Number": + err := q.IValidation.CheckNumberValidation(validation, responseMetas[i].Data) + if err != nil { + if errors.Is(err, model.ErrInvalidNumber) { + c.Logger().Errorf("invalid number: %+v", err) + return res, echo.NewHTTPError(http.StatusBadRequest, err) + } + c.Logger().Errorf("invalid number: %+v", err) + return res, echo.NewHTTPError(http.StatusBadRequest, err) + } + } + } + + // scaleのvalidation + scaleLabelIDs := []int{} + for _, question := range questions { + if question.Type == "Scale" { + scaleLabelIDs = append(scaleLabelIDs, question.ID) + } + } + + scaleLabels, err := q.IScaleLabel.GetScaleLabels(c.Request().Context(), scaleLabelIDs) + if err != nil { + c.Logger().Errorf("failed to get scale labels: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) + } + scaleLabelMap := make(map[int]model.ScaleLabels, len(scaleLabels)) + for _, scaleLabel := range scaleLabels { + scaleLabelMap[scaleLabel.QuestionID] = scaleLabel + } + + for i, question := range questions { + if question.Type == "Scale" { + label, ok := scaleLabelMap[question.ID] + if !ok { + label = model.ScaleLabels{} + } + err := q.IScaleLabel.CheckScaleLabel(label, responseMetas[i].Data) + if err != nil { + c.Logger().Errorf("invalid scale: %+v", err) + return res, echo.NewHTTPError(http.StatusBadRequest, err) + } + } + } + var submittedAt, modifiedAt time.Time //一時保存のときはnull if params.IsDraft { @@ -562,6 +645,14 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID return res, echo.NewHTTPError(http.StatusInternalServerError, err) } + if len(responseMetas) > 0 { + err = q.InsertResponses(c.Request().Context(), resopnseID, responseMetas) + if err != nil { + c.Logger().Errorf("failed to insert responses: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) + } + } + res = openapi.Response{ QuestionnaireId: questionnaireID, ResponseId: resopnseID, From 206a691860e2bbc4f7609f299e7938a123da4bb0 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Sat, 28 Sep 2024 13:39:26 +0900 Subject: [PATCH 5/5] impl: check validation when edit response --- controller/response.go | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/controller/response.go b/controller/response.go index bb3fd0e3..e344f968 100644 --- a/controller/response.go +++ b/controller/response.go @@ -18,6 +18,8 @@ type Response struct { model.IResponse model.ITarget model.IQuestion + model.IValidation + model.IScaleLabel } func NewResponse() *Response { @@ -164,6 +166,77 @@ func (r Response) EditResponse(ctx echo.Context, responseID openapi.ResponseIDIn return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert response body into response metas: %w", err)) } + // validationでチェック + questionIDs := make([]int, len(questions)) + questionTypes := make(map[int]string, len(questions)) + for i, question := range questions { + questionIDs[i] = question.ID + questionTypes[question.ID] = question.Type + } + + validations, err := r.IValidation.GetValidations(ctx.Request().Context(), questionIDs) + if err != nil { + ctx.Logger().Errorf("failed to get validations: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get validations: %w", err)) + } + + for i, validation := range validations { + switch questionTypes[validation.QuestionID] { + case "Text", "TextLong": + err := r.IValidation.CheckTextValidation(validation, responseMetas[i].Data) + if err != nil { + if errors.Is(err, model.ErrTextMatching) { + ctx.Logger().Errorf("invalid text: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid text: %w", err)) + } + ctx.Logger().Errorf("invalid text: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid text: %w", err)) + } + case "Number": + err := r.IValidation.CheckNumberValidation(validation, responseMetas[i].Data) + if err != nil { + if errors.Is(err, model.ErrInvalidNumber) { + ctx.Logger().Errorf("invalid number: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid number: %w", err)) + } + ctx.Logger().Errorf("invalid number: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid number: %w", err)) + } + } + } + + // scaleのvalidation + scaleLabelIDs := []int{} + for _, question := range questions { + if question.Type == "Scale" { + scaleLabelIDs = append(scaleLabelIDs, question.ID) + } + } + + scaleLabels, err := r.IScaleLabel.GetScaleLabels(ctx.Request().Context(), scaleLabelIDs) + if err != nil { + ctx.Logger().Errorf("failed to get scale labels: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get scale labels: %w", err)) + } + scaleLabelMap := make(map[int]model.ScaleLabels, len(scaleLabels)) + for _, scaleLabel := range scaleLabels { + scaleLabelMap[scaleLabel.QuestionID] = scaleLabel + } + + for i, question := range questions { + if question.Type == "Scale" { + label, ok := scaleLabelMap[question.ID] + if !ok { + label = model.ScaleLabels{} + } + err := r.IScaleLabel.CheckScaleLabel(label, responseMetas[i].Data) + if err != nil { + ctx.Logger().Errorf("invalid scale: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid scale: %w", err)) + } + } + } + if len(responseMetas) > 0 { err = r.IResponse.InsertResponses(ctx.Request().Context(), responseID, responseMetas) if err != nil {