diff --git a/model/errors.go b/model/errors.go index 9c7df7cc..eb74acac 100755 --- a/model/errors.go +++ b/model/errors.go @@ -29,4 +29,6 @@ var ( ErrInvalidTx = errors.New("invalid tx") // ErrDeadlineExceeded deadline exceeded ErrDeadlineExceeded = errors.New("deadline exceeded") + // ErrDuplicatedAnswered + ErrDuplicatedAnswered = errors.New("duplicated answered is not allowed") ) diff --git a/model/questionnaires.go b/model/questionnaires.go index ec759d2e..ca34e422 100644 --- a/model/questionnaires.go +++ b/model/questionnaires.go @@ -10,8 +10,8 @@ import ( // IQuestionnaire QuestionnaireのRepository type IQuestionnaire interface { - InsertQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string) (int, error) - UpdateQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, questionnaireID int) error + InsertQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, IsDuplicateAnswerAllowed bool) (int, error) + UpdateQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, IsDuplicateAnswerAllowed bool, questionnaireID int) error DeleteQuestionnaire(ctx context.Context, questionnaireID int) error GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, nontargeted bool) ([]QuestionnaireInfo, int, error) GetAdminQuestionnaires(ctx context.Context, userID string) ([]Questionnaires, error) diff --git a/model/questionnaires_impl.go b/model/questionnaires_impl.go index e7756bc7..386f6cc5 100755 --- a/model/questionnaires_impl.go +++ b/model/questionnaires_impl.go @@ -21,18 +21,19 @@ func NewQuestionnaire() *Questionnaire { // Questionnaires questionnairesテーブルの構造体 type Questionnaires struct { - ID int `json:"questionnaireID" gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` - Title string `json:"title" gorm:"type:char(50);size:50;not null"` - Description string `json:"description" gorm:"type:text;not null"` - ResTimeLimit null.Time `json:"res_time_limit,omitempty" gorm:"type:TIMESTAMP NULL;default:NULL;"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"type:TIMESTAMP NULL;default:NULL;"` - ResSharedTo string `json:"res_shared_to" gorm:"type:char(30);size:30;not null;default:administrators"` - CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;not null;default:CURRENT_TIMESTAMP"` - ModifiedAt time.Time `json:"modified_at" gorm:"type:timestamp;not null;default:CURRENT_TIMESTAMP"` - Administrators []Administrators `json:"-" gorm:"foreignKey:QuestionnaireID"` - Targets []Targets `json:"-" gorm:"foreignKey:QuestionnaireID"` - Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` - Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` + ID int `json:"questionnaireID" gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` + Title string `json:"title" gorm:"type:char(50);size:50;not null"` + Description string `json:"description" gorm:"type:text;not null"` + ResTimeLimit null.Time `json:"res_time_limit,omitempty" gorm:"type:TIMESTAMP NULL;default:NULL;"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"type:TIMESTAMP NULL;default:NULL;"` + ResSharedTo string `json:"res_shared_to" gorm:"type:char(30);size:30;not null;default:administrators"` + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed" gorm:"type:tinyint(4);size:4;not null;default:0"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;not null;default:CURRENT_TIMESTAMP"` + ModifiedAt time.Time `json:"modified_at" gorm:"type:timestamp;not null;default:CURRENT_TIMESTAMP"` + Administrators []Administrators `json:"-" gorm:"foreignKey:QuestionnaireID"` + Targets []Targets `json:"-" gorm:"foreignKey:QuestionnaireID"` + Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` + Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` } // BeforeCreate Update時に自動でmodified_atを現在時刻に @@ -79,7 +80,7 @@ type ResponseReadPrivilegeInfo struct { } // InsertQuestionnaire アンケートの追加 -func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string) (int, error) { +func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, isDuplicateAnswerAllowed bool) (int, error) { db, err := getTx(ctx) if err != nil { return 0, fmt.Errorf("failed to get tx: %w", err) @@ -88,16 +89,18 @@ func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, des var questionnaire Questionnaires if !resTimeLimit.Valid { questionnaire = Questionnaires{ - Title: title, - Description: description, - ResSharedTo: resSharedTo, + Title: title, + Description: description, + ResSharedTo: resSharedTo, + IsDuplicateAnswerAllowed: isDuplicateAnswerAllowed, } } else { questionnaire = Questionnaires{ - Title: title, - Description: description, - ResTimeLimit: resTimeLimit, - ResSharedTo: resSharedTo, + Title: title, + Description: description, + ResTimeLimit: resTimeLimit, + ResSharedTo: resSharedTo, + IsDuplicateAnswerAllowed: isDuplicateAnswerAllowed, } } @@ -110,7 +113,7 @@ func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, des } // UpdateQuestionnaire アンケートの更新 -func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, questionnaireID int) error { +func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, isDuplicateAnswerAllowed bool, questionnaireID int) error { db, err := getTx(ctx) if err != nil { return fmt.Errorf("failed to get tx: %w", err) @@ -119,17 +122,19 @@ func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, des var questionnaire interface{} if resTimeLimit.Valid { questionnaire = Questionnaires{ - Title: title, - Description: description, - ResTimeLimit: resTimeLimit, - ResSharedTo: resSharedTo, + Title: title, + Description: description, + ResTimeLimit: resTimeLimit, + ResSharedTo: resSharedTo, + IsDuplicateAnswerAllowed: isDuplicateAnswerAllowed, } } else { questionnaire = map[string]interface{}{ - "title": title, - "description": description, - "res_time_limit": gorm.Expr("NULL"), - "res_shared_to": resSharedTo, + "title": title, + "description": description, + "res_time_limit": gorm.Expr("NULL"), + "res_shared_to": resSharedTo, + "is_duplicate_answer_allowed": isDuplicateAnswerAllowed, } } diff --git a/model/questionnaires_test.go b/model/questionnaires_test.go index 16b80506..949a91b3 100644 --- a/model/questionnaires_test.go +++ b/model/questionnaires_test.go @@ -351,10 +351,11 @@ func insertQuestionnaireTest(t *testing.T) { assertion := assert.New(t) type args struct { - title string - description string - resTimeLimit null.Time - resSharedTo string + title string + description string + resTimeLimit null.Time + resSharedTo string + isDuplicateAnswerAllowed bool } type expect struct { isErr bool @@ -451,7 +452,7 @@ func insertQuestionnaireTest(t *testing.T) { for _, testCase := range testCases { ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, testCase.args.title, testCase.args.description, testCase.args.resTimeLimit, testCase.args.resSharedTo) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, testCase.args.title, testCase.args.description, testCase.args.resTimeLimit, testCase.args.resSharedTo, testCase.args.isDuplicateAnswerAllowed) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -475,6 +476,7 @@ func insertQuestionnaireTest(t *testing.T) { assertion.Equal(testCase.args.description, questionnaire.Description, testCase.description, "description") assertion.WithinDuration(testCase.args.resTimeLimit.ValueOrZero(), questionnaire.ResTimeLimit.ValueOrZero(), 2*time.Second, testCase.description, "res_time_limit") assertion.Equal(testCase.args.resSharedTo, questionnaire.ResSharedTo, testCase.description, "res_shared_to") + assertion.Equal(testCase.args.isDuplicateAnswerAllowed, questionnaire.IsDuplicateAnswerAllowed, testCase.description, "is_duplicate_answer_allowed") assertion.WithinDuration(time.Now(), questionnaire.CreatedAt, 2*time.Second, testCase.description, "created_at") assertion.WithinDuration(time.Now(), questionnaire.ModifiedAt, 2*time.Second, testCase.description, "modified_at") @@ -487,10 +489,11 @@ func updateQuestionnaireTest(t *testing.T) { assertion := assert.New(t) type args struct { - title string - description string - resTimeLimit null.Time - resSharedTo string + title string + description string + resTimeLimit null.Time + resSharedTo string + isDuplicateAnswerAllowed bool } type expect struct { isErr bool @@ -647,10 +650,11 @@ func updateQuestionnaireTest(t *testing.T) { before := &testCase.before questionnaire := Questionnaires{ - Title: before.title, - Description: before.description, - ResTimeLimit: before.resTimeLimit, - ResSharedTo: before.resSharedTo, + Title: before.title, + Description: before.description, + ResTimeLimit: before.resTimeLimit, + ResSharedTo: before.resSharedTo, + IsDuplicateAnswerAllowed: before.isDuplicateAnswerAllowed, } err := db. Session(&gorm.Session{NewDB: true}). @@ -662,7 +666,7 @@ func updateQuestionnaireTest(t *testing.T) { createdAt := questionnaire.CreatedAt questionnaireID := questionnaire.ID after := &testCase.after - err = questionnaireImpl.UpdateQuestionnaire(ctx, after.title, after.description, after.resTimeLimit, after.resSharedTo, questionnaireID) + err = questionnaireImpl.UpdateQuestionnaire(ctx, after.title, after.description, after.resTimeLimit, after.resSharedTo, after.isDuplicateAnswerAllowed, questionnaireID) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -686,6 +690,7 @@ func updateQuestionnaireTest(t *testing.T) { assertion.Equal(after.description, questionnaire.Description, testCase.description, "description") assertion.WithinDuration(after.resTimeLimit.ValueOrZero(), questionnaire.ResTimeLimit.ValueOrZero(), 2*time.Second, testCase.description, "res_time_limit") assertion.Equal(after.resSharedTo, questionnaire.ResSharedTo, testCase.description, "res_shared_to") + assertion.Equal(after.isDuplicateAnswerAllowed, questionnaire.IsDuplicateAnswerAllowed, testCase.description, "is_duplicate_answer_allowed") assertion.WithinDuration(createdAt, questionnaire.CreatedAt, 2*time.Second, testCase.description, "created_at") assertion.WithinDuration(time.Now(), questionnaire.ModifiedAt, 2*time.Second, testCase.description, "modified_at") @@ -726,7 +731,7 @@ func updateQuestionnaireTest(t *testing.T) { for _, arg := range invalidTestCases { ctx := context.Background() - err := questionnaireImpl.UpdateQuestionnaire(ctx, arg.title, arg.description, arg.resTimeLimit, arg.resSharedTo, invalidQuestionnaireID) + err := questionnaireImpl.UpdateQuestionnaire(ctx, arg.title, arg.description, arg.resTimeLimit, arg.resSharedTo, arg.isDuplicateAnswerAllowed, invalidQuestionnaireID) if !errors.Is(err, ErrNoRecordUpdated) { if err == nil { t.Errorf("Succeeded with invalid questionnaireID") @@ -743,10 +748,11 @@ func deleteQuestionnaireTest(t *testing.T) { assertion := assert.New(t) type args struct { - title string - description string - resTimeLimit null.Time - resSharedTo string + title string + description string + resTimeLimit null.Time + resSharedTo string + isDuplicateAnswerAllowed bool } type expect struct { isErr bool @@ -772,10 +778,11 @@ func deleteQuestionnaireTest(t *testing.T) { ctx := context.Background() questionnaire := Questionnaires{ - Title: testCase.args.title, - Description: testCase.args.description, - ResTimeLimit: testCase.args.resTimeLimit, - ResSharedTo: testCase.args.resSharedTo, + Title: testCase.args.title, + Description: testCase.args.description, + ResTimeLimit: testCase.args.resTimeLimit, + ResSharedTo: testCase.args.resSharedTo, + IsDuplicateAnswerAllowed: testCase.args.isDuplicateAnswerAllowed, } err := db. Session(&gorm.Session{NewDB: true}). @@ -1341,6 +1348,7 @@ func getQuestionnaireInfoTest(t *testing.T) { assertion.Equal(testCase.expect.questionnaire.Title, actualQuestionnaire.Title, testCase.description, "questionnaire(Title)") assertion.Equal(testCase.expect.questionnaire.Description, actualQuestionnaire.Description, testCase.description, "questionnaire(Description)") assertion.Equal(testCase.expect.questionnaire.ResSharedTo, actualQuestionnaire.ResSharedTo, testCase.description, "questionnaire(ResSharedTo)") + assertion.Equal(testCase.expect.questionnaire.IsDuplicateAnswerAllowed, actualQuestionnaire.IsDuplicateAnswerAllowed, testCase.description, "questionnaire(IsDuplicateAnswerAllowed)") assertion.WithinDuration(testCase.expect.questionnaire.ResTimeLimit.ValueOrZero(), actualQuestionnaire.ResTimeLimit.ValueOrZero(), 2*time.Second, testCase.description, "questionnaire(ResTimeLimit)") assertion.WithinDuration(testCase.expect.questionnaire.CreatedAt, actualQuestionnaire.CreatedAt, 2*time.Second, testCase.description, "questionnaire(CreatedAt)") assertion.WithinDuration(testCase.expect.questionnaire.ModifiedAt, actualQuestionnaire.ModifiedAt, 2*time.Second, testCase.description, "questionnaire(ModifiedAt)") diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 29019230..0b6bd005 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -72,7 +72,19 @@ func (*Respondent) InsertRespondent(ctx context.Context, userID string, question return 0, fmt.Errorf("failed to get tx: %w", err) } + var questionnaire Questionnaires var respondent Respondents + + err = db. + Where("id = ?", questionnaireID). + First(&questionnaire).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, ErrRecordNotFound + } + if err != nil { + return 0, fmt.Errorf("failed to get questionnaire: %w", err) + } + if submittedAt.Valid { respondent = Respondents{ QuestionnaireID: questionnaireID, @@ -86,12 +98,26 @@ func (*Respondent) InsertRespondent(ctx context.Context, userID string, question } } + if !questionnaire.IsDuplicateAnswerAllowed { + err = db. + Where("questionnaire_id = ? AND user_traqid = ?", questionnaireID, userID). + First(&Respondents{}).Error + if err == nil { + return 0, ErrDuplicatedAnswered + } + if !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, fmt.Errorf("failed to check duplicate answer: %w", err) + } + + } + err = db.Create(&respondent).Error if err != nil { return 0, fmt.Errorf("failed to insert a respondent record: %w", err) } return respondent.ResponseID, nil + } // UpdateSubmittedAt 投稿日時更新 diff --git a/model/respondents_test.go b/model/respondents_test.go index cafc7dd7..95820f64 100644 --- a/model/respondents_test.go +++ b/model/respondents_test.go @@ -19,7 +19,7 @@ func TestInsertRespondent(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -129,7 +129,7 @@ func TestUpdateSubmittedAt(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -203,7 +203,7 @@ func TestDeleteRespondent(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -390,9 +390,9 @@ func TestGetRespondentInfos(t *testing.T) { args expect } - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) - questionnaireID2, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID2, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) questionnaire := Questionnaires{} @@ -522,7 +522,7 @@ func TestGetRespondentDetail(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) require.NoError(t, err) questionnaire := Questionnaires{} @@ -619,7 +619,7 @@ func TestGetRespondentDetails(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) require.NoError(t, err) questionnaire := Questionnaires{} @@ -908,7 +908,7 @@ func TestGetRespondentsUserIDs(t *testing.T) { } questionnaireIDs := make([]int, 0, 3) for i := 0; i < 3; i++ { - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) questionnaireIDs = append(questionnaireIDs, questionnaireID) } @@ -996,7 +996,7 @@ func TestTestCheckRespondent(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) diff --git a/model/responses_test.go b/model/responses_test.go index 9790b350..ec0b5b1d 100644 --- a/model/responses_test.go +++ b/model/responses_test.go @@ -19,7 +19,7 @@ func TestInsertResponses(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -142,7 +142,7 @@ func TestDeleteResponse(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) diff --git a/model/scale_labels_test.go b/model/scale_labels_test.go index f4f79ccd..ad5ea98e 100644 --- a/model/scale_labels_test.go +++ b/model/scale_labels_test.go @@ -20,7 +20,7 @@ func TestInsertScaleLabel(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -163,7 +163,7 @@ func TestUpdateScaleLabel(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -283,7 +283,7 @@ func TestDeleteScaleLabel(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -371,7 +371,7 @@ func TestGetScaleLabels(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -489,7 +489,7 @@ func TestCheckScaleLabel(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) diff --git a/model/validations_test.go b/model/validations_test.go index a56434a3..54ab6548 100644 --- a/model/validations_test.go +++ b/model/validations_test.go @@ -20,7 +20,7 @@ func TestInsertValidation(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -212,7 +212,7 @@ func TestUpdateValidation(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -360,7 +360,7 @@ func TestDeleteValidation(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -458,7 +458,7 @@ func TestGetValidations(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public") + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) diff --git a/router/questionnaires.go b/router/questionnaires.go index b33db7f4..44dcd962 100644 --- a/router/questionnaires.go +++ b/router/questionnaires.go @@ -141,12 +141,13 @@ func (q *Questionnaire) GetQuestionnaires(c echo.Context) error { } type PostAndEditQuestionnaireRequest struct { - Title string `json:"title" validate:"required,max=50"` - Description string `json:"description"` - ResTimeLimit null.Time `json:"res_time_limit"` - ResSharedTo string `json:"res_shared_to" validate:"required,oneof=administrators respondents public"` - Targets []string `json:"targets" validate:"dive,max=32"` - Administrators []string `json:"administrators" validate:"required,min=1,dive,max=32"` + Title string `json:"title" validate:"required,max=50"` + Description string `json:"description"` + ResTimeLimit null.Time `json:"res_time_limit"` + ResSharedTo string `json:"res_shared_to" validate:"required,oneof=administrators respondents public"` + Targets []string `json:"targets" validate:"dive,max=32"` + Administrators []string `json:"administrators" validate:"required,min=1,dive,max=32"` + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed"` } // PostQuestionnaire POST /questionnaires @@ -182,7 +183,7 @@ func (q *Questionnaire) PostQuestionnaire(c echo.Context) error { var questionnaireID int err = q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - questionnaireID, err = q.InsertQuestionnaire(ctx, req.Title, req.Description, req.ResTimeLimit, req.ResSharedTo) + questionnaireID, err = q.InsertQuestionnaire(ctx, req.Title, req.Description, req.ResTimeLimit, req.ResSharedTo, req.IsDuplicateAnswerAllowed) if err != nil { c.Logger().Errorf("failed to insert a questionnaire: %+v", err) return err @@ -228,16 +229,17 @@ func (q *Questionnaire) PostQuestionnaire(c echo.Context) error { now := time.Now() return c.JSON(http.StatusCreated, map[string]interface{}{ - "questionnaireID": questionnaireID, - "title": req.Title, - "description": req.Description, - "res_time_limit": req.ResTimeLimit, - "deleted_at": "NULL", - "created_at": now.Format(time.RFC3339), - "modified_at": now.Format(time.RFC3339), - "res_shared_to": req.ResSharedTo, - "targets": req.Targets, - "administrators": req.Administrators, + "questionnaireID": questionnaireID, + "title": req.Title, + "description": req.Description, + "res_time_limit": req.ResTimeLimit, + "deleted_at": "NULL", + "created_at": now.Format(time.RFC3339), + "modified_at": now.Format(time.RFC3339), + "res_shared_to": req.ResSharedTo, + "targets": req.Targets, + "administrators": req.Administrators, + "is_duplicate_answer_allowed": req.IsDuplicateAnswerAllowed, }) } @@ -261,16 +263,17 @@ func (q *Questionnaire) GetQuestionnaire(c echo.Context) error { } return c.JSON(http.StatusOK, map[string]interface{}{ - "questionnaireID": questionnaire.ID, - "title": questionnaire.Title, - "description": questionnaire.Description, - "res_time_limit": questionnaire.ResTimeLimit, - "created_at": questionnaire.CreatedAt.Format(time.RFC3339), - "modified_at": questionnaire.ModifiedAt.Format(time.RFC3339), - "res_shared_to": questionnaire.ResSharedTo, - "targets": targets, - "administrators": administrators, - "respondents": respondents, + "questionnaireID": questionnaire.ID, + "title": questionnaire.Title, + "description": questionnaire.Description, + "res_time_limit": questionnaire.ResTimeLimit, + "created_at": questionnaire.CreatedAt.Format(time.RFC3339), + "modified_at": questionnaire.ModifiedAt.Format(time.RFC3339), + "res_shared_to": questionnaire.ResSharedTo, + "targets": targets, + "administrators": administrators, + "respondents": respondents, + "is_duplicate_answer_allowed": questionnaire.IsDuplicateAnswerAllowed, }) } @@ -409,7 +412,7 @@ func (q *Questionnaire) EditQuestionnaire(c echo.Context) error { } err = q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - err = q.UpdateQuestionnaire(ctx, req.Title, req.Description, req.ResTimeLimit, req.ResSharedTo, questionnaireID) + err = q.UpdateQuestionnaire(ctx, req.Title, req.Description, req.ResTimeLimit, req.ResSharedTo, req.IsDuplicateAnswerAllowed, questionnaireID) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to update questionnaire: %+v", err) return err diff --git a/router/questionnaires_test.go b/router/questionnaires_test.go index 8e934895..c542dbb5 100644 --- a/router/questionnaires_test.go +++ b/router/questionnaires_test.go @@ -601,6 +601,7 @@ func TestPostQuestionnaire(t *testing.T) { testCase.request.Description, mockTimeLimit, testCase.request.ResSharedTo, + testCase.request.IsDuplicateAnswerAllowed, ). Return(testCase.questionnaireID, testCase.InsertQuestionnaireError) @@ -659,6 +660,7 @@ func TestPostQuestionnaire(t *testing.T) { assert.Nil(t, questionnaire["res_time_limit"], "resTimeLimit nil") } assert.Equal(t, testCase.request.ResSharedTo, questionnaire["res_shared_to"], "resSharedTo") + assert.Equal(t, testCase.request.IsDuplicateAnswerAllowed, questionnaire["is_duplicate_answer_allowed"], "isDuplicateAnswerAllowed") strCreatedAt, ok := questionnaire["created_at"].(string) assert.True(t, ok, "created_at convert") @@ -1489,6 +1491,7 @@ func TestEditQuestionnaire(t *testing.T) { testCase.request.Description, mockTimeLimit, testCase.request.ResSharedTo, + testCase.request.IsDuplicateAnswerAllowed, testCase.questionnaireID, ). Return(testCase.InsertQuestionnaireError) diff --git a/router/responses.go b/router/responses.go index 81d11d64..d741ae8f 100644 --- a/router/responses.go +++ b/router/responses.go @@ -166,6 +166,9 @@ func (r *Response) PostResponse(c echo.Context) error { } responseID, err := r.InsertRespondent(c.Request().Context(), userID, req.ID, null.NewTime(submittedAt, !req.Temporarily)) + if errors.Is(err, model.ErrDuplicatedAnswered) { + return echo.NewHTTPError(http.StatusConflict, err) + } if err != nil { c.Logger().Errorf("failed to insert respondent: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, err)