From a4bd5cc24f03b258ed5053ddcbda082b997756a8 Mon Sep 17 00:00:00 2001 From: ramdos0207 Date: Wed, 20 Dec 2023 23:57:15 +0900 Subject: [PATCH 01/83] add isDuplicateAnswerAllowed --- model/questionnaires.go | 4 +-- model/questionnaires_impl.go | 63 +++++++++++++++++++---------------- model/questionnaires_test.go | 54 +++++++++++++++++------------- model/respondents_test.go | 18 +++++----- model/responses_test.go | 4 +-- model/scale_labels_test.go | 10 +++--- model/validations_test.go | 8 ++--- router/questionnaires.go | 59 ++++++++++++++++---------------- router/questionnaires_test.go | 3 ++ 9 files changed, 121 insertions(+), 102 deletions(-) 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_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) From eb083bdbc28904beb55c0a9dfae664c578dbf577 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Thu, 2 May 2024 13:26:09 +0900 Subject: [PATCH 02/83] impl delete duplicate answer --- model/respondents_impl.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 29019230..74b14742 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,28 @@ func (*Respondent) InsertRespondent(ctx context.Context, userID string, question } } + if !questionnaire.IsDuplicateAnswerAllowed && submittedAt.Valid { + // delete old answers + err = db. + Where("questionnaire_id = ? AND user_traqid = ?", questionnaireID, userID). + Delete(&Respondents{}).Error + // 既存の回答がなかった場合はそのまま進む + if errors.Is(err, ErrNoRecordDeleted) { + err = nil + } + if err != nil { + return 0, fmt.Errorf("failed to delete old answers: %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 投稿日時更新 From 8062e5d57c52382fe9355e8f6ee67816994f38d9 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Thu, 2 May 2024 14:59:25 +0900 Subject: [PATCH 03/83] change error handling --- model/errors.go | 2 ++ model/respondents_impl.go | 14 ++++++-------- router/responses.go | 3 +++ 3 files changed, 11 insertions(+), 8 deletions(-) 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/respondents_impl.go b/model/respondents_impl.go index 74b14742..0b6bd005 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -98,17 +98,15 @@ func (*Respondent) InsertRespondent(ctx context.Context, userID string, question } } - if !questionnaire.IsDuplicateAnswerAllowed && submittedAt.Valid { - // delete old answers + if !questionnaire.IsDuplicateAnswerAllowed { err = db. Where("questionnaire_id = ? AND user_traqid = ?", questionnaireID, userID). - Delete(&Respondents{}).Error - // 既存の回答がなかった場合はそのまま進む - if errors.Is(err, ErrNoRecordDeleted) { - err = nil + First(&Respondents{}).Error + if err == nil { + return 0, ErrDuplicatedAnswered } - if err != nil { - return 0, fmt.Errorf("failed to delete old answers: %w", err) + if !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, fmt.Errorf("failed to check duplicate answer: %w", err) } } 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) From 98c17acaca27ed821066de83082336ad040311ba Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Fri, 3 May 2024 11:18:55 +0900 Subject: [PATCH 04/83] setup oapi codegen --- handler/handler.go | 3 + handler/questionnaire.go | 70 ++ handler/response.go | 30 + main.go | 9 +- openapi/server.go | 401 ++++++++++ openapi/spec.go | 162 ++++ openapi/types.go | 1524 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 2198 insertions(+), 1 deletion(-) create mode 100644 handler/handler.go create mode 100644 handler/questionnaire.go create mode 100644 handler/response.go create mode 100644 openapi/server.go create mode 100644 openapi/spec.go create mode 100644 openapi/types.go diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 00000000..f620ece3 --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,3 @@ +package handler + +type Handler struct{} diff --git a/handler/questionnaire.go b/handler/questionnaire.go new file mode 100644 index 00000000..c58ad897 --- /dev/null +++ b/handler/questionnaire.go @@ -0,0 +1,70 @@ +package handler + +import ( + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/openapi" +) + +// (GET /questionnaires) +func (h Handler) GetQuestionnaires(ctx echo.Context, params openapi.GetQuestionnairesParams) error { + res := openapi.QuestionnaireList{} + + return ctx.JSON(200, res) +} + +// (POST /questionnaires) +func (h Handler) PostQuestionnaire(ctx echo.Context) error { + res := openapi.QuestionnaireDetail{} + + return ctx.JSON(200, res) +} + +// (GET /questionnaires/{questionnaireID}) +func (h Handler) GetQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + res := openapi.QuestionnaireDetail{} + + return ctx.JSON(200, res) +} + +// (PATCH /questionnaires/{questionnaireID}) +func (h Handler) EditQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + return ctx.NoContent(200) +} + +// (DELETE /questionnaires/{questionnaireID}) +func (h Handler) DeleteQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + return ctx.NoContent(200) +} + +// (GET /questionnaires/{questionnaireID}/myRemindStatus) +func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + res := openapi.QuestionnaireIsRemindEnabled{} + + return ctx.JSON(200, res) +} + +// (PATCH /questionnaires/{questionnaireID}/myRemindStatus) +func (h Handler) EditQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + return ctx.NoContent(200) +} + +// (GET /questionnaires/{questionnaireID}/responses) +func (h Handler) GetQuestionnaireResponses(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath, params openapi.GetQuestionnaireResponsesParams) error { + res := openapi.Responses{} + + return ctx.JSON(200, res) +} + +// (POST /questionnaires/{questionnaireID}/responses) +func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + res := openapi.Response{} + + return ctx.JSON(201, res) +} + +// (GET /questionnaires/{questionnaireID}/result) +func (h Handler) GetQuestionnaireResult(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + res := openapi.Result{} + + return ctx.JSON(200, res) +} diff --git a/handler/response.go b/handler/response.go new file mode 100644 index 00000000..6212214f --- /dev/null +++ b/handler/response.go @@ -0,0 +1,30 @@ +package handler + +import ( + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/openapi" +) + +// (GET /responses/myResponses) +func (h Handler) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesParams) error { + res := []openapi.ResponsesWithQuestionnaireInfo{} + + return ctx.JSON(200, res) +} + +// (DELETE /responses/{responseID}) +func (h Handler) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { + return ctx.NoContent(200) +} + +// (GET /responses/{responseID}) +func (h Handler) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { + res := openapi.Response{} + + return ctx.JSON(200, res) +} + +// (PATCH /responses/{responseID}) +func (h Handler) EditResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { + return ctx.NoContent(200) +} diff --git a/main.go b/main.go index f5457edc..5c1be0ed 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,10 @@ import ( "os" "runtime" + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/handler" "github.com/traPtitech/anke-to/model" + "github.com/traPtitech/anke-to/openapi" "github.com/traPtitech/anke-to/tuning" ) @@ -51,5 +54,9 @@ func main() { panic("no PORT") } - SetRouting(port) + e := echo.New() + openapi.RegisterHandlers(e, handler.Handler{}) + e.Logger.Fatal(e.Start(":" + port)) + + // SetRouting(port) } diff --git a/openapi/server.go b/openapi/server.go new file mode 100644 index 00000000..5bc86917 --- /dev/null +++ b/openapi/server.go @@ -0,0 +1,401 @@ +// Package openapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT. +package openapi + +import ( + "fmt" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /questionnaires) + GetQuestionnaires(ctx echo.Context, params GetQuestionnairesParams) error + + // (POST /questionnaires) + PostQuestionnaire(ctx echo.Context) error + + // (DELETE /questionnaires/{questionnaireID}) + DeleteQuestionnaire(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (GET /questionnaires/{questionnaireID}) + GetQuestionnaire(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (PATCH /questionnaires/{questionnaireID}) + EditQuestionnaire(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (GET /questionnaires/{questionnaireID}/myRemindStatus) + GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (PATCH /questionnaires/{questionnaireID}/myRemindStatus) + EditQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (GET /questionnaires/{questionnaireID}/responses) + GetQuestionnaireResponses(ctx echo.Context, questionnaireID QuestionnaireIDInPath, params GetQuestionnaireResponsesParams) error + + // (POST /questionnaires/{questionnaireID}/responses) + PostQuestionnaireResponse(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (GET /questionnaires/{questionnaireID}/result) + GetQuestionnaireResult(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error + + // (GET /responses/myResponses) + GetMyResponses(ctx echo.Context, params GetMyResponsesParams) error + + // (DELETE /responses/{responseID}) + DeleteResponse(ctx echo.Context, responseID ResponseIDInPath) error + + // (GET /responses/{responseID}) + GetResponse(ctx echo.Context, responseID ResponseIDInPath) error + + // (PATCH /responses/{responseID}) + EditResponse(ctx echo.Context, responseID ResponseIDInPath) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetQuestionnaires converts echo context to params. +func (w *ServerInterfaceWrapper) GetQuestionnaires(ctx echo.Context) error { + var err error + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetQuestionnairesParams + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", ctx.QueryParams(), ¶ms.Sort) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter sort: %s", err)) + } + + // ------------- Optional query parameter "search" ------------- + + err = runtime.BindQueryParameter("form", true, false, "search", ctx.QueryParams(), ¶ms.Search) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter search: %s", err)) + } + + // ------------- Optional query parameter "page" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page", ctx.QueryParams(), ¶ms.Page) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter page: %s", err)) + } + + // ------------- Optional query parameter "onlyTargetingMe" ------------- + + err = runtime.BindQueryParameter("form", true, false, "onlyTargetingMe", ctx.QueryParams(), ¶ms.OnlyTargetingMe) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter onlyTargetingMe: %s", err)) + } + + // ------------- Optional query parameter "onlyAdministratedByMe" ------------- + + err = runtime.BindQueryParameter("form", true, false, "onlyAdministratedByMe", ctx.QueryParams(), ¶ms.OnlyAdministratedByMe) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter onlyAdministratedByMe: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetQuestionnaires(ctx, params) + return err +} + +// PostQuestionnaire converts echo context to params. +func (w *ServerInterfaceWrapper) PostQuestionnaire(ctx echo.Context) error { + var err error + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostQuestionnaire(ctx) + return err +} + +// DeleteQuestionnaire converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteQuestionnaire(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteQuestionnaire(ctx, questionnaireID) + return err +} + +// GetQuestionnaire converts echo context to params. +func (w *ServerInterfaceWrapper) GetQuestionnaire(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetQuestionnaire(ctx, questionnaireID) + return err +} + +// EditQuestionnaire converts echo context to params. +func (w *ServerInterfaceWrapper) EditQuestionnaire(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.EditQuestionnaire(ctx, questionnaireID) + return err +} + +// GetQuestionnaireMyRemindStatus converts echo context to params. +func (w *ServerInterfaceWrapper) GetQuestionnaireMyRemindStatus(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetQuestionnaireMyRemindStatus(ctx, questionnaireID) + return err +} + +// EditQuestionnaireMyRemindStatus converts echo context to params. +func (w *ServerInterfaceWrapper) EditQuestionnaireMyRemindStatus(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.EditQuestionnaireMyRemindStatus(ctx, questionnaireID) + return err +} + +// GetQuestionnaireResponses converts echo context to params. +func (w *ServerInterfaceWrapper) GetQuestionnaireResponses(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetQuestionnaireResponsesParams + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", ctx.QueryParams(), ¶ms.Sort) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter sort: %s", err)) + } + + // ------------- Optional query parameter "onlyMyResponse" ------------- + + err = runtime.BindQueryParameter("form", true, false, "onlyMyResponse", ctx.QueryParams(), ¶ms.OnlyMyResponse) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter onlyMyResponse: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetQuestionnaireResponses(ctx, questionnaireID, params) + return err +} + +// PostQuestionnaireResponse converts echo context to params. +func (w *ServerInterfaceWrapper) PostQuestionnaireResponse(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostQuestionnaireResponse(ctx, questionnaireID) + return err +} + +// GetQuestionnaireResult converts echo context to params. +func (w *ServerInterfaceWrapper) GetQuestionnaireResult(ctx echo.Context) error { + var err error + // ------------- Path parameter "questionnaireID" ------------- + var questionnaireID QuestionnaireIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetQuestionnaireResult(ctx, questionnaireID) + return err +} + +// GetMyResponses converts echo context to params. +func (w *ServerInterfaceWrapper) GetMyResponses(ctx echo.Context) error { + var err error + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetMyResponsesParams + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", ctx.QueryParams(), ¶ms.Sort) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter sort: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetMyResponses(ctx, params) + return err +} + +// DeleteResponse converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteResponse(ctx echo.Context) error { + var err error + // ------------- Path parameter "responseID" ------------- + var responseID ResponseIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "responseID", runtime.ParamLocationPath, ctx.Param("responseID"), &responseID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter responseID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteResponse(ctx, responseID) + return err +} + +// GetResponse converts echo context to params. +func (w *ServerInterfaceWrapper) GetResponse(ctx echo.Context) error { + var err error + // ------------- Path parameter "responseID" ------------- + var responseID ResponseIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "responseID", runtime.ParamLocationPath, ctx.Param("responseID"), &responseID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter responseID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetResponse(ctx, responseID) + return err +} + +// EditResponse converts echo context to params. +func (w *ServerInterfaceWrapper) EditResponse(ctx echo.Context) error { + var err error + // ------------- Path parameter "responseID" ------------- + var responseID ResponseIDInPath + + err = runtime.BindStyledParameterWithLocation("simple", false, "responseID", runtime.ParamLocationPath, ctx.Param("responseID"), &responseID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter responseID: %s", err)) + } + + ctx.Set(ApplicationScopes, []string{"read", "write"}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.EditResponse(ctx, responseID) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/questionnaires", wrapper.GetQuestionnaires) + router.POST(baseURL+"/questionnaires", wrapper.PostQuestionnaire) + router.DELETE(baseURL+"/questionnaires/:questionnaireID", wrapper.DeleteQuestionnaire) + router.GET(baseURL+"/questionnaires/:questionnaireID", wrapper.GetQuestionnaire) + router.PATCH(baseURL+"/questionnaires/:questionnaireID", wrapper.EditQuestionnaire) + router.GET(baseURL+"/questionnaires/:questionnaireID/myRemindStatus", wrapper.GetQuestionnaireMyRemindStatus) + router.PATCH(baseURL+"/questionnaires/:questionnaireID/myRemindStatus", wrapper.EditQuestionnaireMyRemindStatus) + router.GET(baseURL+"/questionnaires/:questionnaireID/responses", wrapper.GetQuestionnaireResponses) + router.POST(baseURL+"/questionnaires/:questionnaireID/responses", wrapper.PostQuestionnaireResponse) + router.GET(baseURL+"/questionnaires/:questionnaireID/result", wrapper.GetQuestionnaireResult) + router.GET(baseURL+"/responses/myResponses", wrapper.GetMyResponses) + router.DELETE(baseURL+"/responses/:responseID", wrapper.DeleteResponse) + router.GET(baseURL+"/responses/:responseID", wrapper.GetResponse) + router.PATCH(baseURL+"/responses/:responseID", wrapper.EditResponse) + +} diff --git a/openapi/spec.go b/openapi/spec.go new file mode 100644 index 00000000..482a476c --- /dev/null +++ b/openapi/spec.go @@ -0,0 +1,162 @@ +// Package openapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT. +package openapi + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "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", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/openapi/types.go b/openapi/types.go new file mode 100644 index 00000000..4905ca5b --- /dev/null +++ b/openapi/types.go @@ -0,0 +1,1524 @@ +// Package openapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT. +package openapi + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/oapi-codegen/runtime" +) + +const ( + ApplicationScopes = "application.Scopes" +) + +// Defines values for QuestionSettingsMultipleChoiceQuestionType. +const ( + QuestionSettingsMultipleChoiceQuestionTypeMultipleChoice QuestionSettingsMultipleChoiceQuestionType = "MultipleChoice" +) + +// Defines values for QuestionSettingsNumberQuestionType. +const ( + QuestionSettingsNumberQuestionTypeNumber QuestionSettingsNumberQuestionType = "Number" +) + +// Defines values for QuestionSettingsScaleQuestionType. +const ( + QuestionSettingsScaleQuestionTypeScale QuestionSettingsScaleQuestionType = "Scale" +) + +// Defines values for QuestionSettingsSingleChoiceQuestionType. +const ( + QuestionSettingsSingleChoiceQuestionTypeSingleChoice QuestionSettingsSingleChoiceQuestionType = "SingleChoice" +) + +// Defines values for QuestionSettingsTextQuestionType. +const ( + QuestionSettingsTextQuestionTypeText QuestionSettingsTextQuestionType = "Text" +) + +// Defines values for QuestionSettingsTextLongQuestionType. +const ( + QuestionSettingsTextLongQuestionTypeTextLong QuestionSettingsTextLongQuestionType = "TextLong" +) + +// Defines values for QuestionTypeMultipleChoiceQuestionType. +const ( + QuestionTypeMultipleChoiceQuestionTypeMultipleChoice QuestionTypeMultipleChoiceQuestionType = "MultipleChoice" +) + +// Defines values for QuestionTypeNumberQuestionType. +const ( + QuestionTypeNumberQuestionTypeNumber QuestionTypeNumberQuestionType = "Number" +) + +// Defines values for QuestionTypeScaleQuestionType. +const ( + QuestionTypeScaleQuestionTypeScale QuestionTypeScaleQuestionType = "Scale" +) + +// Defines values for QuestionTypeSingleChoiceQuestionType. +const ( + QuestionTypeSingleChoiceQuestionTypeSingleChoice QuestionTypeSingleChoiceQuestionType = "SingleChoice" +) + +// Defines values for QuestionTypeTextQuestionType. +const ( + QuestionTypeTextQuestionTypeText QuestionTypeTextQuestionType = "Text" +) + +// Defines values for QuestionTypeTextLongQuestionType. +const ( + QuestionTypeTextLongQuestionTypeTextLong QuestionTypeTextLongQuestionType = "TextLong" +) + +// Defines values for ResShareType. +const ( + Admins ResShareType = "admins" + Anyone ResShareType = "anyone" + Respondents ResShareType = "respondents" +) + +// Defines values for ResponseBodyMultipleChoiceQuestionType. +const ( + MultipleChoice ResponseBodyMultipleChoiceQuestionType = "MultipleChoice" +) + +// Defines values for ResponseBodyNumberQuestionType. +const ( + Number ResponseBodyNumberQuestionType = "Number" +) + +// Defines values for ResponseBodyScaleQuestionType. +const ( + Scale ResponseBodyScaleQuestionType = "Scale" +) + +// Defines values for ResponseBodySingleChoiceQuestionType. +const ( + SingleChoice ResponseBodySingleChoiceQuestionType = "SingleChoice" +) + +// Defines values for ResponseBodyTextQuestionType. +const ( + Text ResponseBodyTextQuestionType = "Text" +) + +// Defines values for ResponseBodyTextLongQuestionType. +const ( + TextLong ResponseBodyTextLongQuestionType = "TextLong" +) + +// Defines values for ResponseSortType. +const ( + ResponseSortTypeModifiedAtASC ResponseSortType = "modified_at" + ResponseSortTypeModifiedAtDESC ResponseSortType = "-modified_at" + ResponseSortTypeSubmittedAtASC ResponseSortType = "submitted_at" + ResponseSortTypeSubmittedAtDESC ResponseSortType = "-submitted_at" + ResponseSortTypeTitleASC ResponseSortType = "title" + ResponseSortTypeTitleDESC ResponseSortType = "-title" +) + +// Defines values for SortType. +const ( + SortTypeCreatedAtASC SortType = "created_at" + SortTypeCreatedAtDESC SortType = "-created_at" + SortTypeModifiedAtASC SortType = "modified_at" + SortTypeModifiedAtDESC SortType = "-modified_at" + SortTypeTitleASC SortType = "title" + SortTypeTitleDESC SortType = "-title" +) + +// Groups defines model for Groups. +type Groups = []string + +// NewQuestion defines model for NewQuestion. +type NewQuestion struct { + Description string `json:"description"` + + // IsRequired 回答必須かどうか + IsRequired bool `json:"is_required"` + QuestionnaireId int `json:"questionnaire_id"` + Title string `json:"title"` + union json.RawMessage +} + +// NewQuestionnaire defines model for NewQuestionnaire. +type NewQuestionnaire struct { + Admins UsersAndGroups `json:"admins"` + Description string `json:"description"` + + // IsAllowingMultipleResponses 一人が複数回回答できるかどうか + IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` + + // IsAnonymous 匿名回答かどうか + IsAnonymous bool `json:"is_anonymous"` + + // IsPublished アンケートが公開されているかどうか + IsPublished bool `json:"is_published"` + Questions []NewQuestion `json:"questions"` + + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + + // ResponseViewableBy アンケートの結果を, 運営は見られる ("admins"), 回答済みの人は見られる ("respondents") 誰でも見られる ("anyone") + ResponseViewableBy ResShareType `json:"response_viewable_by"` + Targets UsersAndGroups `json:"targets"` + Title string `json:"title"` +} + +// NewResponse defines model for NewResponse. +type NewResponse struct { + Body []ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` +} + +// Question defines model for Question. +type Question struct { + CreatedAt time.Time `json:"created_at"` + Description string `json:"description"` + + // IsRequired 回答必須かどうか + IsRequired bool `json:"is_required"` + QuestionId int `json:"question_id"` + QuestionnaireId int `json:"questionnaire_id"` + Title string `json:"title"` + union json.RawMessage +} + +// QuestionBase defines model for QuestionBase. +type QuestionBase struct { + Description string `json:"description"` + + // IsRequired 回答必須かどうか + IsRequired bool `json:"is_required"` + QuestionnaireId int `json:"questionnaire_id"` + Title string `json:"title"` +} + +// QuestionSettingsByType defines model for QuestionSettingsByType. +type QuestionSettingsByType struct { + union json.RawMessage +} + +// QuestionSettingsMultipleChoice defines model for QuestionSettingsMultipleChoice. +type QuestionSettingsMultipleChoice struct { + Options []string `json:"options"` + QuestionType QuestionSettingsMultipleChoiceQuestionType `json:"question_type"` +} + +// QuestionSettingsMultipleChoiceQuestionType defines model for QuestionSettingsMultipleChoice.QuestionType. +type QuestionSettingsMultipleChoiceQuestionType string + +// QuestionSettingsNumber defines model for QuestionSettingsNumber. +type QuestionSettingsNumber struct { + MaxValue *int `json:"max_value,omitempty"` + MinValue *int `json:"min_value,omitempty"` + QuestionType QuestionSettingsNumberQuestionType `json:"question_type"` +} + +// QuestionSettingsNumberQuestionType defines model for QuestionSettingsNumber.QuestionType. +type QuestionSettingsNumberQuestionType string + +// QuestionSettingsScale defines model for QuestionSettingsScale. +type QuestionSettingsScale struct { + MaxLabel *string `json:"max_label,omitempty"` + MaxValue int `json:"max_value"` + MinLabel *string `json:"min_label,omitempty"` + MinValue int `json:"min_value"` + QuestionType QuestionSettingsScaleQuestionType `json:"question_type"` +} + +// QuestionSettingsScaleQuestionType defines model for QuestionSettingsScale.QuestionType. +type QuestionSettingsScaleQuestionType string + +// QuestionSettingsSingleChoice defines model for QuestionSettingsSingleChoice. +type QuestionSettingsSingleChoice struct { + Options []string `json:"options"` + QuestionType QuestionSettingsSingleChoiceQuestionType `json:"question_type"` +} + +// QuestionSettingsSingleChoiceQuestionType defines model for QuestionSettingsSingleChoice.QuestionType. +type QuestionSettingsSingleChoiceQuestionType string + +// QuestionSettingsText defines model for QuestionSettingsText. +type QuestionSettingsText struct { + MaxLength *int `json:"max_length,omitempty"` + QuestionType QuestionSettingsTextQuestionType `json:"question_type"` +} + +// QuestionSettingsTextQuestionType defines model for QuestionSettingsText.QuestionType. +type QuestionSettingsTextQuestionType string + +// QuestionSettingsTextLong defines model for QuestionSettingsTextLong. +type QuestionSettingsTextLong struct { + MaxLength *float32 `json:"max_length,omitempty"` + QuestionType QuestionSettingsTextLongQuestionType `json:"question_type"` +} + +// QuestionSettingsTextLongQuestionType defines model for QuestionSettingsTextLong.QuestionType. +type QuestionSettingsTextLongQuestionType string + +// QuestionTypeMultipleChoice defines model for QuestionTypeMultipleChoice. +type QuestionTypeMultipleChoice struct { + QuestionType QuestionTypeMultipleChoiceQuestionType `json:"question_type"` +} + +// QuestionTypeMultipleChoiceQuestionType defines model for QuestionTypeMultipleChoice.QuestionType. +type QuestionTypeMultipleChoiceQuestionType string + +// QuestionTypeNumber defines model for QuestionTypeNumber. +type QuestionTypeNumber struct { + QuestionType QuestionTypeNumberQuestionType `json:"question_type"` +} + +// QuestionTypeNumberQuestionType defines model for QuestionTypeNumber.QuestionType. +type QuestionTypeNumberQuestionType string + +// QuestionTypeScale defines model for QuestionTypeScale. +type QuestionTypeScale struct { + QuestionType QuestionTypeScaleQuestionType `json:"question_type"` +} + +// QuestionTypeScaleQuestionType defines model for QuestionTypeScale.QuestionType. +type QuestionTypeScaleQuestionType string + +// QuestionTypeSingleChoice defines model for QuestionTypeSingleChoice. +type QuestionTypeSingleChoice struct { + QuestionType QuestionTypeSingleChoiceQuestionType `json:"question_type"` +} + +// QuestionTypeSingleChoiceQuestionType defines model for QuestionTypeSingleChoice.QuestionType. +type QuestionTypeSingleChoiceQuestionType string + +// QuestionTypeText defines model for QuestionTypeText. +type QuestionTypeText struct { + QuestionType QuestionTypeTextQuestionType `json:"question_type"` +} + +// QuestionTypeTextQuestionType defines model for QuestionTypeText.QuestionType. +type QuestionTypeTextQuestionType string + +// QuestionTypeTextLong defines model for QuestionTypeTextLong. +type QuestionTypeTextLong struct { + QuestionType QuestionTypeTextLongQuestionType `json:"question_type"` +} + +// QuestionTypeTextLongQuestionType defines model for QuestionTypeTextLong.QuestionType. +type QuestionTypeTextLongQuestionType string + +// QuestionnaireBase defines model for QuestionnaireBase. +type QuestionnaireBase struct { + Admins UsersAndGroups `json:"admins"` + Description string `json:"description"` + + // IsAllowingMultipleResponses 一人が複数回回答できるかどうか + IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` + + // IsAnonymous 匿名回答かどうか + IsAnonymous bool `json:"is_anonymous"` + + // IsPublished アンケートが公開されているかどうか + IsPublished bool `json:"is_published"` + + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + + // ResponseViewableBy アンケートの結果を, 運営は見られる ("admins"), 回答済みの人は見られる ("respondents") 誰でも見られる ("anyone") + ResponseViewableBy ResShareType `json:"response_viewable_by"` + Targets UsersAndGroups `json:"targets"` + Title string `json:"title"` +} + +// QuestionnaireCreatedAt defines model for QuestionnaireCreatedAt. +type QuestionnaireCreatedAt struct { + CreatedAt time.Time `json:"created_at"` +} + +// QuestionnaireDescription defines model for QuestionnaireDescription. +type QuestionnaireDescription struct { + Description string `json:"description"` +} + +// QuestionnaireDetail defines model for QuestionnaireDetail. +type QuestionnaireDetail struct { + Admins UsersAndGroups `json:"admins"` + CreatedAt time.Time `json:"created_at"` + Description string `json:"description"` + + // IsAllowingMultipleResponses 一人が複数回回答できるかどうか + IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` + + // IsAnonymous 匿名回答かどうか + IsAnonymous bool `json:"is_anonymous"` + + // IsPublished アンケートが公開されているかどうか + IsPublished bool `json:"is_published"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + Questions []Question `json:"questions"` + Respondents Users `json:"respondents"` + + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + + // ResponseViewableBy アンケートの結果を, 運営は見られる ("admins"), 回答済みの人は見られる ("respondents") 誰でも見られる ("anyone") + ResponseViewableBy ResShareType `json:"response_viewable_by"` + Targets UsersAndGroups `json:"targets"` + Title string `json:"title"` +} + +// QuestionnaireID defines model for QuestionnaireID. +type QuestionnaireID struct { + QuestionnaireId int `json:"questionnaire_id"` +} + +// QuestionnaireIsAllowingMultipleResponses defines model for QuestionnaireIsAllowingMultipleResponses. +type QuestionnaireIsAllowingMultipleResponses struct { + // IsAllowingMultipleResponses 一人が複数回回答できるかどうか + IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` +} + +// QuestionnaireIsAnonymous defines model for QuestionnaireIsAnonymous. +type QuestionnaireIsAnonymous struct { + // IsAnonymous 匿名回答かどうか + IsAnonymous bool `json:"is_anonymous"` +} + +// QuestionnaireIsPublished defines model for QuestionnaireIsPublished. +type QuestionnaireIsPublished struct { + // IsPublished アンケートが公開されているかどうか + IsPublished bool `json:"is_published"` +} + +// QuestionnaireIsRemindEnabled defines model for QuestionnaireIsRemindEnabled. +type QuestionnaireIsRemindEnabled struct { + // IsRemindEnabled 自分に対するリマインドが有効かどうか。ユーザーが対象者でありかつ回答していない場合、この値がtrueであればリマインドが送信される。 + IsRemindEnabled bool `json:"is_remind_enabled"` +} + +// QuestionnaireIsTargetingMe defines model for QuestionnaireIsTargetingMe. +type QuestionnaireIsTargetingMe struct { + // IsTargetingMe 自分がターゲットになっているかどうか + IsTargetingMe bool `json:"is_targeting_me"` +} + +// QuestionnaireList defines model for QuestionnaireList. +type QuestionnaireList struct { + // PageMax 合計のページ数 + PageMax int `json:"page_max"` + Questionnaires []QuestionnaireSummary `json:"questionnaires"` +} + +// QuestionnaireModifiedAt defines model for QuestionnaireModifiedAt. +type QuestionnaireModifiedAt struct { + ModifiedAt time.Time `json:"modified_at"` +} + +// QuestionnaireResponseDueDateTime defines model for QuestionnaireResponseDueDateTime. +type QuestionnaireResponseDueDateTime struct { + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` +} + +// QuestionnaireResponseViewableBy defines model for QuestionnaireResponseViewableBy. +type QuestionnaireResponseViewableBy struct { + // ResponseViewableBy アンケートの結果を, 運営は見られる ("admins"), 回答済みの人は見られる ("respondents") 誰でも見られる ("anyone") + ResponseViewableBy ResShareType `json:"response_viewable_by"` +} + +// QuestionnaireSummary defines model for QuestionnaireSummary. +type QuestionnaireSummary struct { + // AllResponded すべての対象者が回答済みの場合 true を返す。それ以外は false を返す。 (対象者が存在しない場合は true を返す) + AllResponded bool `json:"all_responded"` + CreatedAt time.Time `json:"created_at"` + Description string `json:"description"` + + // HasMyDraft 下書きが存在する + HasMyDraft bool `json:"has_my_draft"` + + // HasMyResponse 回答が存在する + HasMyResponse bool `json:"has_my_response"` + + // IsAllowingMultipleResponses 一人が複数回回答できるかどうか + IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` + + // IsAnonymous 匿名回答かどうか + IsAnonymous bool `json:"is_anonymous"` + + // IsPublished アンケートが公開されているかどうか + IsPublished bool `json:"is_published"` + + // IsTargetingMe 自分がターゲットになっているかどうか + IsTargetingMe bool `json:"is_targeting_me"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + RespondedDateTimeByMe *time.Time `json:"responded_date_time_by_me,omitempty"` + + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + + // ResponseViewableBy アンケートの結果を, 運営は見られる ("admins"), 回答済みの人は見られる ("respondents") 誰でも見られる ("anyone") + ResponseViewableBy ResShareType `json:"response_viewable_by"` + Title string `json:"title"` +} + +// QuestionnaireTargetsAndAdmins defines model for QuestionnaireTargetsAndAdmins. +type QuestionnaireTargetsAndAdmins struct { + Admins UsersAndGroups `json:"admins"` + Targets UsersAndGroups `json:"targets"` +} + +// QuestionnaireTitle defines model for QuestionnaireTitle. +type QuestionnaireTitle struct { + Title string `json:"title"` +} + +// ResShareType アンケートの結果を, 運営は見られる ("admins"), 回答済みの人は見られる ("respondents") 誰でも見られる ("anyone") +type ResShareType string + +// Response defines model for Response. +type Response struct { + Body []ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + + // Respondent traQ ID + Respondent TraqId `json:"respondent"` + ResponseId int `json:"response_id"` + SubmittedAt time.Time `json:"submitted_at"` +} + +// ResponseBody defines model for ResponseBody. +type ResponseBody struct { + union json.RawMessage +} + +// ResponseBodyBaseInteger defines model for ResponseBodyBaseInteger. +type ResponseBodyBaseInteger struct { + Answer int `json:"answer"` +} + +// ResponseBodyBaseNumber defines model for ResponseBodyBaseNumber. +type ResponseBodyBaseNumber struct { + Answer float32 `json:"answer"` +} + +// ResponseBodyBaseString defines model for ResponseBodyBaseString. +type ResponseBodyBaseString struct { + Answer string `json:"answer"` +} + +// ResponseBodyMultipleChoice defines model for ResponseBodyMultipleChoice. +type ResponseBodyMultipleChoice struct { + Answer []int `json:"answer"` + QuestionType ResponseBodyMultipleChoiceQuestionType `json:"question_type"` +} + +// ResponseBodyMultipleChoiceQuestionType defines model for ResponseBodyMultipleChoice.QuestionType. +type ResponseBodyMultipleChoiceQuestionType string + +// ResponseBodyNumber defines model for ResponseBodyNumber. +type ResponseBodyNumber struct { + Answer float32 `json:"answer"` + QuestionType ResponseBodyNumberQuestionType `json:"question_type"` +} + +// ResponseBodyNumberQuestionType defines model for ResponseBodyNumber.QuestionType. +type ResponseBodyNumberQuestionType string + +// ResponseBodyScale defines model for ResponseBodyScale. +type ResponseBodyScale struct { + Answer int `json:"answer"` + QuestionType ResponseBodyScaleQuestionType `json:"question_type"` +} + +// ResponseBodyScaleQuestionType defines model for ResponseBodyScale.QuestionType. +type ResponseBodyScaleQuestionType string + +// ResponseBodySingleChoice defines model for ResponseBodySingleChoice. +type ResponseBodySingleChoice struct { + Answer int `json:"answer"` + QuestionType ResponseBodySingleChoiceQuestionType `json:"question_type"` +} + +// ResponseBodySingleChoiceQuestionType defines model for ResponseBodySingleChoice.QuestionType. +type ResponseBodySingleChoiceQuestionType string + +// ResponseBodyText defines model for ResponseBodyText. +type ResponseBodyText struct { + Answer string `json:"answer"` + QuestionType ResponseBodyTextQuestionType `json:"question_type"` +} + +// ResponseBodyTextQuestionType defines model for ResponseBodyText.QuestionType. +type ResponseBodyTextQuestionType string + +// ResponseBodyTextLong defines model for ResponseBodyTextLong. +type ResponseBodyTextLong struct { + Answer string `json:"answer"` + QuestionType ResponseBodyTextLongQuestionType `json:"question_type"` +} + +// ResponseBodyTextLongQuestionType defines model for ResponseBodyTextLong.QuestionType. +type ResponseBodyTextLongQuestionType string + +// ResponseSortType response用のsortの種類 +type ResponseSortType string + +// Responses defines model for Responses. +type Responses = []Response + +// ResponsesWithQuestionnaireInfo defines model for ResponsesWithQuestionnaireInfo. +type ResponsesWithQuestionnaireInfo = []struct { + Body []ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + QuestionnaireInfo *struct { + CreatedAt time.Time `json:"created_at"` + + // IsTargetingMe 自分がターゲットになっているかどうか + IsTargetingMe bool `json:"is_targeting_me"` + ModifiedAt time.Time `json:"modified_at"` + + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + Title string `json:"title"` + } `json:"questionnaire_info,omitempty"` + + // Respondent traQ ID + Respondent TraqId `json:"respondent"` + ResponseId int `json:"response_id"` + SubmittedAt time.Time `json:"submitted_at"` +} + +// Result defines model for Result. +type Result = []struct { + Body []ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + ResponseId int `json:"response_id"` + SubmittedAt time.Time `json:"submitted_at"` +} + +// SortType question、questionnaire用のソートの種類 +type SortType string + +// TraqId traQ ID +type TraqId = string + +// Users defines model for Users. +type Users = []TraqId + +// UsersAndGroups defines model for UsersAndGroups. +type UsersAndGroups struct { + Groups Groups `json:"groups"` + Users Users `json:"users"` +} + +// OnlyAdministratedByMeInQuery defines model for onlyAdministratedByMeInQuery. +type OnlyAdministratedByMeInQuery = bool + +// OnlyMyResponseInQuery defines model for onlyMyResponseInQuery. +type OnlyMyResponseInQuery = bool + +// OnlyTargetingMeInQuery defines model for onlyTargetingMeInQuery. +type OnlyTargetingMeInQuery = bool + +// PageInQuery defines model for pageInQuery. +type PageInQuery = int + +// QuestionnaireIDInPath defines model for questionnaireIDInPath. +type QuestionnaireIDInPath = int + +// ResponseIDInPath defines model for responseIDInPath. +type ResponseIDInPath = int + +// ResponseSortInQuery response用のsortの種類 +type ResponseSortInQuery = ResponseSortType + +// SearchInQuery defines model for searchInQuery. +type SearchInQuery = string + +// SortInQuery question、questionnaire用のソートの種類 +type SortInQuery = SortType + +// GetQuestionnairesParams defines parameters for GetQuestionnaires. +type GetQuestionnairesParams struct { + // Sort 並び順 (作成日時が新しい "created_at", 作成日時が古い "-created_at", タイトルの昇順 "title", タイトルの降順 "-title", 更新日時が新しい "modified_at", 更新日時が古い "-modified_at" ) + Sort *SortInQuery `form:"sort,omitempty" json:"sort,omitempty"` + + // Search タイトルの検索 + Search *SearchInQuery `form:"search,omitempty" json:"search,omitempty"` + + // Page 何ページ目か (未定義の場合は1ページ目) + Page *PageInQuery `form:"page,omitempty" json:"page,omitempty"` + + // OnlyTargetingMe 自分がターゲットになっているもののみ取得 (true), ターゲットになっているものも含めてすべて取得 (false)。デフォルトはfalse。 + OnlyTargetingMe *OnlyTargetingMeInQuery `form:"onlyTargetingMe,omitempty" json:"onlyTargetingMe,omitempty"` + + // OnlyAdministratedByMe 自分が管理者になっていないもののみ取得 (true), 管理者になっているものも含めてすべて取得 (false)。デフォルトはfalse。 + OnlyAdministratedByMe *OnlyAdministratedByMeInQuery `form:"onlyAdministratedByMe,omitempty" json:"onlyAdministratedByMe,omitempty"` +} + +// GetQuestionnaireResponsesParams defines parameters for GetQuestionnaireResponses. +type GetQuestionnaireResponsesParams struct { + // Sort 並び順 (作成日時が新しい "submitted_at", 作成日時が古い "-submitted_at", タイトルの昇順 "title", タイトルの降順 "-title", 更新日時が新しい "modified_at", 更新日時が古い "-modified_at" ) + Sort *ResponseSortInQuery `form:"sort,omitempty" json:"sort,omitempty"` + + // OnlyMyResponse 自分の回答のみ取得 (true), 自分の回答以外も含めてすべて取得 (false)。デフォルトはfalse。 + OnlyMyResponse *OnlyMyResponseInQuery `form:"onlyMyResponse,omitempty" json:"onlyMyResponse,omitempty"` +} + +// GetMyResponsesParams defines parameters for GetMyResponses. +type GetMyResponsesParams struct { + // Sort 並び順 (作成日時が新しい "submitted_at", 作成日時が古い "-submitted_at", タイトルの昇順 "title", タイトルの降順 "-title", 更新日時が新しい "modified_at", 更新日時が古い "-modified_at" ) + Sort *ResponseSortInQuery `form:"sort,omitempty" json:"sort,omitempty"` +} + +// PostQuestionnaireJSONRequestBody defines body for PostQuestionnaire for application/json ContentType. +type PostQuestionnaireJSONRequestBody = NewQuestionnaire + +// EditQuestionnaireJSONRequestBody defines body for EditQuestionnaire for application/json ContentType. +type EditQuestionnaireJSONRequestBody = QuestionnaireDetail + +// EditQuestionnaireMyRemindStatusJSONRequestBody defines body for EditQuestionnaireMyRemindStatus for application/json ContentType. +type EditQuestionnaireMyRemindStatusJSONRequestBody = QuestionnaireIsRemindEnabled + +// PostQuestionnaireResponseJSONRequestBody defines body for PostQuestionnaireResponse for application/json ContentType. +type PostQuestionnaireResponseJSONRequestBody = NewResponse + +// EditResponseJSONRequestBody defines body for EditResponse for application/json ContentType. +type EditResponseJSONRequestBody = Response + +// AsQuestionSettingsText returns the union data inside the NewQuestion as a QuestionSettingsText +func (t NewQuestion) AsQuestionSettingsText() (QuestionSettingsText, error) { + var body QuestionSettingsText + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsText overwrites any union data inside the NewQuestion as the provided QuestionSettingsText +func (t *NewQuestion) FromQuestionSettingsText(v QuestionSettingsText) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsText performs a merge with any union data inside the NewQuestion, using the provided QuestionSettingsText +func (t *NewQuestion) MergeQuestionSettingsText(v QuestionSettingsText) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsTextLong returns the union data inside the NewQuestion as a QuestionSettingsTextLong +func (t NewQuestion) AsQuestionSettingsTextLong() (QuestionSettingsTextLong, error) { + var body QuestionSettingsTextLong + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsTextLong overwrites any union data inside the NewQuestion as the provided QuestionSettingsTextLong +func (t *NewQuestion) FromQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsTextLong performs a merge with any union data inside the NewQuestion, using the provided QuestionSettingsTextLong +func (t *NewQuestion) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsNumber returns the union data inside the NewQuestion as a QuestionSettingsNumber +func (t NewQuestion) AsQuestionSettingsNumber() (QuestionSettingsNumber, error) { + var body QuestionSettingsNumber + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsNumber overwrites any union data inside the NewQuestion as the provided QuestionSettingsNumber +func (t *NewQuestion) FromQuestionSettingsNumber(v QuestionSettingsNumber) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsNumber performs a merge with any union data inside the NewQuestion, using the provided QuestionSettingsNumber +func (t *NewQuestion) MergeQuestionSettingsNumber(v QuestionSettingsNumber) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsSingleChoice returns the union data inside the NewQuestion as a QuestionSettingsSingleChoice +func (t NewQuestion) AsQuestionSettingsSingleChoice() (QuestionSettingsSingleChoice, error) { + var body QuestionSettingsSingleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsSingleChoice overwrites any union data inside the NewQuestion as the provided QuestionSettingsSingleChoice +func (t *NewQuestion) FromQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsSingleChoice performs a merge with any union data inside the NewQuestion, using the provided QuestionSettingsSingleChoice +func (t *NewQuestion) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsMultipleChoice returns the union data inside the NewQuestion as a QuestionSettingsMultipleChoice +func (t NewQuestion) AsQuestionSettingsMultipleChoice() (QuestionSettingsMultipleChoice, error) { + var body QuestionSettingsMultipleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsMultipleChoice overwrites any union data inside the NewQuestion as the provided QuestionSettingsMultipleChoice +func (t *NewQuestion) FromQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsMultipleChoice performs a merge with any union data inside the NewQuestion, using the provided QuestionSettingsMultipleChoice +func (t *NewQuestion) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsScale returns the union data inside the NewQuestion as a QuestionSettingsScale +func (t NewQuestion) AsQuestionSettingsScale() (QuestionSettingsScale, error) { + var body QuestionSettingsScale + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsScale overwrites any union data inside the NewQuestion as the provided QuestionSettingsScale +func (t *NewQuestion) FromQuestionSettingsScale(v QuestionSettingsScale) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsScale performs a merge with any union data inside the NewQuestion, using the provided QuestionSettingsScale +func (t *NewQuestion) MergeQuestionSettingsScale(v QuestionSettingsScale) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +func (t NewQuestion) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + if err != nil { + return nil, err + } + object := make(map[string]json.RawMessage) + if t.union != nil { + err = json.Unmarshal(b, &object) + if err != nil { + return nil, err + } + } + + object["description"], err = json.Marshal(t.Description) + if err != nil { + return nil, fmt.Errorf("error marshaling 'description': %w", err) + } + + object["is_required"], err = json.Marshal(t.IsRequired) + if err != nil { + return nil, fmt.Errorf("error marshaling 'is_required': %w", err) + } + + object["questionnaire_id"], err = json.Marshal(t.QuestionnaireId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'questionnaire_id': %w", err) + } + + object["title"], err = json.Marshal(t.Title) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title': %w", err) + } + + b, err = json.Marshal(object) + return b, err +} + +func (t *NewQuestion) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + if err != nil { + return err + } + object := make(map[string]json.RawMessage) + err = json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["description"]; found { + err = json.Unmarshal(raw, &t.Description) + if err != nil { + return fmt.Errorf("error reading 'description': %w", err) + } + } + + if raw, found := object["is_required"]; found { + err = json.Unmarshal(raw, &t.IsRequired) + if err != nil { + return fmt.Errorf("error reading 'is_required': %w", err) + } + } + + if raw, found := object["questionnaire_id"]; found { + err = json.Unmarshal(raw, &t.QuestionnaireId) + if err != nil { + return fmt.Errorf("error reading 'questionnaire_id': %w", err) + } + } + + if raw, found := object["title"]; found { + err = json.Unmarshal(raw, &t.Title) + if err != nil { + return fmt.Errorf("error reading 'title': %w", err) + } + } + + return err +} + +// AsQuestionSettingsText returns the union data inside the Question as a QuestionSettingsText +func (t Question) AsQuestionSettingsText() (QuestionSettingsText, error) { + var body QuestionSettingsText + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsText overwrites any union data inside the Question as the provided QuestionSettingsText +func (t *Question) FromQuestionSettingsText(v QuestionSettingsText) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsText performs a merge with any union data inside the Question, using the provided QuestionSettingsText +func (t *Question) MergeQuestionSettingsText(v QuestionSettingsText) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsTextLong returns the union data inside the Question as a QuestionSettingsTextLong +func (t Question) AsQuestionSettingsTextLong() (QuestionSettingsTextLong, error) { + var body QuestionSettingsTextLong + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsTextLong overwrites any union data inside the Question as the provided QuestionSettingsTextLong +func (t *Question) FromQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsTextLong performs a merge with any union data inside the Question, using the provided QuestionSettingsTextLong +func (t *Question) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsNumber returns the union data inside the Question as a QuestionSettingsNumber +func (t Question) AsQuestionSettingsNumber() (QuestionSettingsNumber, error) { + var body QuestionSettingsNumber + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsNumber overwrites any union data inside the Question as the provided QuestionSettingsNumber +func (t *Question) FromQuestionSettingsNumber(v QuestionSettingsNumber) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsNumber performs a merge with any union data inside the Question, using the provided QuestionSettingsNumber +func (t *Question) MergeQuestionSettingsNumber(v QuestionSettingsNumber) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsSingleChoice returns the union data inside the Question as a QuestionSettingsSingleChoice +func (t Question) AsQuestionSettingsSingleChoice() (QuestionSettingsSingleChoice, error) { + var body QuestionSettingsSingleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsSingleChoice overwrites any union data inside the Question as the provided QuestionSettingsSingleChoice +func (t *Question) FromQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsSingleChoice performs a merge with any union data inside the Question, using the provided QuestionSettingsSingleChoice +func (t *Question) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsMultipleChoice returns the union data inside the Question as a QuestionSettingsMultipleChoice +func (t Question) AsQuestionSettingsMultipleChoice() (QuestionSettingsMultipleChoice, error) { + var body QuestionSettingsMultipleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsMultipleChoice overwrites any union data inside the Question as the provided QuestionSettingsMultipleChoice +func (t *Question) FromQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsMultipleChoice performs a merge with any union data inside the Question, using the provided QuestionSettingsMultipleChoice +func (t *Question) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsScale returns the union data inside the Question as a QuestionSettingsScale +func (t Question) AsQuestionSettingsScale() (QuestionSettingsScale, error) { + var body QuestionSettingsScale + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsScale overwrites any union data inside the Question as the provided QuestionSettingsScale +func (t *Question) FromQuestionSettingsScale(v QuestionSettingsScale) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsScale performs a merge with any union data inside the Question, using the provided QuestionSettingsScale +func (t *Question) MergeQuestionSettingsScale(v QuestionSettingsScale) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +func (t Question) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + if err != nil { + return nil, err + } + object := make(map[string]json.RawMessage) + if t.union != nil { + err = json.Unmarshal(b, &object) + if err != nil { + return nil, err + } + } + + object["created_at"], err = json.Marshal(t.CreatedAt) + if err != nil { + return nil, fmt.Errorf("error marshaling 'created_at': %w", err) + } + + object["description"], err = json.Marshal(t.Description) + if err != nil { + return nil, fmt.Errorf("error marshaling 'description': %w", err) + } + + object["is_required"], err = json.Marshal(t.IsRequired) + if err != nil { + return nil, fmt.Errorf("error marshaling 'is_required': %w", err) + } + + object["question_id"], err = json.Marshal(t.QuestionId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'question_id': %w", err) + } + + object["questionnaire_id"], err = json.Marshal(t.QuestionnaireId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'questionnaire_id': %w", err) + } + + object["title"], err = json.Marshal(t.Title) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title': %w", err) + } + + b, err = json.Marshal(object) + return b, err +} + +func (t *Question) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + if err != nil { + return err + } + object := make(map[string]json.RawMessage) + err = json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["created_at"]; found { + err = json.Unmarshal(raw, &t.CreatedAt) + if err != nil { + return fmt.Errorf("error reading 'created_at': %w", err) + } + } + + if raw, found := object["description"]; found { + err = json.Unmarshal(raw, &t.Description) + if err != nil { + return fmt.Errorf("error reading 'description': %w", err) + } + } + + if raw, found := object["is_required"]; found { + err = json.Unmarshal(raw, &t.IsRequired) + if err != nil { + return fmt.Errorf("error reading 'is_required': %w", err) + } + } + + if raw, found := object["question_id"]; found { + err = json.Unmarshal(raw, &t.QuestionId) + if err != nil { + return fmt.Errorf("error reading 'question_id': %w", err) + } + } + + if raw, found := object["questionnaire_id"]; found { + err = json.Unmarshal(raw, &t.QuestionnaireId) + if err != nil { + return fmt.Errorf("error reading 'questionnaire_id': %w", err) + } + } + + if raw, found := object["title"]; found { + err = json.Unmarshal(raw, &t.Title) + if err != nil { + return fmt.Errorf("error reading 'title': %w", err) + } + } + + return err +} + +// AsQuestionSettingsText returns the union data inside the QuestionSettingsByType as a QuestionSettingsText +func (t QuestionSettingsByType) AsQuestionSettingsText() (QuestionSettingsText, error) { + var body QuestionSettingsText + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsText overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsText +func (t *QuestionSettingsByType) FromQuestionSettingsText(v QuestionSettingsText) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsText performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsText +func (t *QuestionSettingsByType) MergeQuestionSettingsText(v QuestionSettingsText) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsTextLong returns the union data inside the QuestionSettingsByType as a QuestionSettingsTextLong +func (t QuestionSettingsByType) AsQuestionSettingsTextLong() (QuestionSettingsTextLong, error) { + var body QuestionSettingsTextLong + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsTextLong overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsTextLong +func (t *QuestionSettingsByType) FromQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsTextLong performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsTextLong +func (t *QuestionSettingsByType) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsNumber returns the union data inside the QuestionSettingsByType as a QuestionSettingsNumber +func (t QuestionSettingsByType) AsQuestionSettingsNumber() (QuestionSettingsNumber, error) { + var body QuestionSettingsNumber + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsNumber overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsNumber +func (t *QuestionSettingsByType) FromQuestionSettingsNumber(v QuestionSettingsNumber) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsNumber performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsNumber +func (t *QuestionSettingsByType) MergeQuestionSettingsNumber(v QuestionSettingsNumber) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsSingleChoice returns the union data inside the QuestionSettingsByType as a QuestionSettingsSingleChoice +func (t QuestionSettingsByType) AsQuestionSettingsSingleChoice() (QuestionSettingsSingleChoice, error) { + var body QuestionSettingsSingleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsSingleChoice overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsSingleChoice +func (t *QuestionSettingsByType) FromQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsSingleChoice performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsSingleChoice +func (t *QuestionSettingsByType) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsMultipleChoice returns the union data inside the QuestionSettingsByType as a QuestionSettingsMultipleChoice +func (t QuestionSettingsByType) AsQuestionSettingsMultipleChoice() (QuestionSettingsMultipleChoice, error) { + var body QuestionSettingsMultipleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsMultipleChoice overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsMultipleChoice +func (t *QuestionSettingsByType) FromQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsMultipleChoice performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsMultipleChoice +func (t *QuestionSettingsByType) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsQuestionSettingsScale returns the union data inside the QuestionSettingsByType as a QuestionSettingsScale +func (t QuestionSettingsByType) AsQuestionSettingsScale() (QuestionSettingsScale, error) { + var body QuestionSettingsScale + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromQuestionSettingsScale overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsScale +func (t *QuestionSettingsByType) FromQuestionSettingsScale(v QuestionSettingsScale) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeQuestionSettingsScale performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsScale +func (t *QuestionSettingsByType) MergeQuestionSettingsScale(v QuestionSettingsScale) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +func (t QuestionSettingsByType) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *QuestionSettingsByType) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsResponseBodyText returns the union data inside the ResponseBody as a ResponseBodyText +func (t ResponseBody) AsResponseBodyText() (ResponseBodyText, error) { + var body ResponseBodyText + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResponseBodyText overwrites any union data inside the ResponseBody as the provided ResponseBodyText +func (t *ResponseBody) FromResponseBodyText(v ResponseBodyText) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResponseBodyText performs a merge with any union data inside the ResponseBody, using the provided ResponseBodyText +func (t *ResponseBody) MergeResponseBodyText(v ResponseBodyText) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsResponseBodyTextLong returns the union data inside the ResponseBody as a ResponseBodyTextLong +func (t ResponseBody) AsResponseBodyTextLong() (ResponseBodyTextLong, error) { + var body ResponseBodyTextLong + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResponseBodyTextLong overwrites any union data inside the ResponseBody as the provided ResponseBodyTextLong +func (t *ResponseBody) FromResponseBodyTextLong(v ResponseBodyTextLong) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResponseBodyTextLong performs a merge with any union data inside the ResponseBody, using the provided ResponseBodyTextLong +func (t *ResponseBody) MergeResponseBodyTextLong(v ResponseBodyTextLong) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsResponseBodyNumber returns the union data inside the ResponseBody as a ResponseBodyNumber +func (t ResponseBody) AsResponseBodyNumber() (ResponseBodyNumber, error) { + var body ResponseBodyNumber + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResponseBodyNumber overwrites any union data inside the ResponseBody as the provided ResponseBodyNumber +func (t *ResponseBody) FromResponseBodyNumber(v ResponseBodyNumber) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResponseBodyNumber performs a merge with any union data inside the ResponseBody, using the provided ResponseBodyNumber +func (t *ResponseBody) MergeResponseBodyNumber(v ResponseBodyNumber) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsResponseBodySingleChoice returns the union data inside the ResponseBody as a ResponseBodySingleChoice +func (t ResponseBody) AsResponseBodySingleChoice() (ResponseBodySingleChoice, error) { + var body ResponseBodySingleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResponseBodySingleChoice overwrites any union data inside the ResponseBody as the provided ResponseBodySingleChoice +func (t *ResponseBody) FromResponseBodySingleChoice(v ResponseBodySingleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResponseBodySingleChoice performs a merge with any union data inside the ResponseBody, using the provided ResponseBodySingleChoice +func (t *ResponseBody) MergeResponseBodySingleChoice(v ResponseBodySingleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsResponseBodyMultipleChoice returns the union data inside the ResponseBody as a ResponseBodyMultipleChoice +func (t ResponseBody) AsResponseBodyMultipleChoice() (ResponseBodyMultipleChoice, error) { + var body ResponseBodyMultipleChoice + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResponseBodyMultipleChoice overwrites any union data inside the ResponseBody as the provided ResponseBodyMultipleChoice +func (t *ResponseBody) FromResponseBodyMultipleChoice(v ResponseBodyMultipleChoice) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResponseBodyMultipleChoice performs a merge with any union data inside the ResponseBody, using the provided ResponseBodyMultipleChoice +func (t *ResponseBody) MergeResponseBodyMultipleChoice(v ResponseBodyMultipleChoice) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsResponseBodyScale returns the union data inside the ResponseBody as a ResponseBodyScale +func (t ResponseBody) AsResponseBodyScale() (ResponseBodyScale, error) { + var body ResponseBodyScale + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResponseBodyScale overwrites any union data inside the ResponseBody as the provided ResponseBodyScale +func (t *ResponseBody) FromResponseBodyScale(v ResponseBodyScale) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResponseBodyScale performs a merge with any union data inside the ResponseBody, using the provided ResponseBodyScale +func (t *ResponseBody) MergeResponseBodyScale(v ResponseBodyScale) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +func (t ResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} From 76acd7687140ae61e3214e766f96f33f928c5258 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Mon, 6 May 2024 00:40:21 +0900 Subject: [PATCH 05/83] wip --- controller/adapter.go | 165 +++++++++++++++++++++ controller/questionnaire.go | 232 ++++++++++++++++++++++++++++++ controller/utils.go | 87 +++++++++++ go.mod | 70 ++++++--- go.sum | 114 +++++++++++++++ handler/middleware.go | 40 ++++++ handler/questionnaire.go | 22 +++ main.go | 13 +- model/administratorGroups.go | 10 ++ model/administratorGroups_impl.go | 83 +++++++++++ model/questionnaires.go | 2 +- model/questionnaires_impl.go | 16 ++- model/questions_impl.go | 14 +- model/targetGroups.go | 12 ++ model/targetGroups_impl.go | 84 +++++++++++ traq/traq.go | 45 ++++++ 16 files changed, 976 insertions(+), 33 deletions(-) create mode 100644 controller/adapter.go create mode 100644 controller/questionnaire.go create mode 100644 controller/utils.go create mode 100644 handler/middleware.go create mode 100644 model/administratorGroups.go create mode 100644 model/administratorGroups_impl.go create mode 100644 model/targetGroups.go create mode 100644 model/targetGroups_impl.go create mode 100644 traq/traq.go diff --git a/controller/adapter.go b/controller/adapter.go new file mode 100644 index 00000000..05cdfc70 --- /dev/null +++ b/controller/adapter.go @@ -0,0 +1,165 @@ +package controller + +import ( + "github.com/traPtitech/anke-to/model" + "github.com/traPtitech/anke-to/openapi" + "gopkg.in/guregu/null.v4" +) + +func questionnaireInfo2questionnaireSummary(questionnaireInfo model.QuestionnaireInfo, allResponded bool, hasMyDraft bool, hasMyResponse bool, respondedDateTimeByMe null.Time) *openapi.QuestionnaireSummary { + res := openapi.QuestionnaireSummary{ + AllResponded: allResponded, + CreatedAt: questionnaireInfo.CreatedAt, + Description: questionnaireInfo.Description, + HasMyDraft: hasMyDraft, + HasMyResponse: hasMyResponse, + // IsAllowingMultipleResponses: questionnaireInfo.IsAllowingMultipleResponses, + // IsAnonymous: questionnaireInfo.IsAnonymous, + // IsPublished: questionnaireInfo.IsPublished, + IsTargetingMe: questionnaireInfo.IsTargeted, + ModifiedAt: questionnaireInfo.ModifiedAt, + QuestionnaireId: questionnaireInfo.ID, + Title: questionnaireInfo.Title, + } + if respondedDateTimeByMe.Valid { + res.RespondedDateTimeByMe = &respondedDateTimeByMe.Time + } else { + res.RespondedDateTimeByMe = nil + } + if questionnaireInfo.ResTimeLimit.Valid { + res.ResponseDueDateTime = &questionnaireInfo.ResTimeLimit.Time + } else { + res.ResponseDueDateTime = nil + } + return &res +} + +func convertResponseViewableBy(resShareType openapi.ResShareType) string { + switch resShareType { + case "admins": + return "administrators" + case "respondents": + return "respondents" + case "anyone": + return "public" + default: + return "administrators" + } +} + +func convertResSharedTo(resSharedTo string) openapi.ResShareType { + switch resSharedTo { + case "administrators": + return "admins" + case "respondents": + return "respondents" + case "public": + return "anyone" + default: + return "admins" + } + +} + +func createUsersAndGroups(users []string, groups []string) openapi.UsersAndGroups { + res := openapi.UsersAndGroups{ + Users: users, + Groups: groups, + } + return res +} + +func convertOptions(options []model.Options) openapi.QuestionSettingsSingleChoice { + res := openapi.QuestionSettingsSingleChoice{} + for _, option := range options { + res.Options = append(res.Options, option.Body) + } + return res +} + +func convertQuestions(questions []model.Questions) []openapi.Question { + res := []openapi.Question{} + for _, question := range questions { + q := openapi.Question{ + CreatedAt: question.CreatedAt, + // Description: question.Description, + IsRequired: question.IsRequired, + QuestionId: question.ID, + QuestionnaireId: question.QuestionnaireID, + Title: question.Body, + } + switch question.Type { + case "Text": + q.FromQuestionSettingsText( + openapi.QuestionSettingsText{ + QuestionType: "Text", + }, + ) + case "TextArea": + q.FromQuestionSettingsText( + openapi.QuestionSettingsText{ + QuestionType: "TextLong", + }, + ) + case "Number": + q.FromQuestionSettingsNumber( + openapi.QuestionSettingsNumber{ + QuestionType: "Number", + }, + ) + case "Radio": + q.FromQuestionSettingsSingleChoice( + openapi.QuestionSettingsSingleChoice{ + QuestionType: "Radio", + Options: convertOptions(question.Options).Options, + }, + ) + case "MultipleChoice": + q.FromQuestionSettingsMultipleChoice( + openapi.QuestionSettingsMultipleChoice{ + QuestionType: "MultipleChoice", + Options: convertOptions(question.Options).Options, + }, + ) + case "LinearScale": + q.FromQuestionSettingsScale( + openapi.QuestionSettingsScale{ + QuestionType: "LinearScale", + MinLabel: &question.ScaleLabels[0].ScaleLabelLeft, + MaxLabel: &question.ScaleLabels[0].ScaleLabelRight, + MinValue: question.ScaleLabels[0].ScaleMin, + MaxValue: question.ScaleLabels[0].ScaleMax, + }, + ) + } + } + return res +} + +func convertRespondents(respondents []model.Respondents) []string { + res := []string{} + for _, respondent := range respondents { + res = append(res, respondent.UserTraqid) + } + return res +} + +func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, adminUsers []string, adminGroups []string, targetUsers []string, targetGroups []string) openapi.QuestionnaireDetail { + res := openapi.QuestionnaireDetail{ + Admins: createUsersAndGroups(adminUsers, adminGroups), + CreatedAt: questionnaires.CreatedAt, + Description: questionnaires.Description, + // IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, + // IsAnonymous: questionnaires.IsAnonymous, + // IsPublished: questionnaires.IsPublished, + ModifiedAt: questionnaires.ModifiedAt, + QuestionnaireId: questionnaires.ID, + Questions: convertQuestions(questionnaires.Questions), + Respondents: convertRespondents(questionnaires.Respondents), + ResponseDueDateTime: &questionnaires.ResTimeLimit.Time, + ResponseViewableBy: convertResSharedTo(questionnaires.ResSharedTo), + Targets: createUsersAndGroups(targetUsers, targetGroups), + Title: questionnaires.Title, + } + return res +} diff --git a/controller/questionnaire.go b/controller/questionnaire.go new file mode 100644 index 00000000..43835490 --- /dev/null +++ b/controller/questionnaire.go @@ -0,0 +1,232 @@ +package controller + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/model" + "github.com/traPtitech/anke-to/openapi" + "github.com/traPtitech/anke-to/traq" + "gopkg.in/guregu/null.v4" +) + +// Response Responseの構造体 +type Response struct { + model.IQuestionnaire + model.IValidation + model.IScaleLabel + model.IRespondent + model.IResponse +} + +// Questionnaire Questionnaireの構造体 +type Questionnaire struct { + model.IQuestionnaire + model.ITarget + model.ITargetGroup + model.IAdministrator + model.IAdministratorGroup + model.IQuestion + model.IOption + model.IScaleLabel + model.IValidation + model.ITransaction + traq.IWebhook + Response +} + +func NewQuestionnaire() *Questionnaire { + return &Questionnaire{} +} + +const MaxTitleLength = 50 + +func (q Questionnaire) GetQuestionnaires(ctx echo.Context, userID string, params openapi.GetQuestionnairesParams) (openapi.QuestionnaireList, error) { + res := openapi.QuestionnaireList{} + sort := string(*params.Sort) + search := string(*params.Search) + pageNum := int(*params.Page) + if pageNum < 1 { + pageNum = 1 + } + + questionnaireList, pageMax, err := q.IQuestionnaire.GetQuestionnaires(ctx.Request().Context(), userID, sort, search, pageNum, *params.OnlyTargetingMe, *params.OnlyAdministratedByMe) + if err != nil { + return res, err + } + + for _, questionnaire := range questionnaireList { + targets, err := q.ITarget.GetTargets(ctx.Request().Context(), []int{questionnaire.ID}) + if err != nil { + return res, err + } + allRespondend := false + if len(targets) == 0 { + allRespondend = true + } else { + respondents, err := q.IRespondent.GetRespondentsUserIDs(ctx.Request().Context(), []int{questionnaire.ID}) + if err != nil { + return res, err + } + allRespondend = isAllTargetsReponded(targets, respondents) + } + + hasMyDraft := false + hasMyResponse := false + respondendDateTimeByMe := null.Time{} + + myRespondents, err := q.GetRespondentInfos(ctx.Request().Context(), userID, questionnaire.ID) + if err != nil { + return res, err + } + for _, respondent := range myRespondents { + if !respondent.SubmittedAt.Valid { + hasMyDraft = true + } + if respondent.SubmittedAt.Valid { + if !respondendDateTimeByMe.Valid { + respondendDateTimeByMe = respondent.SubmittedAt + } + hasMyResponse = true + } + } + + res.PageMax = pageMax + res.Questionnaires = append(res.Questionnaires, *questionnaireInfo2questionnaireSummary(questionnaire, allRespondend, hasMyDraft, hasMyResponse, respondendDateTimeByMe)) + } + return res, nil +} + +func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params openapi.PostQuestionnaireJSONRequestBody) (openapi.QuestionnaireDetail, error) { + responseDueDateTime := null.Time{} + if params.ResponseDueDateTime != nil { + responseDueDateTime.Valid = true + responseDueDateTime.Time = *params.ResponseDueDateTime + } + if responseDueDateTime.Valid { + isBefore := responseDueDateTime.ValueOrZero().Before(time.Now()) + if isBefore { + c.Logger().Infof("invalid resTimeLimit: %+v", responseDueDateTime) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusBadRequest, "invalid resTimeLimit") + } + } + + questionnaireID := 0 + + err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { + questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy)) + if err != nil { + c.Logger().Errorf("failed to insert questionnaire: %+v", err) + return err + } + allTargetUsers, err := rollOutUsersAndGroups(params.Targets.Users, params.Targets.Groups) + if err != nil { + c.Logger().Errorf("failed to roll out users and groups: %+v", err) + return err + } + targetGroupNames, err := uuid2GroupNames(params.Targets.Groups) + if err != nil { + c.Logger().Errorf("failed to get group names: %+v", err) + return err + } + err = q.InsertTargets(ctx, questionnaireID, allTargetUsers) + if err != nil { + c.Logger().Errorf("failed to insert targets: %+v", err) + return err + } + err = q.InsertTargetGroups(ctx, questionnaireID, params.Targets.Groups) + if err != nil { + c.Logger().Errorf("failed to insert target groups: %+v", err) + return err + } + allAdminUsers, err := rollOutUsersAndGroups(params.Admins.Users, params.Admins.Groups) + if err != nil { + c.Logger().Errorf("failed to roll out administrators: %+v", err) + return err + } + adminGroupNames, err := uuid2GroupNames(params.Admins.Groups) + if err != nil { + c.Logger().Errorf("failed to get group names: %+v", err) + return err + } + err = q.InsertAdministrators(ctx, questionnaireID, allAdminUsers) + if err != nil { + c.Logger().Errorf("failed to insert administrators: %+v", err) + return err + } + err = q.InsertAdministratorGroups(ctx, questionnaireID, params.Admins.Groups) + if err != nil { + c.Logger().Errorf("failed to insert administrator groups: %+v", err) + return err + } + + message := createQuestionnaireMessage( + questionnaireID, + params.Title, + params.Description, + append(allAdminUsers, adminGroupNames...), + responseDueDateTime, + append(allTargetUsers, targetGroupNames...), + ) + err = q.PostMessage(message) + if err != nil { + c.Logger().Errorf("failed to post message: %+v", err) + return err + } + + return nil + }) + if err != nil { + c.Logger().Errorf("failed to create a questionnaire: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to create a questionnaire") + } + questionnaireInfo, _, _, _, err := q.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to get questionnaire info: %+v", err) + return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get questionnaire info") + } + + questionnaireDetail := questionnaire2QuestionnaireDetail(*questionnaireInfo, params.Admins.Users, params.Admins.Groups, params.Targets.Users, params.Targets.Groups) + return questionnaireDetail, nil +} + +func createQuestionnaireMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit null.Time, targets []string) string { + var resTimeLimitText string + if resTimeLimit.Valid { + resTimeLimitText = resTimeLimit.Time.Local().Format("2006/01/02 15:04") + } else { + resTimeLimitText = "なし" + } + + var targetsMentionText string + if len(targets) == 0 { + targetsMentionText = "なし" + } else { + targetsMentionText = "@" + strings.Join(targets, " @") + } + + return fmt.Sprintf( + `### アンケート『[%s](https://anke-to.trap.jp/questionnaires/%d)』が作成されました +#### 管理者 +%s +#### 説明 +%s +#### 回答期限 +%s +#### 対象者 +%s +#### 回答リンク +https://anke-to.trap.jp/responses/new/%d`, + title, + questionnaireID, + strings.Join(administrators, ","), + description, + resTimeLimitText, + targetsMentionText, + questionnaireID, + ) +} diff --git a/controller/utils.go b/controller/utils.go new file mode 100644 index 00000000..121281b0 --- /dev/null +++ b/controller/utils.go @@ -0,0 +1,87 @@ +package controller + +import ( + "context" + "slices" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/gofrs/uuid" + "github.com/traPtitech/anke-to/model" + "github.com/traPtitech/anke-to/traq" +) + +func isAllTargetsReponded(targets []model.Targets, respondents []model.Respondents) bool { + respondentsString := []string{} + for _, respondent := range respondents { + respondentsString = append(respondentsString, respondent.UserTraqid) + } + + for _, target := range targets { + if !slices.Contains(respondentsString, target.UserTraqid) { + return false + } + } + return true +} + +func minimizeUsersAndGroups(users []string, groups []uuid.UUID) ([]string, []uuid.UUID, error) { + ctx := context.Background() + client := traq.NewTraqAPIClient() + userSet := mapset.NewSet[string]() + for _, user := range users { + userSet.Add(user) + } + groupUserSet := mapset.NewSet[string]() + for _, group := range groups { + members, err := client.GetGroupMembers(ctx, group.String()) + if err != nil { + return nil, nil, err + } + for _, member := range members { + memberTraqID, err := client.GetUserTraqID(ctx, member.Id) + if err != nil { + return nil, nil, err + } + groupUserSet.Add(memberTraqID) + } + } + userSet = userSet.Difference(groupUserSet) + return userSet.ToSlice(), groups, nil +} + +func rollOutUsersAndGroups(users []string, groups []string) ([]string, error) { + ctx := context.Background() + client := traq.NewTraqAPIClient() + userSet := mapset.NewSet[string]() + for _, user := range users { + userSet.Add(user) + } + for _, group := range groups { + members, err := client.GetGroupMembers(ctx, group) + if err != nil { + return nil, err + } + for _, member := range members { + memberTraqID, err := client.GetUserTraqID(ctx, member.Id) + if err != nil { + return nil, err + } + userSet.Add(memberTraqID) + } + } + return userSet.ToSlice(), nil +} + +func uuid2GroupNames(users []string) ([]string, error) { + ctx := context.Background() + client := traq.NewTraqAPIClient() + groupNames := []string{} + for _, user := range users { + groupName, err := client.GetGroupName(ctx, user) + if err != nil { + return nil, err + } + groupNames = append(groupNames, groupName) + } + return groupNames, nil +} diff --git a/go.mod b/go.mod index 5358018d..d5fee144 100644 --- a/go.mod +++ b/go.mod @@ -4,43 +4,43 @@ go 1.20 require ( github.com/golang/mock v1.6.0 - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/subcommands v1.2.0 // indirect github.com/google/wire v0.5.0 - github.com/labstack/echo/v4 v4.11.1 - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/stretchr/testify v1.8.1 - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f - golang.org/x/sync v0.1.0 - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect - golang.org/x/tools v0.6.0 // indirect + github.com/labstack/echo/v4 v4.12.0 + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.10.0 + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.12.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-playground/validator/v10 v10.10.1 + github.com/go-playground/validator/v10 v10.14.1 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/labstack/gommon v0.4.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect ) require ( - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/labstack/echo-contrib v0.12.0 - github.com/leodido/go-urn v1.2.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect ) require ( @@ -65,4 +65,34 @@ require ( gorm.io/plugin/prometheus v0.0.0-20210820101226-2a49866f83ee ) -require github.com/go-gormigrate/gormigrate/v2 v2.1.1 // indirect +require ( + github.com/deepmap/oapi-codegen v1.16.2 + github.com/go-gormigrate/gormigrate/v2 v2.1.1 +) + +require ( + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/traPtitech/go-traq v0.0.0-20240420012203-0152d96098b0 // indirect +) + +require ( + github.com/getkin/kin-openapi v0.118.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/invopop/yaml v0.2.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oapi-codegen/echo-middleware v1.0.1 + github.com/perimeterx/marshmallow v1.1.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gofrs/uuid v4.4.0+incompatible + github.com/google/uuid v1.5.0 // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect +) diff --git a/go.sum b/go.sum index 83e0cc26..e9cfe8f2 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -44,12 +45,15 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/casbin/casbin/v2 v2.40.6/go.mod h1:sEL80qBYTbd+BPeL4iyvwYzFT3qwLaESq5aFKVLbLfA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -67,6 +71,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/deepmap/oapi-codegen v1.16.2 h1:xGHx0dNqYfy9gE8a7AVgVM8Sd5oF9SEgePzP+UPAUXI= +github.com/deepmap/oapi-codegen v1.16.2/go.mod h1:rdYoEA2GE+riuZ91DvpmBX9hJbQpuY9wchXpfQ3n+ho= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -81,6 +89,10 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM= +github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -93,20 +105,39 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= @@ -141,6 +172,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -169,11 +202,14 @@ github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3 github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= @@ -183,6 +219,10 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= @@ -197,6 +237,8 @@ github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -204,6 +246,7 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -225,12 +268,24 @@ github.com/labstack/echo-contrib v0.12.0/go.mod h1:kR62TbwsBgmpV2HVab5iQRsQtLuhP github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k= github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -243,6 +298,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -250,11 +307,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k= +github.com/oapi-codegen/echo-middleware v1.0.1/go.mod h1:DBQKRn+D/vfXOFbaX5GRwFttoJY64JH6yu+pdt7wU3o= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -264,6 +327,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= +github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= +github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -306,6 +371,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -320,8 +386,14 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/traPtitech/go-traq v0.0.0-20240420012203-0152d96098b0 h1:qXV2Edt1yRQulqcNv7G9mg+dmkJPX9XXOGSHLuZk+Os= +github.com/traPtitech/go-traq v0.0.0-20240420012203-0152d96098b0/go.mod h1:iac/zAWmOCe6eGomX2U8EeOAM1kwrm5TM19gRMOxJrU= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -358,6 +430,13 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -396,6 +475,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -439,6 +520,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -447,6 +536,8 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -461,6 +552,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -523,6 +616,13 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -540,12 +640,18 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -594,6 +700,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -686,6 +794,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -704,9 +816,11 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y= diff --git a/handler/middleware.go b/handler/middleware.go new file mode 100644 index 00000000..48718891 --- /dev/null +++ b/handler/middleware.go @@ -0,0 +1,40 @@ +package handler + +import ( + "errors" + + "github.com/labstack/echo/v4" +) + +const ( + validatorKey = "validator" + userIDKey = "userID" + questionnaireIDKey = "questionnaireID" + responseIDKey = "responseID" + questionIDKey = "questionID" +) + +// SetUserIDMiddleware X-Showcase-UserからユーザーIDを取得しセットする +func SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + userID := c.Request().Header.Get("X-Showcase-User") + if userID == "" { + userID = "mds_boy" + } + + c.Set(userIDKey, userID) + + return next(c) + } +} + +// getUserID ユーザーIDを取得する +func getUserID(c echo.Context) (string, error) { + rowUserID := c.Get(userIDKey) + userID, ok := rowUserID.(string) + if !ok { + return "", errors.New("invalid context userID") + } + + return userID, nil +} diff --git a/handler/questionnaire.go b/handler/questionnaire.go index c58ad897..35630b62 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -1,19 +1,41 @@ package handler import ( + "fmt" + "net/http" + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/controller" "github.com/traPtitech/anke-to/openapi" ) // (GET /questionnaires) func (h Handler) GetQuestionnaires(ctx echo.Context, params openapi.GetQuestionnairesParams) error { res := openapi.QuestionnaireList{} + q := controller.NewQuestionnaire() + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + res, err = q.GetQuestionnaires(ctx, userID, params) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaires: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaires: %w", err)) + } return ctx.JSON(200, res) } // (POST /questionnaires) func (h Handler) PostQuestionnaire(ctx echo.Context) error { + params := openapi.PostQuestionnaireJSONRequestBody{} + if err := ctx.Bind(¶ms); err != nil { + ctx.Logger().Errorf("failed to bind request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) + } + res := openapi.QuestionnaireDetail{} return ctx.JSON(200, res) diff --git a/main.go b/main.go index 5c1be0ed..fe08cd25 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,12 @@ import ( "runtime" "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + oapiMiddleware "github.com/oapi-codegen/echo-middleware" "github.com/traPtitech/anke-to/handler" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" + "github.com/traPtitech/anke-to/tuning" ) @@ -55,8 +58,16 @@ func main() { } e := echo.New() + swagger, err := openapi.GetSwagger() + if err != nil { + panic(err) + } + e.Use(oapiMiddleware.OapiRequestValidator(swagger)) + e.Use(handler.SetUserIDMiddleware) + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) openapi.RegisterHandlers(e, handler.Handler{}) - e.Logger.Fatal(e.Start(":" + port)) + e.Logger.Fatal(e.Start(port)) // SetRouting(port) } diff --git a/model/administratorGroups.go b/model/administratorGroups.go new file mode 100644 index 00000000..dfddc73a --- /dev/null +++ b/model/administratorGroups.go @@ -0,0 +1,10 @@ +package model + +import "context" + +// IAdministratorGroup AdministratorGroupのRepository +type IAdministratorGroup interface { + InsertAdministratorGroups(ctx context.Context, questionnaireID int, administratorGroups []string) error + DeleteAdministratorGroups(ctx context.Context, questionnaireID int) error + GetAdministratorGroups(ctx context.Context, questionnaireIDs []int) ([]AdministratorGroups, error) +} diff --git a/model/administratorGroups_impl.go b/model/administratorGroups_impl.go new file mode 100644 index 00000000..a4953d43 --- /dev/null +++ b/model/administratorGroups_impl.go @@ -0,0 +1,83 @@ +package model + +import ( + "context" + "fmt" + + "github.com/gofrs/uuid" +) + +// AdministratorGroup AdministratorGroupRepositoryの実装 +type AdministratorGroup struct{} + +// NewAdministratorGroup AdministratorGroupRepositoryのコンストラクタ +func NewAdministratorGroup() *AdministratorGroup { + return &AdministratorGroup{} +} + +type AdministratorGroups struct { + QuestionnaireID int `gorm:"type:int(11);not null;primaryKey"` + GroupID uuid.UUID `gorm:"type:varchar(36);size:36;not null;primaryKey"` +} + +// InsertAdministratorGroups アンケートの管理者グループを追加 +func (*AdministratorGroup) InsertAdministratorGroups(ctx context.Context, questionnaireID int, groupID []uuid.UUID) error { + db, err := getTx(ctx) + if err != nil { + return fmt.Errorf("failed to get transaction: %w", err) + } + + if len(groupID) == 0 { + return nil + } + + dbAdministratorGroups := make([]AdministratorGroups, 0, len(groupID)) + for _, administratorGroup := range groupID { + dbAdministratorGroups = append(dbAdministratorGroups, AdministratorGroups{ + QuestionnaireID: questionnaireID, + GroupID: administratorGroup, + }) + } + + err = db.Create(&dbAdministratorGroups).Error + if err != nil { + return fmt.Errorf("failed to insert administrator groups: %w", err) + } + + return nil +} + +// DeleteAdministratorGroups アンケートの管理者グループを削除 +func (*AdministratorGroup) DeleteAdministratorGroups(ctx context.Context, questionnaireID int) error { + db, err := getTx(ctx) + if err != nil { + return fmt.Errorf("failed to get transaction: %w", err) + } + + err = db. + Where("questionnaire_id = ?", questionnaireID). + Delete(AdministratorGroups{}).Error + if err != nil { + return fmt.Errorf("failed to delete administrator groups: %w", err) + } + + return nil +} + +// GetAdministratorGroups アンケートの管理者グループを取得 +func (*AdministratorGroup) GetAdministratorGroups(ctx context.Context, questionnaireIDs []int) ([]AdministratorGroups, error) { + db, err := getTx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get transaction: %w", err) + } + + var administratorGroups []AdministratorGroups + err = db. + Where("questionnaire_id IN ?", questionnaireIDs). + Find(&administratorGroups).Error + if err != nil { + return nil, fmt.Errorf("failed to get administrator groups: %w", err) + } + + return administratorGroups, nil +} diff --git a/model/questionnaires.go b/model/questionnaires.go index ec759d2e..e7f30005 100644 --- a/model/questionnaires.go +++ b/model/questionnaires.go @@ -13,7 +13,7 @@ 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 DeleteQuestionnaire(ctx context.Context, questionnaireID int) error - GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, nontargeted bool) ([]QuestionnaireInfo, int, error) + GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, onlyTargetingMe bool, onlyAdministratedByMe bool) ([]QuestionnaireInfo, int, error) GetAdminQuestionnaires(ctx context.Context, userID string) ([]Questionnaires, error) GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []string, []string, error) GetTargettedQuestionnaires(ctx context.Context, userID string, answered string, sort string) ([]TargettedQuestionnaire, error) diff --git a/model/questionnaires_impl.go b/model/questionnaires_impl.go index e7756bc7..1f1e9d3c 100755 --- a/model/questionnaires_impl.go +++ b/model/questionnaires_impl.go @@ -31,6 +31,7 @@ type Questionnaires struct { 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"` + TargetGroups []TargetGroups `json:"-" gorm:"foreignKey:QuestionnaireID"` Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` } @@ -171,7 +172,7 @@ func (*Questionnaire) DeleteQuestionnaire(ctx context.Context, questionnaireID i GetQuestionnaires アンケートの一覧 2つ目の戻り値はページ数の最大値 */ -func (*Questionnaire) GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, nontargeted bool) ([]QuestionnaireInfo, int, error) { +func (*Questionnaire) GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, onlyTargetingMe bool, onlyAdministratedByMe bool) ([]QuestionnaireInfo, int, error) { ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() @@ -192,9 +193,16 @@ func (*Questionnaire) GetQuestionnaires(ctx context.Context, userID string, sort return nil, 0, fmt.Errorf("failed to set the order of the questionnaire table: %w", err) } - if nontargeted { - query = query.Where("targets.questionnaire_id IS NULL OR (targets.user_traqid != ? AND targets.user_traqid != 'traP')", userID) + if onlyTargetingMe { + query = query.Where("targets.user_traqid = ? OR targets.user_traqid = 'traP'", userID) } + + if onlyAdministratedByMe { + query = query. + Joins("INNER JOIN administrators ON questionnaires.id = administrators.questionnaire_id"). + Where("administrators.user_traqid = ?", userID) + } + if len(search) != 0 { // MySQLでのregexpの構文は少なくともGoのregexpの構文でvalidである必要がある _, err := regexp.Compile(search) @@ -315,7 +323,7 @@ func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID return nil, nil, nil, nil, fmt.Errorf("failed to get respondents: %w", err) } - return &questionnaire, targets, administrators, respondents, nil + return &questionnaire, targets, targetGroups, administrators, administoratorGroups, respondents, nil } // GetTargettedQuestionnaires targetになっているアンケートの取得 diff --git a/model/questions_impl.go b/model/questions_impl.go index 0ad5335a..6b48077e 100644 --- a/model/questions_impl.go +++ b/model/questions_impl.go @@ -17,7 +17,7 @@ func NewQuestion() *Question { return new(Question) } -//Questions questionテーブルの構造体 +// Questions questionテーブルの構造体 type Questions struct { ID int `json:"id" gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` QuestionnaireID int `json:"questionnaireID" gorm:"type:int(11);not null"` @@ -41,18 +41,18 @@ func (questionnaire *Questions) BeforeCreate(tx *gorm.DB) error { return nil } -//TableName テーブル名が単数形なのでその対応 +// TableName テーブル名が単数形なのでその対応 func (*Questions) TableName() string { return "question" } -//QuestionIDType 質問のIDと種類の構造体 +// QuestionIDType 質問のIDと種類の構造体 type QuestionIDType struct { ID int Type string } -//InsertQuestion 質問の追加 +// InsertQuestion 質問の追加 func (*Question) InsertQuestion(ctx context.Context, questionnaireID int, pageNum int, questionNum int, questionType string, body string, isRequired bool) (int, error) { db, err := getTx(ctx) if err != nil { @@ -77,7 +77,7 @@ func (*Question) InsertQuestion(ctx context.Context, questionnaireID int, pageNu return question.ID, nil } -//UpdateQuestion 質問の修正 +// UpdateQuestion 質問の修正 func (*Question) UpdateQuestion(ctx context.Context, questionnaireID int, pageNum int, questionNum int, questionType string, body string, isRequired bool, questionID int) error { db, err := getTx(ctx) if err != nil { @@ -104,7 +104,7 @@ func (*Question) UpdateQuestion(ctx context.Context, questionnaireID int, pageNu return nil } -//DeleteQuestion 質問の削除 +// DeleteQuestion 質問の削除 func (*Question) DeleteQuestion(ctx context.Context, questionID int) error { db, err := getTx(ctx) if err != nil { @@ -125,7 +125,7 @@ func (*Question) DeleteQuestion(ctx context.Context, questionID int) error { return nil } -//GetQuestions 質問一覧の取得 +// GetQuestions 質問一覧の取得 func (*Question) GetQuestions(ctx context.Context, questionnaireID int) ([]Questions, error) { db, err := getTx(ctx) if err != nil { diff --git a/model/targetGroups.go b/model/targetGroups.go new file mode 100644 index 00000000..dbb58abd --- /dev/null +++ b/model/targetGroups.go @@ -0,0 +1,12 @@ +package model + +import ( + "context" +) + +// ITargetGroup TargetGroupのRepository +type ITargetGroup interface { + InsertTargetGroups(ctx context.Context, questionnaireID int, groupID []string) error + GetTargetGroups(ctx context.Context, questionnaireIDs []int) ([]TargetGroups, error) + DeleteTargetGroups(ctx context.Context, questionnaireIDs []int) error +} diff --git a/model/targetGroups_impl.go b/model/targetGroups_impl.go new file mode 100644 index 00000000..ea04f23d --- /dev/null +++ b/model/targetGroups_impl.go @@ -0,0 +1,84 @@ +package model + +import ( + "context" + "fmt" + + "github.com/gofrs/uuid" +) + +// TargetGroup TargetGroupsRepositoryの実装 +type TargetGroup struct{} + +// NewTargetGroups TargetGroupsのコンストラクター +func NewTargetGroups() *TargetGroups { + return new(TargetGroups) +} + +// TargetGroups targets_groupsテーブルの構造体 +type TargetGroups struct { + QuestionnaireID int `gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` + GroupID uuid.UUID `gorm:"type:char(36);size:36;not null;primaryKey"` +} + +// InsertTargetGroups アンケートの対象としてuser_groupを追加 +func (*TargetGroup) InsertTargetGroups(ctx context.Context, questionnaireID int, groupID []uuid.UUID) error { + db, err := getTx(ctx) + if err != nil { + return fmt.Errorf("failed to get transaction: %w", err) + } + + if len(groupID) == 0 { + return nil + } + + dbTargetGroups := make([]TargetGroups, 0, len(groupID)) + for _, targetGroup := range groupID { + dbTargetGroups = append(dbTargetGroups, TargetGroups{ + QuestionnaireID: questionnaireID, + GroupID: targetGroup, + }) + } + + err = db.Create(&dbTargetGroups).Error + if err != nil { + return fmt.Errorf("failed to insert target groups: %w", err) + } + + return nil +} + +// GetTargetGroups アンケートの対象としてuser_groupを取得 +func (*TargetGroup) GetTargetGroups(ctx context.Context, questionnaireIDs []int) ([]TargetGroups, error) { + db, err := getTx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get transaction: %w", err) + } + + var targetGroups []TargetGroups + err = db. + Where("questionnaire_id IN ?", questionnaireIDs). + Find(&targetGroups).Error + if err != nil { + return nil, fmt.Errorf("failed to get target groups: %w", err) + } + + return targetGroups, nil +} + +// DeleteTargetGroups アンケートの対象としてuser_groupを削除 +func (*TargetGroup) DeleteTargetGroups(ctx context.Context, questionnaireID int) error { + db, err := getTx(ctx) + if err != nil { + return fmt.Errorf("failed to get transaction: %w", err) + } + + err = db. + Where("questionnaire_id = ?", questionnaireID). + Delete(&TargetGroups{}).Error + if err != nil { + return fmt.Errorf("failed to delete target groups: %w", err) + } + + return nil +} diff --git a/traq/traq.go b/traq/traq.go new file mode 100644 index 00000000..6f08b8ac --- /dev/null +++ b/traq/traq.go @@ -0,0 +1,45 @@ +package traq + +import ( + "context" + + traq "github.com/traPtitech/go-traq" +) + +const TOKEN = "/* your token */" + +type TraqAPIClient struct { + client *traq.APIClient + auth context.Context +} + +func NewTraqAPIClient() *TraqAPIClient { + return &TraqAPIClient{ + client: traq.NewAPIClient(traq.NewConfiguration()), + auth: context.WithValue(context.Background(), traq.ContextAccessToken, TOKEN), + } +} + +func (t *TraqAPIClient) GetGroupMembers(ctx context.Context, groupID string) ([]traq.UserGroupMember, error) { + v, _, err := t.client.GroupApi.GetUserGroupMembers(ctx, groupID).Execute() + if err != nil { + return nil, err + } + return v, nil +} + +func (t *TraqAPIClient) GetUserTraqID(ctx context.Context, userUUID string) (string, error) { + v, _, err := t.client.UserApi.GetUser(ctx, userUUID).Execute() + if err != nil { + return "", err + } + return v.Name, nil +} + +func (t *TraqAPIClient) GetGroupName(ctx context.Context, groupID string) (string, error) { + v, _, err := t.client.GroupApi.GetUserGroup(ctx, groupID).Execute() + if err != nil { + return "", err + } + return v.Name, nil +} From 7a7fc48454ef2a65a63278301d0f77ed6d8bf756 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Tue, 14 May 2024 15:25:42 +0900 Subject: [PATCH 06/83] until deletequestionnaire --- controller/adapter.go | 4 +- controller/questionnaire.go | 120 +++++++++++++++++++++++++++++++++++- controller/utils.go | 2 +- handler/questionnaire.go | 39 +++++++++++- model/questionnaires.go | 2 +- model/targetGroups.go | 2 +- 6 files changed, 161 insertions(+), 8 deletions(-) diff --git a/controller/adapter.go b/controller/adapter.go index 05cdfc70..1b6bbc3a 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -144,7 +144,7 @@ func convertRespondents(respondents []model.Respondents) []string { return res } -func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, adminUsers []string, adminGroups []string, targetUsers []string, targetGroups []string) openapi.QuestionnaireDetail { +func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, adminUsers []string, adminGroups []string, targetUsers []string, targetGroups []string, respondents []string) openapi.QuestionnaireDetail { res := openapi.QuestionnaireDetail{ Admins: createUsersAndGroups(adminUsers, adminGroups), CreatedAt: questionnaires.CreatedAt, @@ -155,7 +155,7 @@ func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, admi ModifiedAt: questionnaires.ModifiedAt, QuestionnaireId: questionnaires.ID, Questions: convertQuestions(questionnaires.Questions), - Respondents: convertRespondents(questionnaires.Respondents), + Respondents: respondents, ResponseDueDateTime: &questionnaires.ResTimeLimit.Time, ResponseViewableBy: convertResSharedTo(questionnaires.ResSharedTo), Targets: createUsersAndGroups(targetUsers, targetGroups), diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 43835490..22cef948 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -2,6 +2,7 @@ package controller import ( "context" + "errors" "fmt" "net/http" "strings" @@ -184,15 +185,130 @@ 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") } - questionnaireInfo, _, _, _, err := q.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) + 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) return openapi.QuestionnaireDetail{}, echo.NewHTTPError(http.StatusInternalServerError, "failed to get questionnaire info") } - questionnaireDetail := questionnaire2QuestionnaireDetail(*questionnaireInfo, params.Admins.Users, params.Admins.Groups, params.Targets.Users, params.Targets.Groups) + questionnaireDetail := questionnaire2QuestionnaireDetail(*questionnaireInfo, admins, adminGroups, targets, targetGroups, respondents) return questionnaireDetail, nil } +func (q Questionnaire) GetQuestionnaire(ctx echo.Context, questionnaireID int) (openapi.QuestionnaireDetail, error) { + questionnaireInfo, targets, targetGroups, admins, adminGroups, respondents, err := q.GetQuestionnaireInfo(ctx.Request().Context(), questionnaireID) + if err != nil { + return openapi.QuestionnaireDetail{}, err + } + questionnaireDetail := questionnaire2QuestionnaireDetail(*questionnaireInfo, admins, adminGroups, targets, targetGroups, respondents) + return questionnaireDetail, nil +} + +func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, params openapi.EditQuestionnaireJSONRequestBody) error { + responseDueDateTime := null.Time{} + if params.ResponseDueDateTime != nil { + responseDueDateTime.Valid = true + responseDueDateTime.Time = *params.ResponseDueDateTime + } + err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { + err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID) + if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { + c.Logger().Errorf("failed to update questionnaire: %+v", err) + return err + } + err = q.DeleteTargets(ctx, questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete targets: %+v", err) + return err + } + err = q.DeleteTargetGroups(ctx, questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete target groups: %+v", err) + return err + } + allTargetUsers, err := rollOutUsersAndGroups(params.Targets.Users, params.Targets.Groups) + if err != nil { + c.Logger().Errorf("failed to roll out users and groups: %+v", err) + return err + } + err = q.InsertTargets(ctx, questionnaireID, allTargetUsers) + if err != nil { + c.Logger().Errorf("failed to insert targets: %+v", err) + return err + } + err = q.InsertTargetGroups(ctx, questionnaireID, params.Targets.Groups) + if err != nil { + c.Logger().Errorf("failed to insert target groups: %+v", err) + return err + } + err = q.DeleteAdministrators(ctx, questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete administrators: %+v", err) + return err + } + err = q.DeleteAdministratorGroups(ctx, questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete administrator groups: %+v", err) + return err + } + allAdminUsers, err := rollOutUsersAndGroups(params.Admins.Users, params.Admins.Groups) + if err != nil { + c.Logger().Errorf("failed to roll out administrators: %+v", err) + return err + } + err = q.InsertAdministrators(ctx, questionnaireID, allAdminUsers) + if err != nil { + c.Logger().Errorf("failed to insert administrators: %+v", err) + return err + } + err = q.InsertAdministratorGroups(ctx, questionnaireID, params.Admins.Groups) + if err != nil { + c.Logger().Errorf("failed to insert administrator groups: %+v", err) + return err + } + + return nil + }) + if err != nil { + c.Logger().Errorf("failed to update a questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to update a questionnaire") + } + + return nil +} + +func (q Questionnaire) DeleteQuestionnaire(c echo.Context, questionnaireID int) error { + err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { + err := q.IQuestionnaire.DeleteQuestionnaire(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete questionnaire: %+v", err) + return err + } + + err = q.DeleteTargets(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete targets: %+v", err) + return err + } + + err = q.DeleteAdministrators(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete administrators: %+v", err) + return err + } + + return nil + }) + if err != nil { + var httpError *echo.HTTPError + if errors.As(err, &httpError) { + return httpError + } + + c.Logger().Errorf("failed to delete questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to delete a questionnaire") + } + return nil +} func createQuestionnaireMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit null.Time, targets []string) string { var resTimeLimitText string diff --git a/controller/utils.go b/controller/utils.go index 121281b0..fe9df181 100644 --- a/controller/utils.go +++ b/controller/utils.go @@ -5,7 +5,7 @@ import ( "slices" mapset "github.com/deckarep/golang-set/v2" - "github.com/gofrs/uuid" + "github.com/google/uuid" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/traq" ) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 35630b62..bcb52285 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -37,6 +37,18 @@ func (h Handler) PostQuestionnaire(ctx echo.Context) error { } res := openapi.QuestionnaireDetail{} + q := controller.NewQuestionnaire() + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + res, err = q.PostQuestionnaire(ctx, userID, params) + if err != nil { + ctx.Logger().Errorf("failed to post questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to post questionnaire: %w", err)) + } return ctx.JSON(200, res) } @@ -44,17 +56,42 @@ func (h Handler) PostQuestionnaire(ctx echo.Context) error { // (GET /questionnaires/{questionnaireID}) func (h Handler) GetQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.QuestionnaireDetail{} - + q := controller.NewQuestionnaire() + res, err := q.GetQuestionnaire(ctx, questionnaireID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire: %w", err)) + } return ctx.JSON(200, res) } // (PATCH /questionnaires/{questionnaireID}) func (h Handler) EditQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + params := openapi.EditQuestionnaireJSONRequestBody{} + if err := ctx.Bind(¶ms); err != nil { + ctx.Logger().Errorf("failed to bind request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) + } + + q := controller.NewQuestionnaire() + err := q.EditQuestionnaire(ctx, questionnaireID, params) + if err != nil { + ctx.Logger().Errorf("failed to edit questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to edit questionnaire: %w", err)) + } + return ctx.NoContent(200) } // (DELETE /questionnaires/{questionnaireID}) func (h Handler) DeleteQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + q := controller.NewQuestionnaire() + err := q.DeleteQuestionnaire(ctx, questionnaireID) + if err != nil { + ctx.Logger().Errorf("failed to delete questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete questionnaire: %w", err)) + } + return ctx.NoContent(200) } diff --git a/model/questionnaires.go b/model/questionnaires.go index e7f30005..8338abbf 100644 --- a/model/questionnaires.go +++ b/model/questionnaires.go @@ -15,7 +15,7 @@ type IQuestionnaire interface { DeleteQuestionnaire(ctx context.Context, questionnaireID int) error GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, onlyTargetingMe bool, onlyAdministratedByMe bool) ([]QuestionnaireInfo, int, error) GetAdminQuestionnaires(ctx context.Context, userID string) ([]Questionnaires, error) - GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []string, []string, error) + GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []string, []string, []string, []string, error) GetTargettedQuestionnaires(ctx context.Context, userID string, answered string, sort string) ([]TargettedQuestionnaire, error) GetQuestionnaireLimit(ctx context.Context, questionnaireID int) (null.Time, error) GetQuestionnaireLimitByResponseID(ctx context.Context, responseID int) (null.Time, error) diff --git a/model/targetGroups.go b/model/targetGroups.go index dbb58abd..7217c148 100644 --- a/model/targetGroups.go +++ b/model/targetGroups.go @@ -8,5 +8,5 @@ import ( type ITargetGroup interface { InsertTargetGroups(ctx context.Context, questionnaireID int, groupID []string) error GetTargetGroups(ctx context.Context, questionnaireIDs []int) ([]TargetGroups, error) - DeleteTargetGroups(ctx context.Context, questionnaireIDs []int) error + DeleteTargetGroups(ctx context.Context, questionnaireIDs int) error } From df97a1d3ec4311d8db244908e6407506d08d2936 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:40:03 +0900 Subject: [PATCH 07/83] some api implmented --- controller/adapter.go | 113 +++++++++++++++++++++++++++++++++++ controller/questionnaire.go | 30 ++++++++++ go.mod | 2 +- go.sum | 2 + handler/questionnaire.go | 21 ++++++- model/questionnaires_impl.go | 19 +++--- model/questionnaires_test.go | 2 +- model/respondents.go | 2 +- model/respondents_impl.go | 10 +++- 9 files changed, 187 insertions(+), 14 deletions(-) diff --git a/controller/adapter.go b/controller/adapter.go index 1b6bbc3a..110703d9 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -1,6 +1,9 @@ package controller import ( + "strconv" + + "github.com/labstack/echo/v4" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" "gopkg.in/guregu/null.v4" @@ -163,3 +166,113 @@ func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, admi } return res } + +func respondentDetail2Response(ctx echo.Context, respondentDetail model.RespondentDetail) (openapi.Response, error) { + oResponseBodies := []openapi.ResponseBody{} + for j, r := range respondentDetail.Responses { + oResponseBody := openapi.ResponseBody{} + switch r.QuestionType { + case "Text": + if r.Body.Valid { + oResponseBody.FromResponseBodyText( + openapi.ResponseBodyText{ + Answer: r.Body.String, + QuestionType: "Text", + }, + ) + } + case "TextArea": + if r.Body.Valid { + oResponseBody.FromResponseBodyText( + openapi.ResponseBodyText{ + Answer: r.Body.String, + QuestionType: "TextLong", + }, + ) + } + case "Number": + if r.Body.Valid { + answer, err := strconv.ParseFloat(r.Body.String, 32) + if err != nil { + ctx.Logger().Errorf("failed to convert string to float: %+v", err) + return openapi.Response{}, err + } + oResponseBody.FromResponseBodyNumber( + openapi.ResponseBodyNumber{ + Answer: float32(answer), + QuestionType: "Number", + }, + ) + } + case "MultipleChoice": + if r.Body.Valid { + answer := []int{} + questionnaire, _, _, _, _, _, err := model.NewQuestionnaire().GetQuestionnaireInfo(ctx.Request().Context(), r.QuestionID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire info: %+v", err) + return openapi.Response{}, err + } + for _, a := range r.OptionResponse { + for i, o := range questionnaire.Questions[j].Options { + if a == o.Body { + answer = append(answer, i) + } + } + } + oResponseBody.FromResponseBodyMultipleChoice( + openapi.ResponseBodyMultipleChoice{ + Answer: answer, + QuestionType: "MultipleChoice", + }, + ) + } + case "Checkbox": + if r.Body.Valid { + questionnaire, _, _, _, _, _, err := model.NewQuestionnaire().GetQuestionnaireInfo(ctx.Request().Context(), r.QuestionID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire info: %+v", err) + return openapi.Response{}, err + } + for _, a := range r.OptionResponse { + for i, o := range questionnaire.Questions[j].Options { + if a == o.Body { + oResponseBody.FromResponseBodySingleChoice( + openapi.ResponseBodySingleChoice{ + Answer: i, + QuestionType: "SingleChoice", + }, + ) + } + } + } + } + case "LinearScale": + if r.Body.Valid { + answer, err := strconv.Atoi(r.Body.String) + if err != nil { + ctx.Logger().Errorf("failed to convert string to int: %+v", err) + return openapi.Response{}, err + } + oResponseBody.FromResponseBodyScale( + openapi.ResponseBodyScale{ + Answer: answer, + QuestionType: "LinearScale", + }, + ) + } + } + oResponseBodies = append(oResponseBodies, oResponseBody) + } + + res := openapi.Response{ + Body: oResponseBodies, + IsDraft: respondentDetail.SubmittedAt.Valid, + ModifiedAt: respondentDetail.ModifiedAt, + QuestionnaireId: respondentDetail.QuestionnaireID, + Respondent: respondentDetail.TraqID, + ResponseId: respondentDetail.ResponseID, + SubmittedAt: respondentDetail.SubmittedAt.Time, + } + + return res, nil +} diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 22cef948..a7459361 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -310,6 +310,36 @@ func (q Questionnaire) DeleteQuestionnaire(c echo.Context, questionnaireID int) return nil } +func (q Questionnaire) GetQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int) (bool, error) { + // todo: check remind status + return false, nil +} + +func (q Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int) error { + // todo: edit remind status + return nil +} + +func (q Questionnaire) GetQuestionnaireResponses(c echo.Context, questionnaireID int, params openapi.GetQuestionnaireResponsesParams, userID string) (openapi.Responses, error) { + res := []openapi.Response{} + respondentDetails, err := q.GetRespondentDetails(c.Request().Context(), questionnaireID, string(*params.Sort), *params.OnlyMyResponse, userID) + if err != nil { + c.Logger().Errorf("failed to get respondent details: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, "failed to get respondent details") + } + + for _, respondentDetail := range respondentDetails { + response, err := respondentDetail2Response(c, respondentDetail) + if err != nil { + c.Logger().Errorf("failed to convert respondent detail to response: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, "failed to convert respondent detail to response") + } + res = append(res, response) + } + + return res, nil +} + func createQuestionnaireMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit null.Time, targets []string) string { var resTimeLimitText string if resTimeLimit.Valid { diff --git a/go.mod b/go.mod index d5fee144..acb59648 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/wire v0.5.0 github.com/labstack/echo/v4 v4.12.0 github.com/mattn/go-isatty v0.0.20 // indirect - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.22.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.24.0 // indirect diff --git a/go.sum b/go.sum index e9cfe8f2..26e92dc0 100644 --- a/go.sum +++ b/go.sum @@ -388,6 +388,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/traPtitech/go-traq v0.0.0-20240420012203-0152d96098b0 h1:qXV2Edt1yRQulqcNv7G9mg+dmkJPX9XXOGSHLuZk+Os= github.com/traPtitech/go-traq v0.0.0-20240420012203-0152d96098b0/go.mod h1:iac/zAWmOCe6eGomX2U8EeOAM1kwrm5TM19gRMOxJrU= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= diff --git a/handler/questionnaire.go b/handler/questionnaire.go index bcb52285..b7daa0b4 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -98,18 +98,37 @@ func (h Handler) DeleteQuestionnaire(ctx echo.Context, questionnaireID openapi.Q // (GET /questionnaires/{questionnaireID}/myRemindStatus) func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.QuestionnaireIsRemindEnabled{} + q := controller.NewQuestionnaire() + status, err := q.GetQuestionnaireMyRemindStatus(ctx, questionnaireID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire my remind status: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire my remind status: %w", err)) + } + res.IsRemindEnabled = status return ctx.JSON(200, res) } // (PATCH /questionnaires/{questionnaireID}/myRemindStatus) func (h Handler) EditQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + q := controller.NewQuestionnaire() + err := q.EditQuestionnaireMyRemindStatus(ctx, questionnaireID) + if err != nil { + ctx.Logger().Errorf("failed to edit questionnaire my remind status: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to edit questionnaire my remind status: %w", err)) + } return ctx.NoContent(200) } // (GET /questionnaires/{questionnaireID}/responses) func (h Handler) GetQuestionnaireResponses(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath, params openapi.GetQuestionnaireResponsesParams) error { - res := openapi.Responses{} + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + q := controller.NewQuestionnaire() + res, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) return ctx.JSON(200, res) } diff --git a/model/questionnaires_impl.go b/model/questionnaires_impl.go index 1f1e9d3c..da751531 100755 --- a/model/questionnaires_impl.go +++ b/model/questionnaires_impl.go @@ -7,6 +7,7 @@ import ( "regexp" "time" + "github.com/google/uuid" "gopkg.in/guregu/null.v4" "gorm.io/gorm" ) @@ -275,25 +276,27 @@ func (*Questionnaire) GetAdminQuestionnaires(ctx context.Context, userID string) } // GetQuestionnaireInfo アンケートの詳細な情報取得 -func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []string, []string, error) { +func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []uuid.UUID, []string, []uuid.UUID, []string, error) { db, err := getTx(ctx) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to get tx: %w", err) + return nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get tx: %w", err) } questionnaire := Questionnaires{} targets := []string{} + targetGroups := []uuid.UUID{} administrators := []string{} + administoratorGroups := []uuid.UUID{} respondents := []string{} err = db. Where("questionnaires.id = ?", questionnaireID). First(&questionnaire).Error if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil, nil, nil, ErrRecordNotFound + return nil, nil, nil, nil, nil, nil, ErrRecordNotFound } if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to get a questionnaire: %w", err) + return nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get a questionnaire: %w", err) } err = db. @@ -302,7 +305,7 @@ func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID Where("questionnaire_id = ?", questionnaire.ID). Pluck("user_traqid", &targets).Error if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to get targets: %w", err) + return nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get targets: %w", err) } err = db. @@ -311,7 +314,7 @@ func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID Where("questionnaire_id = ?", questionnaire.ID). Pluck("user_traqid", &administrators).Error if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to get administrators: %w", err) + return nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get administrators: %w", err) } err = db. @@ -320,7 +323,7 @@ func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID Where("questionnaire_id = ? AND deleted_at IS NULL AND submitted_at IS NOT NULL", questionnaire.ID). Pluck("user_traqid", &respondents).Error if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to get respondents: %w", err) + return nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get respondents: %w", err) } return &questionnaire, targets, targetGroups, administrators, administoratorGroups, respondents, nil @@ -417,6 +420,7 @@ func (*Questionnaire) GetQuestionnaireLimitByResponseID(ctx context.Context, res return res.ResTimeLimit, nil } +// GetResponseReadPrivilegeInfoByResponseID 回答のIDから回答の閲覧権限情報を取得 func (*Questionnaire) GetResponseReadPrivilegeInfoByResponseID(ctx context.Context, userID string, responseID int) (*ResponseReadPrivilegeInfo, error) { db, err := getTx(ctx) if err != nil { @@ -442,6 +446,7 @@ func (*Questionnaire) GetResponseReadPrivilegeInfoByResponseID(ctx context.Conte return &responseReadPrivilegeInfo, nil } +// GetResponseReadPrivilegeInfoByQuestionnaireID アンケートのIDから回答の閲覧権限情報を取得 func (*Questionnaire) GetResponseReadPrivilegeInfoByQuestionnaireID(ctx context.Context, userID string, questionnaireID int) (*ResponseReadPrivilegeInfo, error) { db, err := getTx(ctx) if err != nil { diff --git a/model/questionnaires_test.go b/model/questionnaires_test.go index 16b80506..c3b716cf 100644 --- a/model/questionnaires_test.go +++ b/model/questionnaires_test.go @@ -1324,7 +1324,7 @@ func getQuestionnaireInfoTest(t *testing.T) { for _, testCase := range testCases { ctx := context.Background() - actualQuestionnaire, actualTargets, actualAdministrators, actualRespondents, err := questionnaireImpl.GetQuestionnaireInfo(ctx, testCase.questionnaireID) + actualQuestionnaire, actualTargets, actualTargetGroups, actualAdministrators, actualAdministratorGroups, actualRespondents, err := questionnaireImpl.GetQuestionnaireInfo(ctx, testCase.questionnaireID) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") diff --git a/model/respondents.go b/model/respondents.go index 67504296..56813245 100644 --- a/model/respondents.go +++ b/model/respondents.go @@ -16,7 +16,7 @@ type IRespondent interface { GetRespondent(ctx context.Context, responseID int) (*Respondents, error) GetRespondentInfos(ctx context.Context, userID string, questionnaireIDs ...int) ([]RespondentInfo, error) GetRespondentDetail(ctx context.Context, responseID int) (RespondentDetail, error) - GetRespondentDetails(ctx context.Context, questionnaireID int, sort string) ([]RespondentDetail, error) + GetRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) CheckRespondent(ctx context.Context, userID string, questionnaireID int) (bool, error) } diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 29019230..bcf55d27 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -257,7 +257,7 @@ func (*Respondent) GetRespondentDetail(ctx context.Context, responseID int) (Res } // GetRespondentDetails アンケートの回答の詳細情報一覧の取得 -func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int, sort string) ([]RespondentDetail, error) { +func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) { db, err := getTx(ctx) if err != nil { return nil, fmt.Errorf("failed to get tx: %w", err) @@ -306,7 +306,7 @@ func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int } questions := []Questions{} - err = db. + query = db. Preload("Responses", func(db *gorm.DB) *gorm.DB { return db. Select("ResponseID", "QuestionID", "Body"). @@ -314,7 +314,11 @@ func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int }). Where("questionnaire_id = ?", questionnaireID). Order("question_num"). - Select("ID", "Type"). + Select("ID", "Type") + if onlyMyResponse { + query = query.Where("user_traqid = ?", userID) + } + err = query. Find(&questions).Error if err != nil { return nil, fmt.Errorf("failed to get questions: %w", err) From 60007b86cdb4898d0d0af41b09ef50b3113626ee Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:10:01 +0900 Subject: [PATCH 08/83] chore: Update Go version to 1.22 in GitHub Actions workflow --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5c8d7c8e..212df284 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.22' - uses: actions/checkout@v4 - uses: actions/cache@v3.3.2 with: From 7f74909ad766b116e4b1fe49575f1aa4cafb74a1 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:12:22 +0900 Subject: [PATCH 09/83] fix version number --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 212df284..d288a744 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.22' + go-version: '1.22.2' - uses: actions/checkout@v4 - uses: actions/cache@v3.3.2 with: From a17788f7b80485c174185f271967473233735237 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:14:49 +0900 Subject: [PATCH 10/83] upgrade go version --- .github/workflows/main.yml | 6 +++--- go.mod | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d288a744..7f86efbd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.22.2' - uses: actions/checkout@v4 - uses: actions/cache@v3.3.2 with: @@ -68,7 +68,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.22.2' - uses: actions/checkout@v4 - uses: actions/cache@v3.3.2 with: @@ -99,7 +99,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.22.2' - uses: actions/checkout@v4 - name: golangci-lint uses: reviewdog/action-golangci-lint@v2.3 diff --git a/go.mod b/go.mod index acb59648..ee1468c6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/traPtitech/anke-to -go 1.20 +go 1.22.2 require ( github.com/golang/mock v1.6.0 From b34ce6eb6c43edb8dd716f9464316933ab242683 Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Sun, 23 Jun 2024 01:54:03 +0900 Subject: [PATCH 11/83] wip --- controller/questionnaire.go | 4 ++++ handler/middleware.go | 13 +++++++++++++ handler/questionnaire.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index a7459361..9ccbf238 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -340,6 +340,10 @@ func (q Questionnaire) GetQuestionnaireResponses(c echo.Context, questionnaireID return res, nil } +func (q Questionnaire) PostQuestionnaireResponse(c echo.Context) error { + // todo: PostQuestionnaireResponse +} + func createQuestionnaireMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit null.Time, targets []string) string { var resTimeLimitText string if resTimeLimit.Valid { diff --git a/handler/middleware.go b/handler/middleware.go index 48718891..d40ec3af 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -2,7 +2,9 @@ package handler import ( "errors" + "fmt" + "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" ) @@ -28,6 +30,17 @@ func SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { } } +// getValidator Validatorを設定する +func getValidator(c echo.Context) (*validator.Validate, error) { + rowValidate := c.Get(validatorKey) + validate, ok := rowValidate.(*validator.Validate) + if !ok { + return nil, fmt.Errorf("failed to get validator") + } + + return validate, nil +} + // getUserID ユーザーIDを取得する func getUserID(c echo.Context) (string, error) { rowUserID := c.Get(userIDKey) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index b7daa0b4..729fa111 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -35,6 +35,17 @@ func (h Handler) PostQuestionnaire(ctx echo.Context) error { ctx.Logger().Errorf("failed to bind request body: %+v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) } + validate, err := getValidator(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get validator: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get validator: %w", err)) + } + + err = validate.StructCtx(ctx.Request().Context(), params) + if err != nil { + ctx.Logger().Errorf("failed to validate request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to validate request body: %w", err)) + } res := openapi.QuestionnaireDetail{} q := controller.NewQuestionnaire() @@ -136,6 +147,25 @@ func (h Handler) GetQuestionnaireResponses(ctx echo.Context, questionnaireID ope // (POST /questionnaires/{questionnaireID}/responses) func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.Response{} + params := openapi.PostQuestionnaireResponseJSONRequestBody{} + if err := ctx.Bind(¶ms); err != nil { + ctx.Logger().Errorf("failed to bind request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) + } + validate, err := getValidator(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get validator: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get validator: %w", err)) + } + + err = validate.StructCtx(ctx.Request().Context(), params) + if err != nil { + ctx.Logger().Errorf("failed to validate request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to validate request body: %w", err)) + } + + q := controller.NewQuestionnaire() + res, err = q.PostQuestionnaireResponse(ctx, questionnaireID, params) return ctx.JSON(201, res) } From 2e96d6130eed1e8e58e93cce7f3aa52ab4672b24 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Mon, 22 Jul 2024 03:01:16 +0000 Subject: [PATCH 12/83] feat: implement GetQuestionnaireResult --- handler/questionnaire.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 729fa111..202f125c 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -3,6 +3,7 @@ package handler import ( "fmt" "net/http" + "time" "github.com/labstack/echo/v4" "github.com/traPtitech/anke-to/controller" @@ -173,6 +174,38 @@ func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID ope // (GET /questionnaires/{questionnaireID}/result) func (h Handler) GetQuestionnaireResult(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.Result{} + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + params := openapi.GetQuestionnaireResponsesParams{} + q := controller.NewQuestionnaire() + responses, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %+w", err)) + } + + for _, response := range responses { + tmp := struct { + Body []openapi.ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + ResponseId int `json:"response_id"` + SubmittedAt time.Time `json:"submitted_at"` + }{ + Body: response.Body, + IsDraft: response.IsDraft, + ModifiedAt: response.ModifiedAt, + QuestionnaireId: response.QuestionnaireId, + ResponseId: response.ResponseId, + SubmittedAt: response.SubmittedAt, + } + res = append(res, tmp) + } return ctx.JSON(200, res) } From 9733c319772efb633b38b60626d2569ce17e9aa6 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Wed, 7 Aug 2024 14:08:31 +0900 Subject: [PATCH 13/83] feat: PostQuestionnaireResponse handler --- handler/questionnaire.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 729fa111..0490f43b 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -147,6 +147,12 @@ func (h Handler) GetQuestionnaireResponses(ctx echo.Context, questionnaireID ope // (POST /questionnaires/{questionnaireID}/responses) func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.Response{} + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + params := openapi.PostQuestionnaireResponseJSONRequestBody{} if err := ctx.Bind(¶ms); err != nil { ctx.Logger().Errorf("failed to bind request body: %+v", err) @@ -165,7 +171,11 @@ func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID ope } q := controller.NewQuestionnaire() - res, err = q.PostQuestionnaireResponse(ctx, questionnaireID, params) + res, err = q.PostQuestionnaireResponse(ctx, questionnaireID, params, userID) + if err != nil { + ctx.Logger().Errorf("failed to post questionnaire response: %+v", err) + return err + } return ctx.JSON(201, res) } From a26ceea6b0760a7ed606c16444729f38546c1455 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Wed, 7 Aug 2024 14:09:31 +0900 Subject: [PATCH 14/83] feat: Insert QuestionnaireResponse --- controller/questionnaire.go | 46 +++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 9ccbf238..9d8c8737 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -340,8 +340,50 @@ func (q Questionnaire) GetQuestionnaireResponses(c echo.Context, questionnaireID return res, nil } -func (q Questionnaire) PostQuestionnaireResponse(c echo.Context) error { - // todo: PostQuestionnaireResponse +func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID int, params openapi.PostQuestionnaireResponseJSONRequestBody, userID string) (openapi.Response, error) { + res := openapi.Response{} + + limit, err := q.GetQuestionnaireLimit(c.Request().Context(), questionnaireID) + if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + c.Logger().Info("questionnaire not found") + return res, echo.NewHTTPError(http.StatusNotFound, err) + } + c.Logger().Errorf("failed to get questionnaire limit: %+v", err) + } + + // 回答期限を過ぎていたらエラー + if limit.Valid && limit.Time.Before(time.Now()) { + c.Logger().Info("expired questionnaire") + return res, echo.NewHTTPError(http.StatusUnprocessableEntity, err) + } + + var submittedAt, modifiedAt time.Time + if params.IsDraft { + submittedAt = time.Time{} + modifiedAt = time.Time{} + } else { + submittedAt = time.Now() + modifiedAt = time.Now() + } + + resopnseID, err := q.InsertRespondent(c.Request().Context(), userID, questionnaireID, null.NewTime(submittedAt, !params.IsDraft)) + if err != nil { + c.Logger().Errorf("failed to insert respondant: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) + } + + res = openapi.Response{ + QuestionnaireId: questionnaireID, + ResponseId: resopnseID, + Respondent: userID, + SubmittedAt: submittedAt, + ModifiedAt: modifiedAt, + IsDraft: params.IsDraft, + Body: params.Body, + } + + return res, nil } func createQuestionnaireMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit null.Time, targets []string) string { From 65d79f51cf77932eaa9641ef207abe455c6c1670 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Fri, 16 Aug 2024 19:43:49 +0900 Subject: [PATCH 15/83] fix: add missing error response --- controller/questionnaire.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 9d8c8737..2db3b479 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -350,6 +350,7 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID return res, echo.NewHTTPError(http.StatusNotFound, err) } c.Logger().Errorf("failed to get questionnaire limit: %+v", err) + return res, echo.NewHTTPError(http.StatusInternalServerError, err) } // 回答期限を過ぎていたらエラー @@ -359,6 +360,7 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID } var submittedAt, modifiedAt time.Time + //一時保存のときはnull if params.IsDraft { submittedAt = time.Time{} modifiedAt = time.Time{} From af155ecacd882ed0e929a4752defc6bba549886b Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:56:32 +0000 Subject: [PATCH 16/83] feat: implement GetMyResponses(wip) --- controller/questionnaire.go | 9 ---- controller/response.go | 103 ++++++++++++++++++++++++++++++++++++ handler/response.go | 17 +++++- model/respondents.go | 1 + model/respondents_impl.go | 19 +++++++ model/targets.go | 1 + model/targets_impl.go | 5 ++ 7 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 controller/response.go diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 9ccbf238..a0ac8ce9 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -15,15 +15,6 @@ import ( "gopkg.in/guregu/null.v4" ) -// Response Responseの構造体 -type Response struct { - model.IQuestionnaire - model.IValidation - model.IScaleLabel - model.IRespondent - model.IResponse -} - // Questionnaire Questionnaireの構造体 type Questionnaire struct { model.IQuestionnaire diff --git a/controller/response.go b/controller/response.go new file mode 100644 index 00000000..8d2df275 --- /dev/null +++ b/controller/response.go @@ -0,0 +1,103 @@ +package controller + +import ( + "fmt" + "net/http" + "time" + + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/model" + "github.com/traPtitech/anke-to/openapi" +) + +// Response Responseの構造体 +type Response struct { + model.IQuestionnaire + model.IRespondent + model.ITarget +} + +func NewResponse() *Response { + return &Response{} +} + +func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesParams, userID string) (openapi.ResponsesWithQuestionnaireInfo, error) { + res := openapi.ResponsesWithQuestionnaireInfo{} + + sort := string(*params.Sort) + responsesID := []int{} + responsesID, err := r.IRespondent.GetMyResponsesID(ctx.Request().Context(), sort, userID) + if err != nil { + ctx.Logger().Errorf("failed to get my responses ID: %+v", err) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %+w", err)) + } + + for _, responseID := range responsesID { + responseDetail, err := r.IRespondent.GetRespondentDetail(ctx.Request().Context(), responseID) + if err != nil { + ctx.Logger().Errorf("failed to get respondent detail: %+v", err) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondent detail: %+w", err)) + } + + questionnaire, _, _, _, _, _, err := r.IQuestionnaire.GetQuestionnaireInfo(ctx.Request().Context(), responseDetail.QuestionnaireID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire info: %+v", err) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire info: %+w", err)) + } + + isTargetingMe, err := r.ITarget.IsTargetingMe(ctx.Request().Context(), responseDetail.QuestionnaireID, userID) + if err != nil { + ctx.Logger().Errorf("failed to get target info: %+v", err) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get target info: %+w", err)) + } + + questionnaireInfo := struct { + CreatedAt time.Time `json:"created_at"` + IsTargetingMe bool `json:"is_targeting_me"` + ModifiedAt time.Time `json:"modified_at"` + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + Title string `json:"title"` + }{ + CreatedAt: questionnaire.CreatedAt, + IsTargetingMe: isTargetingMe, + ModifiedAt: questionnaire.ModifiedAt, + ResponseDueDateTime: &questionnaire.ResTimeLimit.Time, + Title: questionnaire.Title, + } + + response, err := respondentDetail2Response(ctx, responseDetail) + if err != nil { + ctx.Logger().Errorf("failed to convert respondent detail into response: %+v", err) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %+w", err)) + } + + tmp := struct { + Body []openapi.ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + QuestionnaireInfo *struct { + CreatedAt time.Time `json:"created_at"` + IsTargetingMe bool `json:"is_targeting_me"` + ModifiedAt time.Time `json:"modified_at"` + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + Title string `json:"title"` + } `json:"questionnaire_info,omitempty"` + Respondent openapi.TraqId `json:"respondent"` + ResponseId int `json:"response_id"` + SubmittedAt time.Time `json:"submitted_at"` + }{ + Body: response.Body, + IsDraft: response.IsDraft, + ModifiedAt: response.ModifiedAt, + QuestionnaireId: response.QuestionnaireId, + QuestionnaireInfo: &questionnaireInfo, + Respondent: userID, + ResponseId: response.ResponseId, + SubmittedAt: response.SubmittedAt, + } + res = append(res, tmp) + } + + return res, nil +} diff --git a/handler/response.go b/handler/response.go index 6212214f..40cbda84 100644 --- a/handler/response.go +++ b/handler/response.go @@ -1,14 +1,29 @@ package handler import ( + "fmt" + "net/http" + "github.com/labstack/echo/v4" + "github.com/traPtitech/anke-to/controller" "github.com/traPtitech/anke-to/openapi" ) // (GET /responses/myResponses) func (h Handler) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesParams) error { - res := []openapi.ResponsesWithQuestionnaireInfo{} + res := openapi.ResponsesWithQuestionnaireInfo{} + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + r := controller.NewResponse() + res, err = r.GetMyResponses(ctx, params, userID) + if err != nil { + ctx.Logger().Errorf("failed to get my responses: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get my responses: %+w", err)) + } return ctx.JSON(200, res) } diff --git a/model/respondents.go b/model/respondents.go index 56813245..a5f93e42 100644 --- a/model/respondents.go +++ b/model/respondents.go @@ -18,5 +18,6 @@ type IRespondent interface { GetRespondentDetail(ctx context.Context, responseID int) (RespondentDetail, error) GetRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) + GetMyResponsesID(ctx context.Context, sort string, userID string) ([]int, error) CheckRespondent(ctx context.Context, userID string, questionnaireID int) (bool, error) } diff --git a/model/respondents_impl.go b/model/respondents_impl.go index bcf55d27..92d29ecb 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -386,6 +386,25 @@ func (*Respondent) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs [ return respondents, nil } +// GetMyResponses 自分のすべての回答を取得 +func (*Response) GetMyResponsesID(ctx context.Context, userID int) ([]int, error) { + db, err := getTx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get transaction: %w", err) + } + + responsesID := []int{} + err = db. + Where("user_traqid = ?", userID). + Select("response_id"). + Find(&responsesID).Error + if err != nil { + return nil, fmt.Errorf("failed to get responsesID: %w", err) + } + + return responsesID, nil +} + // CheckRespondent 回答者かどうかの確認 func (*Respondent) CheckRespondent(ctx context.Context, userID string, questionnaireID int) (bool, error) { db, err := getTx(ctx) diff --git a/model/targets.go b/model/targets.go index e6557b12..a1bb5506 100644 --- a/model/targets.go +++ b/model/targets.go @@ -9,4 +9,5 @@ type ITarget interface { InsertTargets(ctx context.Context, questionnaireID int, targets []string) error DeleteTargets(ctx context.Context, questionnaireID int) error GetTargets(ctx context.Context, questionnaireIDs []int) ([]Targets, error) + IsTargetingMe(ctx context.Context, quesionnairID int, userID string) (bool, error) } diff --git a/model/targets_impl.go b/model/targets_impl.go index c3d73dd8..ab9d2b47 100644 --- a/model/targets_impl.go +++ b/model/targets_impl.go @@ -80,3 +80,8 @@ func (*Target) GetTargets(ctx context.Context, questionnaireIDs []int) ([]Target return targets, nil } + +func(*Target) IsTargetingME(ctx context.Context, questionnairID int, userID string) (bool, error) { + // todo: check if the questionnair is targeting me + return true, nil +} \ No newline at end of file From 1b700da24616b5ee9ddaf2c7d173d9b51782bab2 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:03:10 +0000 Subject: [PATCH 17/83] feat: implement GetResponse --- controller/response.go | 16 ++++++++++++++++ handler/response.go | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/controller/response.go b/controller/response.go index 8d2df275..bdef5721 100644 --- a/controller/response.go +++ b/controller/response.go @@ -101,3 +101,19 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses return res, nil } + +func (r Response) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) (openapi.Response, error) { + responseDetail, err := r.IRespondent.GetRespondentDetail(ctx.Request().Context(), responseID) + if err != nil { + ctx.Logger().Errorf("failed to get respondent detail: %+v", err) + return openapi.Response{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondent detail: %+w", err)) + } + + res, err := respondentDetail2Response(ctx, responseDetail) + if err != nil { + ctx.Logger().Errorf("failed to convert respondent detail into response: %+v", err) + return openapi.Response{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %+w", err)) + } + + return res, nil +} diff --git a/handler/response.go b/handler/response.go index 40cbda84..1ecbf785 100644 --- a/handler/response.go +++ b/handler/response.go @@ -36,6 +36,12 @@ func (h Handler) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDI func (h Handler) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { res := openapi.Response{} + r := controller.NewResponse() + res, err := r.GetResponse(ctx, responseID) + if err != nil { + ctx.Logger().Errorf("failed to get my response: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response: %+w", err)) + } return ctx.JSON(200, res) } From b09232718594ac6a2a4f0fa090dea6c4606b647d Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Sun, 25 Aug 2024 18:45:10 +0000 Subject: [PATCH 18/83] feat: implement DeleteResponse --- controller/response.go | 32 ++++++++++++++++++++++++++++++++ handler/response.go | 13 +++++++++++++ 2 files changed, 45 insertions(+) diff --git a/controller/response.go b/controller/response.go index bdef5721..7f8f05dc 100644 --- a/controller/response.go +++ b/controller/response.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "fmt" "net/http" "time" @@ -14,6 +15,7 @@ import ( type Response struct { model.IQuestionnaire model.IRespondent + model.IResponse model.ITarget } @@ -117,3 +119,33 @@ func (r Response) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInP return res, nil } + +func (r Response) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDInPath, userID string) error { + limit, err := r.IQuestionnaire.GetQuestionnaireLimitByResponseID(ctx.Request().Context(), responseID) + if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + ctx.Logger().Errorf("failed to find response by response ID: %+v", err) + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("failed to find response by response ID: %w", err)) + } + ctx.Logger().Errorf("failed to get questionnaire limit by response ID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire limit by response ID: %w", err)) + } + if limit.Valid && limit.Time.Before(time.Now()) { + ctx.Logger().Errorf("unable delete the expired response") + return echo.NewHTTPError(http.StatusMethodNotAllowed, fmt.Errorf("unable delete the expired response")) + } + + err = r.IRespondent.DeleteRespondent(ctx.Request().Context(), responseID) + if err != nil { + ctx.Logger().Errorf("failed to delete respondent: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete respondent: %w", err)) + } + + err = r.IResponse.DeleteResponse(ctx.Request().Context(), responseID) + if err != nil { + ctx.Logger().Errorf("failed to delete response: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete response: %w", err)) + } + + return nil +} diff --git a/handler/response.go b/handler/response.go index 1ecbf785..982a29f0 100644 --- a/handler/response.go +++ b/handler/response.go @@ -29,6 +29,19 @@ func (h Handler) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesP // (DELETE /responses/{responseID}) func (h Handler) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { + userID, err := getUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + r := controller.NewResponse() + err = r.DeleteResponse(ctx, responseID, userID) + if err != nil { + ctx.Logger().Errorf("failed to delete response: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete response: %w", err)) + } + return ctx.NoContent(200) } From 63892f462a2da8479f9596c34e5323c3f1270817 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:38:53 +0000 Subject: [PATCH 19/83] feat: implement GetMyResponses(complete) --- model/respondents_test.go | 95 +++++++++++++++++++++++++++++++++++++++ model/targets_impl.go | 21 +++++++-- model/targets_test.go | 66 +++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 3 deletions(-) diff --git a/model/respondents_test.go b/model/respondents_test.go index cafc7dd7..cb38c8e9 100644 --- a/model/respondents_test.go +++ b/model/respondents_test.go @@ -990,6 +990,101 @@ func TestGetRespondentsUserIDs(t *testing.T) { } } +func TestGetMyResponseIDs(t *testing.T) { + t.Parallel() + + assertion := assert.New(t) + ctx := context.Background() + + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + require.NoError(t, err) + + respondents := []Respondents{ + { + QuestionnaireID: questionnaireID, + UserTraqid: userOne, + SubmittedAt: null.NewTime(time.Now(), true), + }, + { + QuestionnaireID: questionnaireID, + UserTraqid: userTwo, + SubmittedAt: null.NewTime(time.Now(), true), + }, + { + QuestionnaireID: questionnaireID, + UserTraqid: userTwo, + SubmittedAt: null.NewTime(time.Now(), true), + }, + } + responseIDs := []int{} + for _, respondent := range respondents { + responseID, err := respondentImpl.InsertRespondent(ctx, respondent.UserTraqid, questionnaireID, respondent.SubmittedAt) + require.NoError(t, err) + responseIDs = append(responseIDs, responseID) + } + + type args struct { + userID string + } + type expect struct { + isErr bool + err error + responseIDs []int + } + type test struct { + description string + args + expect + } + + testCases := []test{ + { + description: "valid user with one resonse", + args: args{ + userID: userOne, + }, + expect: expect{ + responseIDs: []int{responseIDs[1]}, + }, + }, + { + description: "valid user with multiple responses", + args: args{ + userID: userTwo, + }, + expect: expect{ + responseIDs: []int{responseIDs[2], responseIDs[3]}, + }, + }, + { + description: "valid user with no response", + args: args{ + userID: userThree, + }, + expect: expect{ + responseIDs: []int{}, + }, + }, + } + + for _, testCase := range testCases { + MyResponseIDs, err := respondentImpl.GetMyResponseIDs(ctx, testCase.args.userID) + + if !testCase.expect.isErr { + assertion.NoError(err, testCase.description, "no error") + } else if testCase.expect.err != nil { + assertion.Equal(true, errors.Is(err, testCase.expect.err), testCase.description, "errorIs") + } else { + assertion.Error(err, testCase.description, "any error") + } + if err != nil { + continue + } + + assertion.Equal(testCase.expect.responseIDs, MyResponseIDs, testCase.description, "responseIDs") + } +} + func TestTestCheckRespondent(t *testing.T) { t.Parallel() diff --git a/model/targets_impl.go b/model/targets_impl.go index ab9d2b47..ccad5b1e 100644 --- a/model/targets_impl.go +++ b/model/targets_impl.go @@ -81,7 +81,22 @@ func (*Target) GetTargets(ctx context.Context, questionnaireIDs []int) ([]Target return targets, nil } -func(*Target) IsTargetingME(ctx context.Context, questionnairID int, userID string) (bool, error) { - // todo: check if the questionnair is targeting me - return true, nil +func(*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID string) (bool, error) { + db, err := getTx(ctx) + if err != nil { + return false, fmt.Errorf("failed to get transaction: %w", err) + } + + var count int64 + err = db. + Where("questionnaire_id = ? AND user_traqid = ?", questionnairID, userID). + Count(&count).Error + if err != nil { + return false, fmt.Errorf("failed to get targets which are targeting me: %w", err) + } + + if count > 0 { + return true, nil + } + return false, nil } \ No newline at end of file diff --git a/model/targets_test.go b/model/targets_test.go index a00d7487..24703d1e 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gorm.io/gorm" ) @@ -308,3 +309,68 @@ func TestGetTargets(t *testing.T) { }) } } + +func TestIsTargetingMe(t *testing.T) { + t.Parallel() + + assertion := assert.New(t) + ctx := context.Background() + + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private") + require.NoError(t, err) + + err = targetImpl.InsertTargets(ctx, questionnaireID, []string{userOne}) + require.NoError(t, err) + + type args struct { + userID string + } + type expect struct { + isErr bool + err error + isTargeted bool + } + type test struct { + description string + args + expect + } + + testCases := []test{ + { + description: "is targeted", + args: args{ + userID: userOne, + }, + expect: expect{ + isTargeted: true, + }, + }, + { + description: "not targeted", + args: args{ + userID: userTwo, + }, + expect: expect{ + isTargeted: false, + }, + }, + } + + for _, testCase := range testCases { + isTargeted, err := targetImpl.IsTargetingMe(ctx, questionnaireID, testCase.args.userID) + + if !testCase.expect.isErr { + assertion.NoError(err, testCase.description, "no error") + } else if testCase.expect.err != nil { + assertion.Equal(true, errors.Is(err, testCase.expect.err), testCase.description, "errorIs") + } else { + assertion.Error(err, testCase.description, "any error") + } + if err != nil { + continue + } + + assertion.Equal(testCase.expect.isTargeted, isTargeted, testCase.description, "isTargeted") + } +} From e21b78db65a5a77d852a697562d73b64abb34634 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:35:23 +0000 Subject: [PATCH 20/83] fix: fix some errors, optimize some implementations and add existence check for GetResponse --- controller/questionnaire.go | 32 ++++++++++++++++++++++++++++++++ controller/response.go | 20 ++++++++++++-------- handler/questionnaire.go | 28 ++++------------------------ handler/response.go | 6 +++--- model/respondents.go | 2 +- model/respondents_impl.go | 2 +- model/targets_test.go | 2 ++ 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index a0ac8ce9..607ae11d 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -371,3 +371,35 @@ https://anke-to.trap.jp/responses/new/%d`, questionnaireID, ) } + +func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID int, userID string) (openapi.Result, error){ + res := openapi.Result{} + + params := openapi.GetQuestionnaireResponsesParams{} + responses, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) + return openapi.Result{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %w", err)) + } + + for _, response := range responses { + tmp := struct { + Body []openapi.ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + ResponseId int `json:"response_id"` + SubmittedAt time.Time `json:"submitted_at"` + }{ + Body: response.Body, + IsDraft: response.IsDraft, + ModifiedAt: response.ModifiedAt, + QuestionnaireId: response.QuestionnaireId, + ResponseId: response.ResponseId, + SubmittedAt: response.SubmittedAt, + } + res = append(res, tmp) + } + + return res, nil +} diff --git a/controller/response.go b/controller/response.go index 7f8f05dc..a7bd6927 100644 --- a/controller/response.go +++ b/controller/response.go @@ -28,29 +28,29 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses sort := string(*params.Sort) responsesID := []int{} - responsesID, err := r.IRespondent.GetMyResponsesID(ctx.Request().Context(), sort, userID) + responsesID, err := r.IRespondent.GetMyResponseIDs(ctx.Request().Context(), sort, userID) if err != nil { ctx.Logger().Errorf("failed to get my responses ID: %+v", err) - return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %+w", err)) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %w", err)) } for _, responseID := range responsesID { responseDetail, err := r.IRespondent.GetRespondentDetail(ctx.Request().Context(), responseID) if err != nil { ctx.Logger().Errorf("failed to get respondent detail: %+v", err) - return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondent detail: %+w", err)) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondent detail: %w", err)) } questionnaire, _, _, _, _, _, err := r.IQuestionnaire.GetQuestionnaireInfo(ctx.Request().Context(), responseDetail.QuestionnaireID) if err != nil { ctx.Logger().Errorf("failed to get questionnaire info: %+v", err) - return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire info: %+w", err)) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire info: %w", err)) } isTargetingMe, err := r.ITarget.IsTargetingMe(ctx.Request().Context(), responseDetail.QuestionnaireID, userID) if err != nil { ctx.Logger().Errorf("failed to get target info: %+v", err) - return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get target info: %+w", err)) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get target info: %w", err)) } questionnaireInfo := struct { @@ -70,7 +70,7 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses response, err := respondentDetail2Response(ctx, responseDetail) if err != nil { ctx.Logger().Errorf("failed to convert respondent detail into response: %+v", err) - return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %+w", err)) + return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %w", err)) } tmp := struct { @@ -107,14 +107,18 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses func (r Response) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) (openapi.Response, error) { responseDetail, err := r.IRespondent.GetRespondentDetail(ctx.Request().Context(), responseID) if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + ctx.Logger().Errorf("failed to find response by response ID: %+v", err) + return openapi.Response{}, echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("failed to find response by response ID: %w", err)) + } ctx.Logger().Errorf("failed to get respondent detail: %+v", err) - return openapi.Response{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondent detail: %+w", err)) + return openapi.Response{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondent detail: %w", err)) } res, err := respondentDetail2Response(ctx, responseDetail) if err != nil { ctx.Logger().Errorf("failed to convert respondent detail into response: %+v", err) - return openapi.Response{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %+w", err)) + return openapi.Response{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %w", err)) } return res, nil diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 202f125c..647c67bb 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -180,31 +180,11 @@ func (h Handler) GetQuestionnaireResult(ctx echo.Context, questionnaireID openap return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) } - params := openapi.GetQuestionnaireResponsesParams{} q := controller.NewQuestionnaire() - responses, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) - if err != nil { - ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %+w", err)) - } - - for _, response := range responses { - tmp := struct { - Body []openapi.ResponseBody `json:"body"` - IsDraft bool `json:"is_draft"` - ModifiedAt time.Time `json:"modified_at"` - QuestionnaireId int `json:"questionnaire_id"` - ResponseId int `json:"response_id"` - SubmittedAt time.Time `json:"submitted_at"` - }{ - Body: response.Body, - IsDraft: response.IsDraft, - ModifiedAt: response.ModifiedAt, - QuestionnaireId: response.QuestionnaireId, - ResponseId: response.ResponseId, - SubmittedAt: response.SubmittedAt, - } - res = append(res, tmp) + res, err = q.GetQuestionnaireResult(ctx, questionnaireID, userID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire result: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire result: %w", err)) } return ctx.JSON(200, res) diff --git a/handler/response.go b/handler/response.go index 982a29f0..1f47bcd2 100644 --- a/handler/response.go +++ b/handler/response.go @@ -22,7 +22,7 @@ func (h Handler) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesP res, err = r.GetMyResponses(ctx, params, userID) if err != nil { ctx.Logger().Errorf("failed to get my responses: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get my responses: %+w", err)) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get my responses: %w", err)) } return ctx.JSON(200, res) } @@ -52,8 +52,8 @@ func (h Handler) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPa r := controller.NewResponse() res, err := r.GetResponse(ctx, responseID) if err != nil { - ctx.Logger().Errorf("failed to get my response: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response: %+w", err)) + ctx.Logger().Errorf("failed to get response: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response: %w", err)) } return ctx.JSON(200, res) } diff --git a/model/respondents.go b/model/respondents.go index a5f93e42..e5cff356 100644 --- a/model/respondents.go +++ b/model/respondents.go @@ -18,6 +18,6 @@ type IRespondent interface { GetRespondentDetail(ctx context.Context, responseID int) (RespondentDetail, error) GetRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) - GetMyResponsesID(ctx context.Context, sort string, userID string) ([]int, error) + GetMyResponseIDs(ctx context.Context, sort string, userID string) ([]int, error) CheckRespondent(ctx context.Context, userID string, questionnaireID int) (bool, error) } diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 92d29ecb..3c7a6cb0 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -387,7 +387,7 @@ func (*Respondent) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs [ } // GetMyResponses 自分のすべての回答を取得 -func (*Response) GetMyResponsesID(ctx context.Context, userID int) ([]int, error) { +func (*Respondent) GetMyResponseIDs(ctx context.Context, userID string) ([]int, error) { db, err := getTx(ctx) if err != nil { return nil, fmt.Errorf("failed to get transaction: %w", err) diff --git a/model/targets_test.go b/model/targets_test.go index 24703d1e..fd73aae2 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -4,9 +4,11 @@ import ( "context" "errors" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" "gorm.io/gorm" ) From bd06106aa7bc5aee6d9b6586858cfbc787d9fe42 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Fri, 30 Aug 2024 06:35:55 +0000 Subject: [PATCH 21/83] style: add missing space --- controller/questionnaire.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 607ae11d..b2d74604 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -372,7 +372,7 @@ https://anke-to.trap.jp/responses/new/%d`, ) } -func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID int, userID string) (openapi.Result, error){ +func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID int, userID string) (openapi.Result, error) { res := openapi.Result{} params := openapi.GetQuestionnaireResponsesParams{} From 54e993dfc997fdb08d2f6a486f7024a3f80160c7 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Fri, 30 Aug 2024 06:44:00 +0000 Subject: [PATCH 22/83] style: format code --- model/targets_impl.go | 6 +++--- model/targets_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/model/targets_impl.go b/model/targets_impl.go index ccad5b1e..41350884 100644 --- a/model/targets_impl.go +++ b/model/targets_impl.go @@ -13,7 +13,7 @@ func NewTarget() *Target { return new(Target) } -//Targets targetsテーブルの構造体 +// Targets targetsテーブルの構造体 type Targets struct { QuestionnaireID int `gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` UserTraqid string `gorm:"type:varchar(32);size:32;not null;primaryKey"` @@ -81,7 +81,7 @@ func (*Target) GetTargets(ctx context.Context, questionnaireIDs []int) ([]Target return targets, nil } -func(*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID string) (bool, error) { +func (*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID string) (bool, error) { db, err := getTx(ctx) if err != nil { return false, fmt.Errorf("failed to get transaction: %w", err) @@ -99,4 +99,4 @@ func(*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID stri return true, nil } return false, nil -} \ No newline at end of file +} diff --git a/model/targets_test.go b/model/targets_test.go index fd73aae2..71404545 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -328,9 +328,9 @@ func TestIsTargetingMe(t *testing.T) { userID string } type expect struct { - isErr bool - err error - isTargeted bool + isErr bool + err error + isTargeted bool } type test struct { description string From 18780111a1c68116f5ba24b19b65df6283a4f09a Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Fri, 30 Aug 2024 07:00:29 +0000 Subject: [PATCH 23/83] style: remove unused import --- handler/questionnaire.go | 1 - 1 file changed, 1 deletion(-) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 647c67bb..c8db5526 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -3,7 +3,6 @@ package handler import ( "fmt" "net/http" - "time" "github.com/labstack/echo/v4" "github.com/traPtitech/anke-to/controller" From 7cba992174582fd7d9fb98c5d94ee15e15bfd8d0 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:43:48 +0000 Subject: [PATCH 24/83] fix: specify model of db request in functions respondents/GetMyResponseIDs and targets/IsTargetingMe --- model/respondents_impl.go | 1 + model/targets_impl.go | 1 + 2 files changed, 2 insertions(+) diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 3c7a6cb0..2cbf4aaf 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -395,6 +395,7 @@ func (*Respondent) GetMyResponseIDs(ctx context.Context, userID string) ([]int, responsesID := []int{} err = db. + Model(&Respondents{}). Where("user_traqid = ?", userID). Select("response_id"). Find(&responsesID).Error diff --git a/model/targets_impl.go b/model/targets_impl.go index 41350884..6e046217 100644 --- a/model/targets_impl.go +++ b/model/targets_impl.go @@ -89,6 +89,7 @@ func (*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID str var count int64 err = db. + Model(&Targets{}). Where("questionnaire_id = ? AND user_traqid = ?", questionnairID, userID). Count(&count).Error if err != nil { From 17018e4c12059ed1fde79d7e8b41c56a63f0d69e Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Tue, 3 Sep 2024 23:35:23 +0900 Subject: [PATCH 25/83] refactor swagger and use type --- controller/questionnaire.go | 9 +-- controller/response.go | 25 +------ docs/swagger/swagger.yaml | 15 ++-- go.mod | 25 ++++--- go.sum | 25 +++++++ openapi/server.go | 24 +++--- openapi/spec.go | 141 ++++++++++++++++++------------------ openapi/types.go | 101 ++++++++++++++------------ tools.go | 1 + 9 files changed, 189 insertions(+), 177 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index f1d4c2bd..3c583503 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -427,14 +427,7 @@ func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID } for _, response := range responses { - tmp := struct { - Body []openapi.ResponseBody `json:"body"` - IsDraft bool `json:"is_draft"` - ModifiedAt time.Time `json:"modified_at"` - QuestionnaireId int `json:"questionnaire_id"` - ResponseId int `json:"response_id"` - SubmittedAt time.Time `json:"submitted_at"` - }{ + tmp := openapi.ResultItem{ Body: response.Body, IsDraft: response.IsDraft, ModifiedAt: response.ModifiedAt, diff --git a/controller/response.go b/controller/response.go index a7bd6927..4d21be47 100644 --- a/controller/response.go +++ b/controller/response.go @@ -53,13 +53,7 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get target info: %w", err)) } - questionnaireInfo := struct { - CreatedAt time.Time `json:"created_at"` - IsTargetingMe bool `json:"is_targeting_me"` - ModifiedAt time.Time `json:"modified_at"` - ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` - Title string `json:"title"` - }{ + questionnaireInfo := openapi.QuestionnaireInfo{ CreatedAt: questionnaire.CreatedAt, IsTargetingMe: isTargetingMe, ModifiedAt: questionnaire.ModifiedAt, @@ -73,22 +67,7 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses return openapi.ResponsesWithQuestionnaireInfo{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert respondent detail into response: %w", err)) } - tmp := struct { - Body []openapi.ResponseBody `json:"body"` - IsDraft bool `json:"is_draft"` - ModifiedAt time.Time `json:"modified_at"` - QuestionnaireId int `json:"questionnaire_id"` - QuestionnaireInfo *struct { - CreatedAt time.Time `json:"created_at"` - IsTargetingMe bool `json:"is_targeting_me"` - ModifiedAt time.Time `json:"modified_at"` - ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` - Title string `json:"title"` - } `json:"questionnaire_info,omitempty"` - Respondent openapi.TraqId `json:"respondent"` - ResponseId int `json:"response_id"` - SubmittedAt time.Time `json:"submitted_at"` - }{ + tmp := openapi.ResponseWithQuestionnaireInfoItem{ Body: response.Body, IsDraft: response.IsDraft, ModifiedAt: response.ModifiedAt, diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 443cccb1..fe99b014 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -883,6 +883,14 @@ components: type: array items: $ref: "#/components/schemas/ResponseWithQuestionnaireInfoItem" + QuestionnaireInfo: + type: object + allOf: + - $ref: "#/components/schemas/QuestionnaireTitle" + - $ref: "#/components/schemas/QuestionnaireResponseDueDateTime" + - $ref: "#/components/schemas/QuestionnaireCreatedAt" + - $ref: "#/components/schemas/QuestionnaireModifiedAt" + - $ref: "#/components/schemas/QuestionnaireIsTargetingMe" ResponseWithQuestionnaireInfoItem: type: object allOf: @@ -890,12 +898,7 @@ components: - type: object properties: questionnaire_info: - allOf: - - $ref: "#/components/schemas/QuestionnaireTitle" - - $ref: "#/components/schemas/QuestionnaireResponseDueDateTime" - - $ref: "#/components/schemas/QuestionnaireCreatedAt" - - $ref: "#/components/schemas/QuestionnaireModifiedAt" - - $ref: "#/components/schemas/QuestionnaireIsTargetingMe" + $ref: "#/components/schemas/QuestionnaireInfo" ResponseBody: oneOf: - $ref: "#/components/schemas/ResponseBodyText" diff --git a/go.mod b/go.mod index ee1468c6..1ff36d4d 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( github.com/labstack/echo/v4 v4.12.0 github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.10.0 - golang.org/x/sync v0.3.0 - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.12.0 // indirect + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.21.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -72,20 +72,21 @@ require ( require ( github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 // indirect github.com/traPtitech/go-traq v0.0.0-20240420012203-0152d96098b0 // indirect ) require ( - github.com/getkin/kin-openapi v0.118.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/swag v0.22.4 // indirect - github.com/gorilla/mux v1.8.0 // indirect + github.com/getkin/kin-openapi v0.124.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/swag v0.22.8 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oapi-codegen/echo-middleware v1.0.1 - github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 26e92dc0..2c369dc4 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,8 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM= github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= +github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -109,11 +111,15 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -211,6 +217,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -316,6 +324,8 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k= github.com/oapi-codegen/echo-middleware v1.0.1/go.mod h1:DBQKRn+D/vfXOFbaX5GRwFttoJY64JH6yu+pdt7wU3o= +github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 h1:rICjNsHbPP1LttefanBPnwsSwl09SqhCO7Ee623qR84= +github.com/oapi-codegen/oapi-codegen/v2 v2.3.0/go.mod h1:4k+cJeSq5ntkwlcpQSxLxICCxQzCL772o30PxdibRt4= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -329,6 +339,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -439,6 +451,8 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -479,6 +493,8 @@ golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -530,6 +546,8 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -556,6 +574,7 @@ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -625,6 +644,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -646,6 +667,8 @@ golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -704,6 +727,8 @@ golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/openapi/server.go b/openapi/server.go index 5bc86917..177c413d 100644 --- a/openapi/server.go +++ b/openapi/server.go @@ -1,6 +1,6 @@ // Package openapi provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. package openapi import ( @@ -127,7 +127,7 @@ func (w *ServerInterfaceWrapper) DeleteQuestionnaire(ctx echo.Context) error { // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -145,7 +145,7 @@ func (w *ServerInterfaceWrapper) GetQuestionnaire(ctx echo.Context) error { // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -163,7 +163,7 @@ func (w *ServerInterfaceWrapper) EditQuestionnaire(ctx echo.Context) error { // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -181,7 +181,7 @@ func (w *ServerInterfaceWrapper) GetQuestionnaireMyRemindStatus(ctx echo.Context // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -199,7 +199,7 @@ func (w *ServerInterfaceWrapper) EditQuestionnaireMyRemindStatus(ctx echo.Contex // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -217,7 +217,7 @@ func (w *ServerInterfaceWrapper) GetQuestionnaireResponses(ctx echo.Context) err // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -251,7 +251,7 @@ func (w *ServerInterfaceWrapper) PostQuestionnaireResponse(ctx echo.Context) err // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -269,7 +269,7 @@ func (w *ServerInterfaceWrapper) GetQuestionnaireResult(ctx echo.Context) error // ------------- Path parameter "questionnaireID" ------------- var questionnaireID QuestionnaireIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "questionnaireID", runtime.ParamLocationPath, ctx.Param("questionnaireID"), &questionnaireID) + err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) } @@ -307,7 +307,7 @@ func (w *ServerInterfaceWrapper) DeleteResponse(ctx echo.Context) error { // ------------- Path parameter "responseID" ------------- var responseID ResponseIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "responseID", runtime.ParamLocationPath, ctx.Param("responseID"), &responseID) + err = runtime.BindStyledParameterWithOptions("simple", "responseID", ctx.Param("responseID"), &responseID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter responseID: %s", err)) } @@ -325,7 +325,7 @@ func (w *ServerInterfaceWrapper) GetResponse(ctx echo.Context) error { // ------------- Path parameter "responseID" ------------- var responseID ResponseIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "responseID", runtime.ParamLocationPath, ctx.Param("responseID"), &responseID) + err = runtime.BindStyledParameterWithOptions("simple", "responseID", ctx.Param("responseID"), &responseID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter responseID: %s", err)) } @@ -343,7 +343,7 @@ func (w *ServerInterfaceWrapper) EditResponse(ctx echo.Context) error { // ------------- Path parameter "responseID" ------------- var responseID ResponseIDInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "responseID", runtime.ParamLocationPath, ctx.Param("responseID"), &responseID) + err = runtime.BindStyledParameterWithOptions("simple", "responseID", ctx.Param("responseID"), &responseID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter responseID: %s", err)) } diff --git a/openapi/spec.go b/openapi/spec.go index 482a476c..6b165df9 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -1,6 +1,6 @@ // Package openapi provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. package openapi import ( @@ -18,75 +18,76 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+w8a28TyZZ/xerdD6DtYBPmSnf9LUxGV5GGuzAJux9IZHXsStJz7W7T3Qa8KFKqDSEQ", - "AxGPMCFcQpgMBDI4zIVlMzz/y1basT/tX7iq6ldVP9zdjh3g6koIxd11qs77VJ1zqi9yeblUliUgaSqX", - "vciVBUUoAQ0o5JcsFatDhZIoiaqmCBooHK+eACPSqQpQqvh9Aah5RSxroixxWa515bmxMI9gfa+xvrc0", - "35q7jOAWgs8R/BnBJwheIn9fQrqOYIP8+2TcXDY+3ksd0pQKOMynQgH1RQtK142lLaRD8nwFwd8RfGJP", - "MiUUVXAYzemodgXV7iL9GaptodoCgtvkFZrTxyWO50SM7FlCA89JQglw2WBKOZ5T8zOgJGBatWoZD5yU", - "5SIQJG52lidAJ6o/ALUsS2o0XxrG6sO9F3cCKfeM2X33i7Gx3FdqXcTjkDkmKNNAE6XpOPJH+idUe4/0", - "v6FajWAUIswgRsSF7SdrKGKjeFMWpsMZsvvhLqrdJ+Ts7K02EFxMHWo+eG407u99fIZl/ei1sYSxOkoP", - "OxyCGV4qCB1R0sA0UAg6ZytAxYtLgqiAkeER6aSgzfgRQ/pjVHuF9N/worWFkWGXHWUM4KzpmY/jOQWc", - "rYgKKHBZLLAIdBTbNEIxMdU9HAF3hm7XHpUVLVxCO08QfNV+NJ86tPvhQXNhqXnvl+aKjmC9ufwSwXsI", - "XkqNc2plsiRqGijkBG2c41OeocbNDXPcgHcgVmZ9A2tebQvBRvOnK3ipcU4TtSIIGNBeuW4OGHBGNFdf", - "N5dfBqJVkgvilOgs5hnpYsWMS4WplyorGqNe/6qAKS7L/UvajRBp862a/oFi7hjmPea4CgQlPxPKay8z", - "Nh7svX4chgyZKkjbVU0RpWlzvf1LNq8AIYZc2WH/sFKlpDlrw5B9wJ8UuVImf4kaKKl+bpMBqdOnLUMG", - "F4RSuQi47FHeKzfngaAoQhX//jM4f8pyM3hioVj8jykue6YzqjbEcUEF3Cwfb/Ao0LBXV49XTSon2NWJ", - "k0uOAgFz8CgrchkomggIl2z/yfKu06w0N3zMmqV94Blq9gmbFiemZ72oTMqFamws7GmOY6AAmYlqrqAI", - "UxqexxG26ZQDgiSNswPJmxhNOBDy5I8gr+HZP5M6+KTnmj1DJzeYGcwMZI4OZI6OZTJZ8u/fMv+ezWQ4", - "npuSlRIezxUEDQxoYgnHbJ8N2JLLiQVmatde2FDmlzmG5GkMTRVgGOHTAcZoL/rREtWcu1ZwqDY+XW4/", - "WkBwEcFnCM4juEgs3it1z1YkBp286T+DXX0QB5yJbUieQZilppOaefSAnHtAAqWz4cfABS2x8mGg72XC", - "/mSAf66UJgnfkoGNitJ0EXw7I4v55JZyolLUxHLX4KN5oWi53YiZExs+llwAdqz6y2W/L44ITx7ds2fw", - "q1MQUZaIuiKGEi9LREm4kDsnFCsgaN/LcyVRCn89GwttU0xdYW1JOBDpojAJioEsj0FSB+AIgmnxuWPp", - "ReMJkzGc7pjjNb3Pq53EXXVFiOPoAoQMpGnznNed7jnusGvEHGcaAznJMrLOuAX4lmzYRi+nWSEESJUS", - "lokHcIKPGd3MiTqFLcpLxEfHAugxGo7HiImFOb7XSHgMNC4uNFiPUbJNLCYqZHgfULANKgEaBKSHqLiH", - "pO7OV2Nkhxd3w0FAhqnNYCJA+/AzXAHDggbG8B6+qwn+UwTnhckiOF5NBj+iDkmyVC3JFTUxYLEonxel", - "advt2KgknuhkZbIoqjOgkAzQTJ2qQ1KBZNNV1pOSId+aZ5ahAMvo14HLo7b0qSlKZ4fZI1PH85SL8N6v", - "vx41Vh+i2jqqvUK1JVR7316d331/H8GnJHt9Fem3/++neQT/F+nXEXy6t/K2tb5JkkBrCF7affsW6beM", - "xbX26jx5+BHBlVQsMKgjuIEB9Dqqvf//9zCSHTQVMfihCWKxSyseGU6mTYmO8x7lSgR2wkqmDWk9SeKE", - "Z3DsvHTBLrh1mua0ChS1Q9aHnWzCb2ojw+FuP+65POr0HakxnZySDztRzQnW6FzJGp5T6PHeNO8cUfp6", - "a+NK8+5LY/WhXWR7iuB1pC/6MhWJ01Wd8IlDvOPKA4ml33ryLfVPxtJ1m5x9U+EsFANn1/kH4Vym33Ys", - "LCFYNy7/2l5eRPAu9kdODW/f5Lg4xCDnB1ASpcJ3Eo7FwSQpZEQOuEOCS5tbxjb2xJiE2nNUe0jS/K9Q", - "7SqC9eaDq8a132nSSAXyCSnsvcH/w7qx/bH12zopcD/Fnlq/RsZv2FK+RxfKrfLgHETwNoINY24DwTpm", - "kA1cR/ClH4/2HNz9tG5xXF+0i53RPPUwIQZj6UJpEFs1+32uBPZVL963vjCYRFL2vagG7E7KwjTIlYQL", - "Aaa6tNDaXMCB3a7jNu++DKuEUJkNxpkmDy8EbLRSKglKNTI54GDvWzaSHVR09DGFKkP1dc9GrxOJcNAO", - "3oe57cVzhQrIYURymhikpqZtNh+stVeWsEUTY7QKcvqtNryB8L81pF9lQg/W4Jv4f2KCUqVYpCv+zKR4", - "6D3bTnvKwXhsos4p4Vw6Zw3KTVZjVI9GZwQFOJVhWpCBE0ZK1Nbyg9l3/vO0+XWcNtkI9NkPCkKxmLN3", - "5EGbI7tPCTsCdyNQt3zBzgKCnxwfkcKRLYX0W61PdzAgdjx/RXrd6gyD2ynSysSMSB1ipn3xk/HAPBtS", - "uwm47Zn5cJyAynMzgporVd2qr3cbvthc3SFuz1kX75K4DlMpVKk6yOPGmcrhtuvAc5NVa7fRRZRhqPRj", - "yntEHHDk8iVAfB5VcJ5Hnv2GpILVeoF9uTlzUkAPhfYsvI1HpOsds8uyLBlOtdaX9bAyHWyywsplsOeD", - "SHmYiwShyMSY6HNIY+9/lpoPHyD9Fp9qw0Vj+Q2C260niwRLvE1OHRq3ODLOHeZTHpskZ0zfeOr0Pc4d", - "TrWev8SxX9d980pVWQLjnGVqVprV4j97iOetwZhml7HWs4BGArrfo/vAeEBbOprUKDUeU4SzIwWOauSL", - "1UNAd+D1dT9KY8XQ5cGBj9q5RrYDOSImzobpzIndqkBDxWpT8ALEalGggWK2J9AgiVoTaMCEbQnMmm5L", - "Av34uKCCEUur/A5cUs+bzyNyZdbAEPfFLBZWyPOtRZUsu1xq1FTo6KVCND/mUn3q6GD9fBvuNK89bOmP", - "SVLdzIFcQbUa0reR/juCjfbl68bCPeJ3w6j1FtvpjqROB+pQPniVqUdtIHFVmlInHyo9ae1IgohtRH5M", - "et9O0RVeCVVqXPKT0oNGiiSoW/YbiEaPWif2i47TSOzbmNlhc+/OJoINVVY0vDfbbLTXH1JbI08EHfD8", - "ttv9Buw/6BDLM63R/ho2z10YwOsMnBMUSShhb3CGG7UXGNKGRr/lePrB8HfkCdkGD7l/Wo/dA+GQ5zcZ", - "QHNFTdx+G1Q/cmb7L1GbYTdz0pTMLBFPC6jFImpF1vwHU77fd16kNyf8bnMREyFNRQHirBS1LsT2Bezj", - "/7knD5ZpuPuzDQrNQca2THeI9A/uedXrE6mmBZ6+kdIXf+jYjunUnJ/79oXWsc7HGE0RTqW8t0e4fHkw", - "E6R+ZmE8rjd1j5JeQXlyJb4t8bTzvNP8boqmYuOVvKpvgvL2kn6lJBes8hVF1KqjeCZrG1suF8W8YHeg", - "TBXl8+bzijYjK+J/kzffygXge3haKXJZbkbTymo2nT57RFOE8pEfy2mhLKbPHUvLePBg2gYxbx7KZbso", - "IBQwH4t4uRT+JUrTKQWockXJA0zGeUXUgDuEaGCVHYTlIf8FxESEDGVqGuS5eVXJjkx5WdKEPHEy1v0n", - "TRFOcjxXYdaYFrWZyuSRvFxK4/eaqIH8TFqQ/gIGNBnjxeqm9SI1dHLEMTbv03NAUc3RR49kjmQGZEE9", - "hmeSy0ASyiKX5Y7h5/gYImgzhIlpf/VvGgTmVm8guGAllOBa86/ru+/eIP1W8+0c6fpZGczsvnuz++6X", - "3Z1F4kS8qS9Ue44PQ7UFpN8yr8g6LUVoTucIkgrRCWyZ3J+AdorFjGeuhYfEJXdImr6YF+ZM6eHMvcEY", - "APSl2xjDQ+4vx4QMvf8+O+HGQSK9wUzGVkIrwUVZZ/pH1TTReFfx/OVoouisZjRf/Gzs7CC4ZUvVLDx+", - "tPrB5nS/LpjnYTv5T2nBLM99Y+LfWftql41HvyHYMD48Nt7fRLC+d/clqXJeM+fCE/0haCIvLvqtcPRX", - "kX6b/MR0mDMe8884eup7jEhjrbVeb67o7eXbCNaPqZi4N5cx0nDNbqHQd3feIbjVfPFz68nN1vrm3s2P", - "CNaNG2vG6iNCPcmvT6u+DicStMqyGmCXzqVNP2XmRdKOVnZSVlkzs+43A1WzU3s9USTf/cZZNvJoSgXM", - "+hT5aH8U2Woe7KTK4cz0Krf93OwsWvMDfkkq7iPCo+Id9G+W94aK9EXPvfxZE5ci0EAcrIyr19orGx3V", - "c5hM5lXQZGEg+GMEYW4ztj7Y2Hv1IVS+fi84Moylemmd9Gs9TSJS2LCXr3clTD44xvuXsbSwq4jdZ0l9", - "Fr8QFuL2K/VvMt/EamN0i+mWvPsR6DpHIUHLzyRTnY2rzdXXHVXnu4LYR93pfTgLVZuoiJbQx1icOxAf", - "kwSP3oaNdKlq9saOaoJWCT+CdNn+2pXzOsGi9LW5Mm+38T+IT2PF3dp8YTTu99uzda91Xfi9Pqpdn71g", - "oMbtxx32y/l9DrXss/9kuBpzZ2dc3rR6AM02tyAvGcAV5jZIw7kbEGx4MfysW5Xqka5Hp1OCPskVMwvj", - "/8xeX1061cj6Wfz3sViGwqoEOQNDn1IxitEfM3TW7FE46JxucVbzZ1n8VDjXwvZe3DFubrdqHwgWzPWa", - "Nrxh3HhHfZ+xbp/xzNRC7BQO9UHFLzl6MPWrg80GseuGmZVXwBGZH2d8PxM++7KabwYHI+6VwLp7kYS2", - "Y7/5dj5COCrc54QT9uVW3Tpu2LOl1F6dJ5elMB5OV64/BJrjSSu7faKF28bSlnW1Gd5HczDKJ5JrcuY9", - "Ml33+aSVmGES0/kVHEMsTL+YgOXItr38t9aTp+YOvrn5zFR3+/YjtWM5kGwM1Qnei2iFbcWRKDlOR20I", - "nS/9MtdBnCCeuFZ3otr9Ti5wQ7ZfHU3U4RTSxeTvvUyu1A5PO9W8AnUm4IPNAWKJoS3OxRGvolx0v2vb", - "MXfv7ubYlH1Ivr7rDYjvQ70Js/Q+PBlhxA3EDrttfx8ciI/F4FPPfI2rA06kJh8WqPsidVjsz/yhy9gf", - "wM2Ors4VYtdaGlKe6HBSDXJJ/VfDA9yMJg2ZlBRiBkp3Z7TPQBmhib0/uzGaE5LFc3UnXlau18rT+/NT", - "ssNTPLcZlHI7SLdprd9J874Kn3lwrpJuyCPqybTinTF75OxGuAmsiypQztm6zKJTVuRCJW99tpbtVbPa", - "zOiuuIBLC0U5LxQZ2Gw6TR7OyKqW/WPmjxkTcsKh5WLgN/7J3J5v73OzE7N/DwAA///9vYY9KGQAAA==", + "H4sIAAAAAAAC/+xd3W8TyZb/V6zefQBtB5swV7rrtzAZXUUa7sIk7D6QyOrYlaTn2t2muw14UaRUG0Ig", + "BiI+woRwCWEyEMjgMBeWzfD5v2ylHftp/4Wrqv6q6g93t2MHuLpShOx2napT5/zOOVWnTjUXubxcKssS", + "kDSVy17kyoIilIAGFPJNlorVoUJJlERVUwQNFI5XT4AR6VQFKFX8ewGoeUUsa6IscVmudeW5sTCPYH2v", + "sb63NN+au4zgFoLPEfwZwScIXiKfLyFdR7BB/j4ZN5eNj/dShzSlAg7zqVBCfdGi0nVjaQvpkDxfQfB3", + "BJ/YnUwJRRUcRnM6ql1BtbtIf4ZqW6i2gOA2+QnN6eMSx3MiZvYsmQPPSUIJcNngmXI8p+ZnQEnAc9Wq", + "ZdxwUpaLQJC42VmeEJ2o/gDUsiyp0XJpGKsP917cCZy5p83uu1+MjeW+ztZlPM40xwRlGmiiNB1H/0j/", + "hGrvkf43VKsRjkKUGSSIuLT9FA012SjZlIXpcIHsfriLavfJdHb2VhsILqYONR88Nxr39z4+w7p+9NpY", + "wlwdpZsdDuEMDxXEjihpYBoohJ2zFaDiwSVBVMDI8Ih0UtBm/Iwh/TGqvUL6b3jQ2sLIsCuOMiZwxvT0", + "x/GcAs5WRAUUuCxWWAQ7im0aoZyYcA9nwO2h27FHZUUL19DOEwRftR/Npw7tfnjQXFhq3vuluaIjWG8u", + "v0TwHoKXUuOcWpksiZoGCjlBG+f4lKepcXPDbDfgbYjBrG9g5NW2EGw0f7qChxrnNFErgoAG7ZXrZoMB", + "p0Vz9XVz+WUgWyW5IE6JzmCeli5XTLtUGLxUWdEYeP2rAqa4LPcvaTdCpM1f1fQPlHDHsOyxxFUgKPmZ", + "UFl7hbHxYO/14zBmSFdBaFc1RZSmzfH2r9m8AoQYemWb/cNqldLmrE1D1gF/UuRKmXwSNVBS/dImDVKn", + "T1uGDC4IpXIRcNmjvFdvzgNBUYQq/v5ncP6U5WZwx0Kx+B9TXPZMZ1ZtiuOCCrhZPl7jUaBhr64er5qz", + "nGBHJ04uOQuEzOGjrMhloGgiIFKy/Scru0690tLwCWuW9oFnqN4n7Lk4MT3rZWVSLlRjc2F3cxwTBehM", + "VHMFRZjScD+Osk2nHBAkaZ4dSt7kaMKhkCd/BHkN9/6Z4ODTnmv2zDy5wcxgZiBzdCBzdCyTyZK/f8v8", + "ezaT4XhuSlZKuD1XEDQwoIklHLN9NmBrLicWmK5de2FDmV/nmJKnOTQhwAjChwHGaC/62RLVnDtWcKg2", + "Pl1uP1pAcBHBZwjOI7hILN6rdc9SJMY8edN/Brv6IAk4HduUPMMwO5tOMPPggOx7QALQ2fRj4IKWGHyY", + "6HuZiD8Z4Z8rpUkit2Rko6I0XQTfzshiPrmlnKgUNbHcNfloXihabjei58SGjzUXwB0Lf7ns98UR4cmD", + "PbsHP5yCJmWpqKvJUOplJ1ESLuTOCcUKCFr38lxJlMJ/no3Ftqmmrri2NBzIdFGYBMVAkceYUgfiiAnT", + "6nPb0oPGUyZjON0Jx2t6nxedxF11NRHH0QUoGUjT5j6vO+w57rBrxhxnGoM5yTKyzrwF+JZs2EIvp1kh", + "BEiVEtaJh3CCjxndzI46hS3KS8RnxyLoMRuOx4jJhdm+10x4DDQuLzRZj1myTSwmK6R5H1iwDSoBG4Sk", + "h6y4m6Tu9ldjZIUXd8FBSIapxWAiQnvzM1wBw4IGxvAavqsO/lME54XJIjheTUY/og5JslQtyRU1MWGx", + "KJ8XpWnb7disJO7oZGWyKKozoJCM0EydqkNSgWTTVdaTkibfmnuWoQDL6NeGywNbetcUhdlhdsvUcT/l", + "Mrz3669HjdWHqLaOaq9QbQnV3rdX53ff30fwKcleX0X67f/7aR7B/0X6dQSf7q28ba1vkiTQGoKXdt++", + "RfotY3GtvTpPHn5EcCUViwzqCG5gAr2Oau///z2MFAc9ixjy0ASx2KUVjwwnQ1Oi7bwHXInITljJtCGt", + "J0mc8AyOnZcu2Adunbo5rQJF7ZD1YTub8JvayHC424+7L4/afUciZkSakg/O6+/befcGQQk8LX3aNBst", + "zQ4u3qdrUc0JVutcyWqeU+j23qT5HHEh9dbGlebdl8bqQ/vI8imC15G+6Mv7JE7+deInzuSdwBg4WfpX", + "T/aq/slYum5PZ9+zcAaKwbMbSoN4LtO/djymQ7BuXP61vbyI4F3s3Z0T0X1Px+UhxnR+ACVRKnwn4ZVN", + "8JQU0iIH3CbBB8VbxjaOa3gKteeo9pAcmrxCtasI1psPrhrXfqenRs5zn5Bj0jf4X1g3tj+2flsn5QJP", + "cdzTr5H2G7aW79FlB9Zh6xxE8DaCDWNuA8E6FpBNXEfwpZ+P9hzc/bRuSVxftI+Oo2XqEUIMwdKOIEis", + "mv17rgT2dfq+b7wwnETO7HtRDVjrlYVpkCsJFwJMdWmhtbmAl0n2qXjz7suwcyUqT8SEpuTBmpCNVkol", + "QalGploc7n3DRoqDihQ+oVCHen1dAdPjRDIcFFJ9nNtePFeogBxmJKeJQTA1bbP5YK29soQtmhijdbyp", + "32rDGwj/rSH9KhN6MIJv4n+JCUqVYpGun2A6xU3v2XbaUwnGExO16wuX0jmrUW6yGuMsbnRGUIBzzk4r", + "MrDDSI3aKD+YVfw/9+5fx97dsxT93NsuoVjM2fuboMWRXfWFHYG7EKhbvmBnAcFPjo9I4ciWQvqt1qc7", + "mBA7nr8ivW7V2cHtFCkMY1qkDjHdvvjJeGDutKnVBNz29Hw4TkDluRlBzZWq7hm6dxm+2FzdIW7PGRev", + "krgOXSnUwX+Qx43TlSNt14HnJqvWaqOLKMPM0s8p71FxwAbWl07yeVTBeR65kx6SClYhC/blZs9JCT0z", + "tHvhbT4iXe+YfcjNTsM5+/blkKy8EZv6sTJD7P4gUh/mIEEsMjEmeh/S2PufpebDB0i/xafacNFYfoPg", + "duvJIuESL5NTh8YtiYxzh/mUxybJHtPXnspljHOHU63nL3Hs13Vfv1JVlsA4Z5malbS25M+mRHirMZ6z", + "K1jrWUBZBl09031gPKAlHT3VKBiPKcLZkQJHlUXGqsig6xn7uh6luWLm5eGBj1q5RhZXOSomzoapc4pd", + "+EFTxSr68BLEKvigiWIWe9AkiQo9aMKERR7MmG6BB/34uKCCEQtVfgcuqefN5xGZR6thiPtiBgs7FvWN", + "RR0AdznUqAno6KFCkB9zqD7Vx7B+vg13mtcetvTH5IjCzIFcQbUa0reR/juCjfbl68bCPeJ3w2brLV2g", + "67s6bahD5eAFU4+KauJCmoKTj5WeFMokYcQ2Ij8nvS9O6YqvhJAal/xT6UFZShLWLfsNZKNHhSj7Zccp", + "y/YtzOywuXdnE8GGKisaXpttNtrrD6mlkSeCDni+28WTA/YHOsTyTKG5vyKA5y4M4HEGzgmKJJSwNzjD", + "jdoDDGlDo99yPP1g+DvyhCyDh9yP1mN3Qzjk+U4a0FL5L1Gb8Z0zjWigFF9j7lqAjzgls46w4q8EMUFQ", + "WVEHN68mrs4OOl50eguUT+IhwqUcPHalqCUZo1LUOneWTKFfwGr8nyvrYJSHOzHb1NAcZKzOdGpI/+Du", + "Or2ejSrk4OlbOn3xak6Oy3RNztd9ezRrc+YTjKYIp1LeGzVcvjyYCYKfWSwQ1/bcDaHX7jwZD9/Cdtp5", + "3ql/N9FSsflKXulgkvL2kH5YkUtn+YoiatVR3JO1GC2Xi2JesKtyporyefN5RZuRFfG/yS/fygXge3ha", + "KXJZbkbTymo2nT57RFOE8pEfy2mhLKbPHUvLuPFg2iYxb2PKZTu1LxSwHIt4uBT+JkrTKQWockXJAzyN", + "84qoAbcJQWCVbYT1If8FxGSENGXCDHluXt+yY1ZeljQhT5yMdSdMU4STHM9VmDGmRW2mMnkkL5fS+HdN", + "1EB+Ji1IfwEDmoz5YrFp/ZAaOjniGJv36TmgqGbro0cyRzIDsqAewz3JZSAJZZHLcsfwc7yZELQZIsS0", + "/wxvGgRmSG8guGClheBa86/ru+/eIP1W8+0cqYRaGczsvnuz++6X3Z1F4kS8CSxUe463NLUFpN8yrw07", + "ZVZoTucIkwrBBLZM7k9AO8VyxjNX5UPiktskTV9WDHOmdHPmLmUMAvoicozmIXe6Y1KGvhMAu36mvmQw", + "k7FBaKWpKOtM/6iaJhrveqL/UJkAnUVG88XPxs4Oglu2Vs3jw49Wjdyc7seCuau1U/gUCmZ57huT/87o", + "q102Hv2GYMP48Nh4fxPB+t7dl+Ss8prZF+7oD0EdeXnRb4Wzv4r02+QrnofZ4zF/j6OnvseMNNZa6/Xm", + "it5evo1g/ZiKJ/fmMmYartmFEPruzjsEt5ovfm49udla39y7+RHBunFjzVh9RGZPsuTTqq/qiwStsqwG", + "2KVzkdU/M/NybUcrOymrrJlZd76BqtkJup4AyXfnc5aNPJpSAbM+IB/tD5CtgspOUA4Xphfc9nOzPmjN", + "T/glQdw3CQ/EO+BvlveGivRFz7sKZk1eikADcbgyrl5rr2x0hOcw6cwL0GRhIPgFDWFuMzYebO69eAjV", + "r98LjgxjrV5aJ1VXT5OoFDbs4etdKZMPjvH+YSwUdhWx+6ypz+IXwkLcfrX+TeabWMWI7pG4pe9+BLrO", + "UUjQ8jPJoLNxtbn6uiN0viuIfcRO78NZKGyiIlpCH2NJ7kB8TBI+ehs20qWqWeE6qglaJXwL0mURa1fO", + "6wTL0tfmyrw1w/8gPo1Vd2vzhdG432/P1j3quvB7fYRdn71gIOL24w775fw+Byz77D8ZqcZc2RmXN61K", + "PrNYLchLBkiFudPRcCr8gw0vhp91j2J6hPXodErQa8piZmH8rx7sq0unylE/i/8+FstQWEiQPTD0gYoB", + "Rn/M0BmzR+Ggc7rFGc2fZfHPwrnctffijnFzu1X7QLhgLsm04Q3jxjvqnZV1e49nphZip3Col0x+ydGD", + "Ob862GwQO26YWXkVHJH5cdr3M+GzL6v5ZnAw4nYIrLvXQWg79ptv5y2EA+E+J5ywL7fOv+OGPVtL7dV5", + "cuUJ8+HU1vpDoNmeFKTbO1q4bSxtWde94X00B6N8IrnsZt4G03WfT1qJGSbxPL+CbYjF6RcTsBzdtpf/", + "1nry1FzBNzefmXC37zBSK5YDycZQ9dy9iFbYVhyNku101ILQefsxc6nDCeKJz+pOVLtfyQUuyPaL0UQ1", + "NyGlO/4KyuSgdmTa6cwrEDMBL7EOUEsMtDjXP7xAuei+67dj7t5dzbEp+5B8fdcLEN/LixNm6X18MsqI", + "G4gdcdv+PjgQH4shp575GhcDTqQmrweo+yJ1WOzP/KHL2B8gzY6uzlVi1ygNOZ7osFMNckn9h+EBLkaT", + "hkxKCzEDpbsy2megjEBi7/duDHJCsnguduJl5XoNnt7vn5JtnuK5zaCU20G6TWv8Tsj7KnzmwblKuiCP", + "wJMpxTtj1sjZhXATGIsqUM7ZWGbZKStyoZK3XuXL1qpZZWZ0VVzA1YOinBeKDG02nSYPZ2RVy/4x88eM", + "STnhzOVi4P97QPr2/H8E3OzE7N8DAAD//wbUgE48ZQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index 4905ca5b..b0ca79bc 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -1,6 +1,6 @@ // Package openapi provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. package openapi import ( @@ -376,6 +376,19 @@ type QuestionnaireID struct { QuestionnaireId int `json:"questionnaire_id"` } +// QuestionnaireInfo defines model for QuestionnaireInfo. +type QuestionnaireInfo struct { + CreatedAt time.Time `json:"created_at"` + + // IsTargetingMe 自分がターゲットになっているかどうか + IsTargetingMe bool `json:"is_targeting_me"` + ModifiedAt time.Time `json:"modified_at"` + + // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 + ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` + Title string `json:"title"` +} + // QuestionnaireIsAllowingMultipleResponses defines model for QuestionnaireIsAllowingMultipleResponses. type QuestionnaireIsAllowingMultipleResponses struct { // IsAllowingMultipleResponses 一人が複数回回答できるかどうか @@ -570,26 +583,13 @@ type ResponseBodyTextLongQuestionType string // ResponseSortType response用のsortの種類 type ResponseSortType string -// Responses defines model for Responses. -type Responses = []Response - -// ResponsesWithQuestionnaireInfo defines model for ResponsesWithQuestionnaireInfo. -type ResponsesWithQuestionnaireInfo = []struct { - Body []ResponseBody `json:"body"` - IsDraft bool `json:"is_draft"` - ModifiedAt time.Time `json:"modified_at"` - QuestionnaireId int `json:"questionnaire_id"` - QuestionnaireInfo *struct { - CreatedAt time.Time `json:"created_at"` - - // IsTargetingMe 自分がターゲットになっているかどうか - IsTargetingMe bool `json:"is_targeting_me"` - ModifiedAt time.Time `json:"modified_at"` - - // ResponseDueDateTime 回答期限。この日時を過ぎたら回答できなくなる。nullの場合は回答期限なし。 - ResponseDueDateTime *time.Time `json:"response_due_date_time,omitempty"` - Title string `json:"title"` - } `json:"questionnaire_info,omitempty"` +// ResponseWithQuestionnaireInfoItem defines model for ResponseWithQuestionnaireInfoItem. +type ResponseWithQuestionnaireInfoItem struct { + Body []ResponseBody `json:"body"` + IsDraft bool `json:"is_draft"` + ModifiedAt time.Time `json:"modified_at"` + QuestionnaireId int `json:"questionnaire_id"` + QuestionnaireInfo *QuestionnaireInfo `json:"questionnaire_info,omitempty"` // Respondent traQ ID Respondent TraqId `json:"respondent"` @@ -597,8 +597,17 @@ type ResponsesWithQuestionnaireInfo = []struct { SubmittedAt time.Time `json:"submitted_at"` } +// Responses defines model for Responses. +type Responses = []Response + +// ResponsesWithQuestionnaireInfo defines model for ResponsesWithQuestionnaireInfo. +type ResponsesWithQuestionnaireInfo = []ResponseWithQuestionnaireInfoItem + // Result defines model for Result. -type Result = []struct { +type Result = []ResultItem + +// ResultItem defines model for ResultItem. +type ResultItem struct { Body []ResponseBody `json:"body"` IsDraft bool `json:"is_draft"` ModifiedAt time.Time `json:"modified_at"` @@ -718,7 +727,7 @@ func (t *NewQuestion) MergeQuestionSettingsText(v QuestionSettingsText) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -744,7 +753,7 @@ func (t *NewQuestion) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -770,7 +779,7 @@ func (t *NewQuestion) MergeQuestionSettingsNumber(v QuestionSettingsNumber) erro return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -796,7 +805,7 @@ func (t *NewQuestion) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingle return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -822,7 +831,7 @@ func (t *NewQuestion) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMult return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -848,7 +857,7 @@ func (t *NewQuestion) MergeQuestionSettingsScale(v QuestionSettingsScale) error return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -953,7 +962,7 @@ func (t *Question) MergeQuestionSettingsText(v QuestionSettingsText) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -979,7 +988,7 @@ func (t *Question) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) err return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1005,7 +1014,7 @@ func (t *Question) MergeQuestionSettingsNumber(v QuestionSettingsNumber) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1031,7 +1040,7 @@ func (t *Question) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingleCho return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1057,7 +1066,7 @@ func (t *Question) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMultipl return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1083,7 +1092,7 @@ func (t *Question) MergeQuestionSettingsScale(v QuestionSettingsScale) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1212,7 +1221,7 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsText(v QuestionSettingsTex return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1238,7 +1247,7 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsTextLong(v QuestionSetting return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1264,7 +1273,7 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsNumber(v QuestionSettingsN return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1290,7 +1299,7 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsSingleChoice(v QuestionSet return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1316,7 +1325,7 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsMultipleChoice(v QuestionS return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1342,7 +1351,7 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsScale(v QuestionSettingsSc return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1378,7 +1387,7 @@ func (t *ResponseBody) MergeResponseBodyText(v ResponseBodyText) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1404,7 +1413,7 @@ func (t *ResponseBody) MergeResponseBodyTextLong(v ResponseBodyTextLong) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1430,7 +1439,7 @@ func (t *ResponseBody) MergeResponseBodyNumber(v ResponseBodyNumber) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1456,7 +1465,7 @@ func (t *ResponseBody) MergeResponseBodySingleChoice(v ResponseBodySingleChoice) return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1482,7 +1491,7 @@ func (t *ResponseBody) MergeResponseBodyMultipleChoice(v ResponseBodyMultipleCho return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1508,7 +1517,7 @@ func (t *ResponseBody) MergeResponseBodyScale(v ResponseBodyScale) error { return err } - merged, err := runtime.JsonMerge(t.union, b) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } diff --git a/tools.go b/tools.go index a73f8352..005b37f2 100644 --- a/tools.go +++ b/tools.go @@ -6,4 +6,5 @@ package main import ( _ "github.com/golang/mock/mockgen" _ "github.com/google/wire/cmd/wire" + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" ) From 6418720f2398391108ace3d9aae1670b35e4b193 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Thu, 5 Sep 2024 19:18:20 +0900 Subject: [PATCH 26/83] impl editresponse handler --- handler/response.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/handler/response.go b/handler/response.go index 1f47bcd2..a7dfbfa5 100644 --- a/handler/response.go +++ b/handler/response.go @@ -60,5 +60,22 @@ func (h Handler) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPa // (PATCH /responses/{responseID}) func (h Handler) EditResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { + req := openapi.EditResponseJSONRequestBody{} + if err := ctx.Bind(&req); err != nil { + ctx.Logger().Errorf("failed to bind Responses: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind Responses: %w", err)) + } + + validate, err := getValidator(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get validator: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get validator: %w", err)) + } + + err = validate.Struct(req) + if err != nil { + ctx.Logger().Errorf("failed to validate request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to validate request body: %w", err)) + } return ctx.NoContent(200) } From 2a0e45e7427075a798032ef679eba55e541f349a Mon Sep 17 00:00:00 2001 From: kavos113 Date: Thu, 12 Sep 2024 22:55:15 +0900 Subject: [PATCH 27/83] impl: adapter between openapi.ResponseBody and struct to insert db --- controller/adapter.go | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/controller/adapter.go b/controller/adapter.go index 110703d9..4284a385 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -276,3 +276,69 @@ func respondentDetail2Response(ctx echo.Context, respondentDetail model.Responde return res, nil } + +func responseBody2ResponseMetas(body []openapi.ResponseBody, questions []model.Questions) ([]*model.ResponseMeta, error) { + res := []*model.ResponseMeta{} + + for i, b := range body { + switch questions[i].Type { + case "Text": + bText, err := b.AsResponseBodyText() + if err != nil { + return nil, err + } + res = append(res, &model.ResponseMeta{ + QuestionID: questions[i].ID, + Data: bText.Answer, + }) + case "TextLong": + bTextLong, err := b.AsResponseBodyTextLong() + if err != nil { + return nil, err + } + res = append(res, &model.ResponseMeta{ + QuestionID: questions[i].ID, + Data: bTextLong.Answer, + }) + case "Number": + bNumber, err := b.AsResponseBodyNumber() + if err != nil { + return nil, err + } + res = append(res, &model.ResponseMeta{ + QuestionID: questions[i].ID, + Data: strconv.FormatFloat(float64(bNumber.Answer), 'f', -1, 32), + }) + case "SingleChoice": + bSingleChoice, err := b.AsResponseBodySingleChoice() + if err != nil { + return nil, err + } + res = append(res, &model.ResponseMeta{ + QuestionID: questions[i].ID, + Data: strconv.FormatInt(int64(bSingleChoice.Answer), 10), + }) + case "MultipleChoice": + bMultipleChoice, err := b.AsResponseBodyMultipleChoice() + if err != nil { + return nil, err + } + for _, a := range bMultipleChoice.Answer { + res = append(res, &model.ResponseMeta{ + QuestionID: questions[i].ID, + Data: strconv.FormatInt(int64(a), 10), + }) + } + case "LinearScale": + bScale, err := b.AsResponseBodyScale() + if err != nil { + return nil, err + } + res = append(res, &model.ResponseMeta{ + QuestionID: questions[i].ID, + Data: strconv.FormatInt(int64(bScale.Answer), 10), + }) + } + } + return res, nil +} From 4ca29b59f5b12b97b87d8f7f377046fee724d994 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Thu, 12 Sep 2024 22:57:13 +0900 Subject: [PATCH 28/83] impl: edit response controller --- controller/response.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/controller/response.go b/controller/response.go index 4d21be47..bb3fd0e3 100644 --- a/controller/response.go +++ b/controller/response.go @@ -17,6 +17,7 @@ type Response struct { model.IRespondent model.IResponse model.ITarget + model.IQuestion } func NewResponse() *Response { @@ -132,3 +133,44 @@ func (r Response) DeleteResponse(ctx echo.Context, responseID openapi.ResponseID return nil } + +func (r Response) EditResponse(ctx echo.Context, responseID openapi.ResponseIDInPath, req openapi.EditResponseJSONRequestBody) error { + limit, err := r.IQuestionnaire.GetQuestionnaireLimitByResponseID(ctx.Request().Context(), responseID) + if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + ctx.Logger().Infof("failed to find response by response ID: %+v", err) + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("failed to find response by response ID: %w", err)) + } + ctx.Logger().Errorf("failed to get questionnaire limit by response ID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire limit by response ID: %w", err)) + } + + if limit.Valid && limit.Time.Before(time.Now()) { + ctx.Logger().Info("unable to edit the expired response") + return echo.NewHTTPError(http.StatusMethodNotAllowed, fmt.Errorf("unable edit the expired response")) + } + + err = r.IResponse.DeleteResponse(ctx.Request().Context(), responseID) + if err != nil { + ctx.Logger().Errorf("failed to delete response: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete response: %w", err)) + } + + questions, err := r.IQuestion.GetQuestions(ctx.Request().Context(), req.QuestionnaireId) + + responseMetas, err := responseBody2ResponseMetas(req.Body, questions) + if err != nil { + ctx.Logger().Errorf("failed to convert response body into response metas: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to convert response body into response metas: %w", err)) + } + + if len(responseMetas) > 0 { + err = r.IResponse.InsertResponses(ctx.Request().Context(), responseID, responseMetas) + if err != nil { + ctx.Logger().Errorf("failed to insert responses: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to insert responses: %w", err)) + } + } + + return nil +} \ No newline at end of file From 91b0006dc703bfcfa9c898c6c8dbce602a936b58 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Fri, 27 Sep 2024 20:07:00 +0900 Subject: [PATCH 29/83] 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 30/83] 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 31/83] 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 32/83] 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 33/83] 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 { From 4758c6eeaf3401621adddf7b83347baa5ebc7d9c Mon Sep 17 00:00:00 2001 From: kavos Date: Thu, 10 Oct 2024 17:53:23 +0900 Subject: [PATCH 34/83] fix: PostQuestionnaire response 200 -> 201 --- handler/questionnaire.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index c6fe5dc1..0a424142 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -61,7 +61,7 @@ func (h Handler) PostQuestionnaire(ctx echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to post questionnaire: %w", err)) } - return ctx.JSON(200, res) + return ctx.JSON(201, res) } // (GET /questionnaires/{questionnaireID}) From 511b40c10d464bc8de099d3b0b171c768c73506e Mon Sep 17 00:00:00 2001 From: kavos Date: Fri, 11 Oct 2024 16:34:05 +0900 Subject: [PATCH 35/83] fix: return 404 at GetQuestionnaire --- handler/questionnaire.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 0a424142..065a488b 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -1,11 +1,13 @@ package handler import ( + "errors" "fmt" "net/http" "github.com/labstack/echo/v4" "github.com/traPtitech/anke-to/controller" + "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" ) @@ -70,6 +72,9 @@ func (h Handler) GetQuestionnaire(ctx echo.Context, questionnaireID openapi.Ques q := controller.NewQuestionnaire() res, err := q.GetQuestionnaire(ctx, questionnaireID) if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("questionnaire not found: %w", err)) + } ctx.Logger().Errorf("failed to get questionnaire: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire: %w", err)) } From 9eccbbffc3209af3bec87f8ff403b40fa2a58bd2 Mon Sep 17 00:00:00 2001 From: kavos Date: Fri, 11 Oct 2024 16:39:38 +0900 Subject: [PATCH 36/83] fix: return 404 at GetQuestionnaireResponses --- controller/questionnaire.go | 3 +++ handler/questionnaire.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 3c583503..44d53764 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -315,6 +315,9 @@ func (q Questionnaire) GetQuestionnaireResponses(c echo.Context, questionnaireID res := []openapi.Response{} respondentDetails, err := q.GetRespondentDetails(c.Request().Context(), questionnaireID, string(*params.Sort), *params.OnlyMyResponse, userID) if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + return res, echo.NewHTTPError(http.StatusNotFound, "respondent not found") + } c.Logger().Errorf("failed to get respondent details: %+v", err) return res, echo.NewHTTPError(http.StatusInternalServerError, "failed to get respondent details") } diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 065a488b..26dc7678 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -145,6 +145,10 @@ func (h Handler) GetQuestionnaireResponses(ctx echo.Context, questionnaireID ope } q := controller.NewQuestionnaire() res, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) + if err != nil { + ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) + return err + } return ctx.JSON(200, res) } From bc1b71ee6e954209c1cd4690986806873f7f7047 Mon Sep 17 00:00:00 2001 From: kavos Date: Fri, 11 Oct 2024 16:43:36 +0900 Subject: [PATCH 37/83] fix: return 404 at GetQuestionnaireResult --- controller/questionnaire.go | 3 +++ handler/questionnaire.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 44d53764..ae85c5eb 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -425,6 +425,9 @@ func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID params := openapi.GetQuestionnaireResponsesParams{} responses, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) if err != nil { + if errors.Is(echo.ErrNotFound, err) { + return openapi.Result{}, err + } ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) return openapi.Result{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %w", err)) } diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 26dc7678..7e7c0741 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -201,6 +201,9 @@ func (h Handler) GetQuestionnaireResult(ctx echo.Context, questionnaireID openap q := controller.NewQuestionnaire() res, err = q.GetQuestionnaireResult(ctx, questionnaireID, userID) if err != nil { + if errors.Is(err, echo.ErrNotFound) { + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("questionnaire result not found: %w", err)) + } ctx.Logger().Errorf("failed to get questionnaire result: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire result: %w", err)) } From 674e25a56f578fd41d121b7ff7f031b9126b3891 Mon Sep 17 00:00:00 2001 From: kavos Date: Mon, 14 Oct 2024 13:25:14 +0900 Subject: [PATCH 38/83] fix: return 404, 500 at GetResponse --- handler/response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/response.go b/handler/response.go index a7dfbfa5..bd4fe754 100644 --- a/handler/response.go +++ b/handler/response.go @@ -53,7 +53,7 @@ func (h Handler) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPa res, err := r.GetResponse(ctx, responseID) if err != nil { ctx.Logger().Errorf("failed to get response: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response: %w", err)) + return err } return ctx.JSON(200, res) } From dc54c0d216fa6a37c01c5ac5e4be3a47e693ea02 Mon Sep 17 00:00:00 2001 From: kavos Date: Mon, 14 Oct 2024 13:26:19 +0900 Subject: [PATCH 39/83] fix: return 404, 405, 500 at EditResponse --- handler/response.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/handler/response.go b/handler/response.go index bd4fe754..1553df4b 100644 --- a/handler/response.go +++ b/handler/response.go @@ -77,5 +77,13 @@ func (h Handler) EditResponse(ctx echo.Context, responseID openapi.ResponseIDInP ctx.Logger().Errorf("failed to validate request body: %+v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to validate request body: %w", err)) } + + r := controller.NewResponse() + err = r.EditResponse(ctx, responseID, req) + if err != nil { + ctx.Logger().Errorf("failed to edit response: %+v", err) + return err + } + return ctx.NoContent(200) } From 06cee267e163436bcbd0e31f0a9c4ef44f2d2e00 Mon Sep 17 00:00:00 2001 From: kavos Date: Mon, 14 Oct 2024 13:27:27 +0900 Subject: [PATCH 40/83] fix: return 404, 405, 500 at DeleteResponse --- handler/response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/response.go b/handler/response.go index 1553df4b..7db80a56 100644 --- a/handler/response.go +++ b/handler/response.go @@ -39,7 +39,7 @@ func (h Handler) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDI err = r.DeleteResponse(ctx, responseID, userID) if err != nil { ctx.Logger().Errorf("failed to delete response: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete response: %w", err)) + return err } return ctx.NoContent(200) From 93705a0af062e1a718f13dac30cc3943898f48da Mon Sep 17 00:00:00 2001 From: kavos113 Date: Thu, 24 Oct 2024 18:25:09 +0900 Subject: [PATCH 41/83] impl: add IsPublished in questionnaires --- model/questionnaires.go | 4 ++-- model/questionnaires_impl.go | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/model/questionnaires.go b/model/questionnaires.go index 8338abbf..c9f04c87 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, isPublished bool) (int, error) + UpdateQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, questionnaireID int, isPublished bool) error DeleteQuestionnaire(ctx context.Context, questionnaireID int) error GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, onlyTargetingMe bool, onlyAdministratedByMe 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 da751531..25e20cb9 100755 --- a/model/questionnaires_impl.go +++ b/model/questionnaires_impl.go @@ -35,6 +35,7 @@ type Questionnaires struct { TargetGroups []TargetGroups `json:"-" gorm:"foreignKey:QuestionnaireID"` Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` + IsPublished bool `json:"is_published" gorm:"type:boolean;default:false"` } // BeforeCreate Update時に自動でmodified_atを現在時刻に @@ -81,7 +82,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, isPublished bool) (int, error) { db, err := getTx(ctx) if err != nil { return 0, fmt.Errorf("failed to get tx: %w", err) @@ -93,6 +94,7 @@ func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, des Title: title, Description: description, ResSharedTo: resSharedTo, + IsPublished: isPublished, } } else { questionnaire = Questionnaires{ @@ -100,6 +102,7 @@ func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, des Description: description, ResTimeLimit: resTimeLimit, ResSharedTo: resSharedTo, + IsPublished: isPublished, } } @@ -112,7 +115,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, questionnaireID int, isPublished bool) error { db, err := getTx(ctx) if err != nil { return fmt.Errorf("failed to get tx: %w", err) @@ -125,6 +128,7 @@ func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, des Description: description, ResTimeLimit: resTimeLimit, ResSharedTo: resSharedTo, + IsPublished: isPublished, } } else { questionnaire = map[string]interface{}{ @@ -132,6 +136,7 @@ func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, des "description": description, "res_time_limit": gorm.Expr("NULL"), "res_shared_to": resSharedTo, + "is_published": isPublished, } } @@ -186,7 +191,7 @@ func (*Questionnaire) GetQuestionnaires(ctx context.Context, userID string, sort query := db. Table("questionnaires"). - Where("deleted_at IS NULL"). + Where("deleted_at IS NULL AND is_published IS TRUE"). Joins("LEFT OUTER JOIN targets ON questionnaires.id = targets.questionnaire_id") query, err = setQuestionnairesOrder(query, sort) From 6f70f2db17ff281e69c14fd5c9d6d592bb2c4cd8 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Thu, 24 Oct 2024 18:27:24 +0900 Subject: [PATCH 42/83] impl: isPublished in questionnaires controller --- controller/questionnaire.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 1bfb71f1..622a658f 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -111,7 +111,7 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o questionnaireID := 0 err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy)) + questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy), params.IsPublished) if err != nil { c.Logger().Errorf("failed to insert questionnaire: %+v", err) return err @@ -302,7 +302,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa responseDueDateTime.Time = *params.ResponseDueDateTime } err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID) + err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID, params.IsPublished) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to update questionnaire: %+v", err) return err From 58d8bd24e934a1eaa3ee5d3084f900713408dfaa Mon Sep 17 00:00:00 2001 From: kavos113 Date: Fri, 25 Oct 2024 19:43:13 +0900 Subject: [PATCH 43/83] fix: update test --- model/questionnaires_test.go | 85 ++++++++++++++++++++++++++++++++++-- model/respondents_test.go | 21 ++++----- model/responses_test.go | 4 +- model/scale_labels_test.go | 10 ++--- model/targets_test.go | 2 +- model/validations_test.go | 8 ++-- 6 files changed, 104 insertions(+), 26 deletions(-) diff --git a/model/questionnaires_test.go b/model/questionnaires_test.go index c3b716cf..a3a32a87 100644 --- a/model/questionnaires_test.go +++ b/model/questionnaires_test.go @@ -67,6 +67,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(questionnairesNow, true), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -80,6 +81,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "respondents", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -93,6 +95,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "administrators", + IsPublished: true, CreatedAt: questionnairesNow.Add(time.Second), ModifiedAt: questionnairesNow.Add(2 * time.Second), }, @@ -106,6 +109,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -126,6 +130,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -145,6 +150,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow.Add(2 * time.Second), ModifiedAt: questionnairesNow.Add(3 * time.Second), }, @@ -158,6 +164,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, DeletedAt: gorm.DeletedAt{ @@ -177,6 +184,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow.Add(time.Duration(len(datas)) * time.Second), ModifiedAt: questionnairesNow, }, @@ -191,6 +199,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow.Add(2 * time.Second), ModifiedAt: questionnairesNow.Add(3 * time.Second), }, @@ -203,6 +212,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -222,6 +232,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -240,6 +251,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(questionnairesNow, true), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -259,6 +271,7 @@ func setupQuestionnairesTest(t *testing.T) { Description: "第1回集会らん☆ぷろ参加者募集", ResTimeLimit: null.NewTime(time.Time{}, false), ResSharedTo: "public", + IsPublished: true, CreatedAt: questionnairesNow, ModifiedAt: questionnairesNow, }, @@ -355,6 +368,7 @@ func insertQuestionnaireTest(t *testing.T) { description string resTimeLimit null.Time resSharedTo string + isPublished bool } type expect struct { isErr bool @@ -375,6 +389,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, { @@ -384,6 +399,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, }, { @@ -393,6 +409,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "respondents", + isPublished: true, }, }, { @@ -402,6 +419,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "administrators", + isPublished: true, }, }, { @@ -411,6 +429,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, { @@ -420,6 +439,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, expect: expect{ isErr: true, @@ -432,6 +452,7 @@ func insertQuestionnaireTest(t *testing.T) { description: strings.Repeat("a", 2000), resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, { @@ -441,17 +462,28 @@ func insertQuestionnaireTest(t *testing.T) { description: strings.Repeat("a", 200000), resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, expect: expect{ isErr: true, }, }, + { + description: "not published", + args: args{ + title: "第1回集会らん☆ぷろ募集アンケート", + description: "第1回集会らん☆ぷろ参加者募集", + resTimeLimit: null.NewTime(time.Time{}, false), + resSharedTo: "public", + isPublished: false, + }, + }, } 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.isPublished) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -475,6 +507,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.isPublished, questionnaire.IsPublished, testCase.description, "is_published") 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") @@ -491,6 +524,7 @@ func updateQuestionnaireTest(t *testing.T) { description string resTimeLimit null.Time resSharedTo string + isPublished bool } type expect struct { isErr bool @@ -512,12 +546,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "respondents", + isPublished: true, }, }, { @@ -527,12 +563,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第2回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, { @@ -542,12 +580,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第2回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, { @@ -557,12 +597,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "respondents", + isPublished: true, }, }, { @@ -572,12 +614,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第2回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, }, { @@ -587,12 +631,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第2回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, }, { @@ -602,12 +648,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, }, { @@ -617,12 +665,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now().Add(time.Minute), true), resSharedTo: "public", + isPublished: true, }, }, { @@ -632,12 +682,31 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, + }, + after: args{ + title: "第1回集会らん☆ぷろ募集アンケート", + description: "第1回集会らん☆ぷろ参加者募集", + resTimeLimit: null.NewTime(time.Time{}, false), + resSharedTo: "public", + isPublished: true, + }, + }, + { + description: "update is_published(false->true)", + before: args{ + title: "第1回集会らん☆ぷろ募集アンケート", + description: "第1回集会らん☆ぷろ参加者募集", + resTimeLimit: null.NewTime(time.Time{}, false), + resSharedTo: "public", + isPublished: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, } @@ -651,6 +720,7 @@ func updateQuestionnaireTest(t *testing.T) { Description: before.description, ResTimeLimit: before.resTimeLimit, ResSharedTo: before.resSharedTo, + IsPublished: before.isPublished, } err := db. Session(&gorm.Session{NewDB: true}). @@ -662,7 +732,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, questionnaireID, after.isPublished) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -686,6 +756,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.isPublished, questionnaire.IsPublished, testCase.description, "is_published") 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") @@ -714,19 +785,21 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, { title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isPublished: true, }, } 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, invalidQuestionnaireID, arg.isPublished) if !errors.Is(err, ErrNoRecordUpdated) { if err == nil { t.Errorf("Succeeded with invalid questionnaireID") @@ -747,6 +820,7 @@ func deleteQuestionnaireTest(t *testing.T) { description string resTimeLimit null.Time resSharedTo string + isPublished bool } type expect struct { isErr bool @@ -764,6 +838,7 @@ func deleteQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isPublished: true, }, }, } @@ -776,6 +851,7 @@ func deleteQuestionnaireTest(t *testing.T) { Description: testCase.args.description, ResTimeLimit: testCase.args.resTimeLimit, ResSharedTo: testCase.args.resSharedTo, + IsPublished: testCase.isPublished, } err := db. Session(&gorm.Session{NewDB: true}). @@ -1070,7 +1146,7 @@ func getQuestionnairesTest(t *testing.T) { err = db. Session(&gorm.Session{NewDB: true}). Model(&Questionnaires{}). - Where("deleted_at IS NULL"). + Where("deleted_at IS NULL AND is_published IS TRUE"). Count(&questionnaireNum).Error if err != nil { t.Errorf("failed to count questionnaire(%s): %v", testCase.description, err) @@ -1341,6 +1417,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.IsPublished, actualQuestionnaire.IsPublished, testCase.description, "questionnaire(IsPublished)") 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_test.go b/model/respondents_test.go index cb38c8e9..a9f353bc 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}) @@ -290,6 +290,7 @@ func TestGetRespondent(t *testing.T) { Description: "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", ResTimeLimit: null.NewTime(time.Now(), false), ResSharedTo: "private", + IsPublished: true, } err := db. Session(&gorm.Session{NewDB: true}). @@ -390,9 +391,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 +523,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 +620,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 +909,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 +997,7 @@ func TestGetMyResponseIDs(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) respondents := []Respondents{ @@ -1091,7 +1092,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/targets_test.go b/model/targets_test.go index 71404545..79adf7c6 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -318,7 +318,7 @@ func TestIsTargetingMe(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 = targetImpl.InsertTargets(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}) From 420479ab4ac8b296a512ad52d86d78f0d5cf0781 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 29 Oct 2024 20:38:24 +0900 Subject: [PATCH 44/83] impl:db migration --- docs/db_schema.md | 1 + model/current.go | 4 +++- model/v3.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 model/v3.go diff --git a/docs/db_schema.md b/docs/db_schema.md index 45f30206..fee0ed48 100644 --- a/docs/db_schema.md +++ b/docs/db_schema.md @@ -50,6 +50,7 @@ | res_shared_to | char(30) | NO | | administrators | | アンケートの結果を, 運営は見られる ("administrators"), 回答済みの人は見られる ("respondents") 誰でも見られる ("public") | | created_at | timestamp | NO | | CURRENT_TIMESTAMP | | アンケートが作成された日時 | | modified_at | timestamp | NO | | CURRENT_TIMESTAMP | | アンケートが更新された日時 | +| is_published | bookean | NO | | false | | アンケートが公開かどうか ### respondents diff --git a/model/current.go b/model/current.go index 9b3fea3e..973a7fda 100644 --- a/model/current.go +++ b/model/current.go @@ -6,7 +6,9 @@ import ( // Migrations is all db migrations func Migrations() []*gormigrate.Migration { - return []*gormigrate.Migration{} + return []*gormigrate.Migration{ + v3(), + } } func AllTables() []interface{} { diff --git a/model/v3.go b/model/v3.go new file mode 100644 index 00000000..1935453a --- /dev/null +++ b/model/v3.go @@ -0,0 +1,42 @@ +package model + +import ( + "time" + + "github.com/go-gormigrate/gormigrate/v2" + "gopkg.in/guregu/null.v4" + "gorm.io/gorm" +) + +func v3() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "v3", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&Targets{}); err != nil { + return err + } + return nil + }, + } +} + +type v3Questionnaires 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"` + TargetGroups []TargetGroups `json:"-" gorm:"foreignKey:QuestionnaireID"` + Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` + Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` + IsPublished bool `json:"is_published" gorm:"type:boolean;default:false"` +} + +func (*v3Questionnaires) TableName() string { + return "questionnaires" +} \ No newline at end of file From b9a1da80ad26d66744447e52dff1b67df8c0dfbe Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:36:32 +0000 Subject: [PATCH 45/83] feat: implement middleware --- handler/middleware.go | 222 ++++++++++++++++++++++++++++++++++++++++++ main.go | 14 +++ middleware.go | 80 +++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 middleware.go diff --git a/handler/middleware.go b/handler/middleware.go index d40ec3af..ad6d94a3 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -3,11 +3,28 @@ package handler import ( "errors" "fmt" + "net/http" + "strconv" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/traPtitech/anke-to/model" ) +// Middleware Middlewareの構造体 +type Middleware struct { + model.IAdministrator + model.IRespondent + model.IQuestion + model.IQuestionnaire +} + +// NewMiddleware Middlewareのコンストラクタ +func NewMiddleware() *Middleware { + return &Middleware{} +} + const ( validatorKey = "validator" userIDKey = "userID" @@ -16,6 +33,13 @@ const ( questionIDKey = "questionID" ) +/* + 消せないアンケートの発生を防ぐための管理者 + +暫定的にハードコーディングで対応 +*/ +var adminUserIDs = []string{"ryoha", "xxarupakaxx", "kaitoyama", "cp20", "itzmeowww"} + // SetUserIDMiddleware X-Showcase-UserからユーザーIDを取得しセットする func SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { @@ -30,6 +54,204 @@ func SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { } } +// TraPMemberAuthenticate traP部員かの認証 +func TraPMemberAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + userID, err := getUserID(c) + if err != nil { + c.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + // トークンを持たないユーザはアクセスできない + if userID == "-" { + c.Logger().Info("not logged in") + return echo.NewHTTPError(http.StatusUnauthorized, "You are not logged in") + } + + return next(c) + } +} + +// TrapRateLimitMiddlewareFunc traP IDベースのリクエスト制限 +func TrapRateLimitMiddlewareFunc() echo.MiddlewareFunc { + config := middleware.RateLimiterConfig{ + Store: middleware.NewRateLimiterMemoryStore(5), + IdentifierExtractor: func(c echo.Context) (string, error) { + userID, err := getUserID(c) + if err != nil { + c.Logger().Errorf("failed to get userID: %+v", err) + return "", echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + return userID, nil + }, + } + + return middleware.RateLimiterWithConfig(config) +} + +// QuestionnaireAdministratorAuthenticate アンケートの管理者かどうかの認証 +func QuestionnaireAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + m := NewMiddleware() + + userID, err := getUserID(c) + if err != nil { + c.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + strQuestionnaireID := c.Param("questionnaireID") + questionnaireID, err := strconv.Atoi(strQuestionnaireID) + if err != nil { + c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) + } + + for _, adminID := range adminUserIDs { + if userID == adminID { + c.Set(questionnaireIDKey, questionnaireID) + + return next(c) + } + } + isAdmin, err := m.CheckQuestionnaireAdmin(c.Request().Context(), userID, questionnaireID) + if err != nil { + c.Logger().Errorf("failed to check questionnaire admin: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are administrator: %w", err)) + } + if !isAdmin { + return c.String(http.StatusForbidden, "You are not a administrator of this questionnaire.") + } + + c.Set(questionnaireIDKey, questionnaireID) + + return next(c) + } +} + +// ResponseReadAuthenticate 回答閲覧権限があるかの認証 +func ResponseReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + m := NewMiddleware() + + userID, err := getUserID(c) + if err != nil { + c.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + strResponseID := c.Param("responseID") + responseID, err := strconv.Atoi(strResponseID) + if err != nil { + c.Logger().Info("failed to convert responseID to int: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid responseID:%s(error: %w)", strResponseID, err)) + } + + // 回答者ならOK + respondent, err := m.GetRespondent(c.Request().Context(), responseID) + if errors.Is(err, model.ErrRecordNotFound) { + c.Logger().Infof("response not found: %+v", err) + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("response not found:%d", responseID)) + } + if err != nil { + c.Logger().Errorf("failed to check if you are a respondent: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are a respondent: %w", err)) + } + if respondent == nil { + c.Logger().Error("respondent is nil") + return echo.NewHTTPError(http.StatusInternalServerError) + } + if respondent.UserTraqid == userID { + return next(c) + } + + // 回答者以外は一時保存の回答は閲覧できない + if !respondent.SubmittedAt.Valid { + c.Logger().Info("not submitted") + + // Note: 一時保存の回答の存在もわかってはいけないので、Respondentが見つからない時と全く同じエラーを返す + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("response not found:%d", responseID)) + } + + // アンケートごとの回答閲覧権限チェック + responseReadPrivilegeInfo, err := m.GetResponseReadPrivilegeInfoByResponseID(c.Request().Context(), userID, responseID) + if errors.Is(err, model.ErrRecordNotFound) { + c.Logger().Infof("response not found: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid responseID: %d", responseID)) + } else if err != nil { + c.Logger().Errorf("failed to get response read privilege info: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response read privilege info: %w", err)) + } + + haveReadPrivilege, err := checkResponseReadPrivilege(responseReadPrivilegeInfo) + if err != nil { + c.Logger().Errorf("failed to check response read privilege: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check response read privilege: %w", err)) + } + if !haveReadPrivilege { + return c.String(http.StatusForbidden, "You do not have permission to view this response.") + } + + return next(c) + } +} + +// RespondentAuthenticate 回答者かどうかの認証 +func RespondentAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + m := NewMiddleware() + + userID, err := getUserID(c) + if err != nil { + c.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + strResponseID := c.Param("responseID") + responseID, err := strconv.Atoi(strResponseID) + if err != nil { + c.Logger().Infof("failed to convert responseID to int: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid responseID:%s(error: %w)", strResponseID, err)) + } + + respondent, err := m.GetRespondent(c.Request().Context(), responseID) + if errors.Is(err, model.ErrRecordNotFound) { + c.Logger().Infof("response not found: %+v", err) + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("response not found:%d", responseID)) + } + if err != nil { + c.Logger().Errorf("failed to check if you are a respondent: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are a respondent: %w", err)) + } + if respondent == nil { + c.Logger().Error("respondent is nil") + return echo.NewHTTPError(http.StatusInternalServerError) + } + if respondent.UserTraqid != userID { + return c.String(http.StatusForbidden, "You are not a respondent of this response.") + } + + c.Set(responseIDKey, responseID) + + return next(c) + } +} + +func checkResponseReadPrivilege(responseReadPrivilegeInfo *model.ResponseReadPrivilegeInfo) (bool, error) { + switch responseReadPrivilegeInfo.ResSharedTo { + case "administrators": + return responseReadPrivilegeInfo.IsAdministrator, nil + case "respondents": + return responseReadPrivilegeInfo.IsAdministrator || responseReadPrivilegeInfo.IsRespondent, nil + case "public": + return true, nil + } + + return false, errors.New("invalid resSharedTo") +} + // getValidator Validatorを設定する func getValidator(c echo.Context) (*validator.Validate, error) { rowValidate := c.Get(validatorKey) diff --git a/main.go b/main.go index fe08cd25..4f6c5754 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,21 @@ func main() { e.Use(handler.SetUserIDMiddleware) e.Use(middleware.Logger()) e.Use(middleware.Recover()) + + mws := NewMiddlewareSwitcher() + mws.AddGroupConfig("", handler.TraPMemberAuthenticate) + + mws.AddRouteConfig("/questionnaires", http.MethodGet, handler.TrapRateLimitMiddlewareFunc()) + mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodPatch, handler.QuestionnaireAdministratorAuthenticate) + mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodDelete, handler.QuestionnaireAdministratorAuthenticate) + + mws.AddRouteConfig("/responses/:responseID", http.MethodGet, handler.ResponseReadAuthenticate) + mws.AddRouteConfig("/responses/:responseID", http.MethodPatch, handler.RespondentAuthenticate) + mws.AddRouteConfig("/responses/:responseID", http.MethodDelete, handler.RespondentAuthenticate) + openapi.RegisterHandlers(e, handler.Handler{}) + + e.Use(mws.ApplyMiddlewares) e.Logger.Fatal(e.Start(port)) // SetRouting(port) diff --git a/middleware.go b/middleware.go new file mode 100644 index 00000000..5a7ba3cf --- /dev/null +++ b/middleware.go @@ -0,0 +1,80 @@ +package main + +import ( + "strings" + + "github.com/labstack/echo/v4" +) + +type RouteConfig struct { + path string + method string + middlewares []echo.MiddlewareFunc + isGroup bool +} + +type MiddlewareSwitcher struct { + routeConfigs []RouteConfig +} + +func NewMiddlewareSwitcher() *MiddlewareSwitcher { + return &MiddlewareSwitcher{ + routeConfigs: []RouteConfig{}, + } +} + +func (m *MiddlewareSwitcher) AddGroupConfig(grouppath string, middlewares ...echo.MiddlewareFunc) { + m.routeConfigs = append(m.routeConfigs, RouteConfig{ + path: grouppath, + middlewares: middlewares, + isGroup: true, + }) +} + +func (m *MiddlewareSwitcher) AddRouteConfig(path string, method string, middlewares ...echo.MiddlewareFunc) { + m.routeConfigs = append(m.routeConfigs, RouteConfig{ + path: path, + method: method, + middlewares: middlewares, + isGroup: false, + }) +} + +func (m *MiddlewareSwitcher) IsWithinGroup(groupPath string, path string) bool { + if !strings.HasPrefix(path, groupPath) { + return false + } + return len(groupPath) == len(path) || path[len(groupPath)] == '/' +} + +func (m *MiddlewareSwitcher) FindMiddlewares(path string, method string) []echo.MiddlewareFunc { + var matchedMiddlewares []echo.MiddlewareFunc + + for _, config := range m.routeConfigs { + if config.isGroup && m.IsWithinGroup(config.path, path) { + matchedMiddlewares = append(matchedMiddlewares, config.middlewares...) + } + if !config.isGroup && config.path == path && config.method == method { + matchedMiddlewares = append(matchedMiddlewares, config.middlewares...) + } + } + + return matchedMiddlewares +} + +func (m *MiddlewareSwitcher) ApplyMiddlewares(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + path := c.Path() + method := c.Request().Method + + middlewares := m.FindMiddlewares(path, method) + + for _, mw := range middlewares { + if err := mw(next)(c); err != nil { + return err + } + } + + return next(c) + } +} From 770fe382f12fc01d411d1e270aa629c6d8076b07 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 31 Oct 2024 03:23:12 +0000 Subject: [PATCH 46/83] feat: add middleware for checking questionnaire read permission --- handler/middleware.go | 56 +++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 2 files changed, 57 insertions(+) diff --git a/handler/middleware.go b/handler/middleware.go index ad6d94a3..02f8ee51 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -91,6 +91,62 @@ func TrapRateLimitMiddlewareFunc() echo.MiddlewareFunc { return middleware.RateLimiterWithConfig(config) } +// QuestionnaireReadAuthenticate アンケートの閲覧権限があるかの認証 +func QuestionnaireReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + m := NewMiddleware() + + userID, err := getUserID(c) + if err != nil { + c.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + + strQuestionnaireID := c.Param("questionnaireID") + questionnaireID, err := strconv.Atoi(strQuestionnaireID) + if err != nil { + c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) + } + + // 管理者ならOK + for _, adminID := range adminUserIDs { + if userID == adminID { + c.Set(questionnaireIDKey, questionnaireID) + + return next(c) + } + } + isAdmin, err := m.CheckQuestionnaireAdmin(c.Request().Context(), userID, questionnaireID) + if err != nil { + c.Logger().Errorf("failed to check questionnaire admin: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are administrator: %w", err)) + } + if isAdmin { + c.Set(questionnaireIDKey, questionnaireID) + return next(c) + } + + // 公開されたらOK + questionnaire, _, _, _, _, _, err := m.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) + if errors.Is(err, model.ErrRecordNotFound) { + c.Logger().Infof("questionnaire not found: %+v", err) + return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("questionnaire not found:%d", questionnaireID)) + } + if err != nil { + c.Logger().Errorf("failed to get questionnaire read privilege info: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire read privilege info: %w", err)) + } + if !questionnaire.IsPublished { + return c.String(http.StatusForbidden, "The questionnaire is not published.") + } + + c.Set(questionnaireIDKey, questionnaireID) + + return next(c) + } +} + // QuestionnaireAdministratorAuthenticate アンケートの管理者かどうかの認証 func QuestionnaireAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { diff --git a/main.go b/main.go index 4f6c5754..adec2505 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ func main() { mws.AddGroupConfig("", handler.TraPMemberAuthenticate) mws.AddRouteConfig("/questionnaires", http.MethodGet, handler.TrapRateLimitMiddlewareFunc()) + mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodGet, handler.QuestionnaireReadAuthenticate) mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodPatch, handler.QuestionnaireAdministratorAuthenticate) mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodDelete, handler.QuestionnaireAdministratorAuthenticate) From 94b936ab26d827de2d87bee3bada99232f64dedd Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Sat, 2 Nov 2024 18:38:31 +0000 Subject: [PATCH 47/83] fix: fix parameter mismatch in test files caused by new parameter --- model/questionnaires_test.go | 327 +++++++++++++++++++++++++++-------- model/respondents_test.go | 189 ++++++++++++++++++-- model/responses_test.go | 4 +- model/scale_labels_test.go | 10 +- model/targets_test.go | 2 +- model/validations_test.go | 8 +- 6 files changed, 443 insertions(+), 97 deletions(-) diff --git a/model/questionnaires_test.go b/model/questionnaires_test.go index b88dd65b..fc60adba 100644 --- a/model/questionnaires_test.go +++ b/model/questionnaires_test.go @@ -966,11 +966,12 @@ func getQuestionnairesTest(t *testing.T) { } type args struct { - userID string - sort string - search string - pageNum int - nontargeted bool + userID string + sort string + search string + pageNum int + onlyTargetingMe bool + onlyAdministratedByMe bool } type expect struct { isErr bool @@ -988,81 +989,89 @@ func getQuestionnairesTest(t *testing.T) { { description: "userID:valid, sort:no, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:created_at, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "created_at", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "created_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:-created_at, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "-created_at", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "-created_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:title, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "title", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "title", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:-title, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "-title", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "-title", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:modified_at, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "modified_at", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "modified_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:-modified_at, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "-modified_at", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "-modified_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:no, search:GetQuestionnaireTest$, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "", - search: "GetQuestionnaireTest$", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "", + search: "GetQuestionnaireTest$", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, }, expect: expect{ isCheckLen: true, @@ -1072,21 +1081,23 @@ func getQuestionnairesTest(t *testing.T) { { description: "userID:valid, sort:no, search:no, page:2", args: args{ - userID: questionnairesTestUserID, - sort: "", - search: "", - pageNum: 2, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 2, + onlyTargetingMe: true, + onlyAdministratedByMe: true, }, }, { description: "too large page", args: args{ - userID: questionnairesTestUserID, - sort: "", - search: "", - pageNum: 100000, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 100000, + onlyTargetingMe: true, + onlyAdministratedByMe: true, }, expect: expect{ isErr: true, @@ -1096,21 +1107,23 @@ func getQuestionnairesTest(t *testing.T) { { description: "userID:valid, sort:no, search:no, page:1, nontargetted", args: args{ - userID: questionnairesTestUserID, - sort: "", - search: "", - pageNum: 1, - nontargeted: true, + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 1, + onlyTargetingMe: true, + onlyAdministratedByMe: true, }, }, { description: "userID:valid, sort:no, search:notFoundQuestionnaire, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "", - search: "notFoundQuestionnaire", - pageNum: 1, - nontargeted: true, + userID: questionnairesTestUserID, + sort: "", + search: "notFoundQuestionnaire", + pageNum: 1, + onlyTargetingMe: true, + onlyAdministratedByMe: true, }, expect: expect{ isCheckLen: false, @@ -1120,11 +1133,171 @@ func getQuestionnairesTest(t *testing.T) { { description: "userID:valid, sort:invalid, search:no, page:1", args: args{ - userID: questionnairesTestUserID, - sort: "hogehoge", - search: "", - pageNum: 1, - nontargeted: false, + userID: questionnairesTestUserID, + sort: "hogehoge", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: true, + }, + expect: expect{ + isErr: true, + err: ErrInvalidSortParam, + }, + }, + { + description: "userID:valid, sort:no, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:created_at, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "created_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:-created_at, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "-created_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:title, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "title", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:-title, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "-title", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:modified_at, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "modified_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:-modified_at, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "-modified_at", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:no, search:GetQuestionnaireTest$, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "", + search: "GetQuestionnaireTest$", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, + }, + expect: expect{ + isCheckLen: true, + length: 4, + }, + }, + { + description: "userID:valid, sort:no, search:no, page:2", + args: args{ + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 2, + onlyTargetingMe: true, + onlyAdministratedByMe: false, + }, + }, + { + description: "too large page", + args: args{ + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 100000, + onlyTargetingMe: true, + onlyAdministratedByMe: false, + }, + expect: expect{ + isErr: true, + err: ErrTooLargePageNum, + }, + }, + { + description: "userID:valid, sort:no, search:no, page:1, nontargetted", + args: args{ + userID: questionnairesTestUserID, + sort: "", + search: "", + pageNum: 1, + onlyTargetingMe: true, + onlyAdministratedByMe: false, + }, + }, + { + description: "userID:valid, sort:no, search:notFoundQuestionnaire, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "", + search: "notFoundQuestionnaire", + pageNum: 1, + onlyTargetingMe: true, + onlyAdministratedByMe: false, + }, + expect: expect{ + isCheckLen: false, + length: 0, + }, + }, + { + description: "userID:valid, sort:invalid, search:no, page:1", + args: args{ + userID: questionnairesTestUserID, + sort: "hogehoge", + search: "", + pageNum: 1, + onlyTargetingMe: false, + onlyAdministratedByMe: false, }, expect: expect{ isErr: true, @@ -1136,7 +1309,7 @@ func getQuestionnairesTest(t *testing.T) { for _, testCase := range testCases { ctx := context.Background() - questionnaires, pageMax, err := questionnaireImpl. (ctx, testCase.args.userID, testCase.args.sort, testCase.args.search, testCase.args.pageNum, testCase.args.nontargeted) + questionnaires, pageMax, err := questionnaireImpl.GetQuestionnaires(ctx, testCase.args.userID, testCase.args.sort, testCase.args.search, testCase.args.pageNum, testCase.args.onlyTargetingMe, testCase.args.onlyAdministratedByMe) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -1163,11 +1336,16 @@ func getQuestionnairesTest(t *testing.T) { for _, questionnaire := range questionnaires { actualQuestionnaireIDs = append(actualQuestionnaireIDs, questionnaire.ID) } - if testCase.args.nontargeted { + if testCase.args.onlyTargetingMe { for _, targettedQuestionnaireID := range userTargetMap[questionnairesTestUserID] { assertion.NotContains(actualQuestionnaireIDs, targettedQuestionnaireID, testCase.description, "not contain(targetted)") } } + if testCase.args.onlyAdministratedByMe { + for _, targettedQuestionnaireID := range userTargetMap[questionnairesTestUserID] { + assertion.NotContains(actualQuestionnaireIDs, targettedQuestionnaireID, testCase.description, "not contain(administrated)") + } + } for _, deletedQuestionnaireID := range deletedQuestionnaireIDs { assertion.NotContains(actualQuestionnaireIDs, deletedQuestionnaireID, testCase.description, "not contain(deleted)") } @@ -1176,7 +1354,7 @@ func getQuestionnairesTest(t *testing.T) { assertion.Regexp(testCase.args.search, questionnaire.Title, testCase.description, "regexp") } - if len(testCase.args.search) == 0 && !testCase.args.nontargeted { + if len(testCase.args.search) == 0 && !testCase.args.onlyTargetingMe && !testCase.args.onlyAdministratedByMe { fmt.Println(testCase.description) fmt.Println(questionnaireNum) fmt.Println(pageMax) @@ -1409,6 +1587,9 @@ func getQuestionnaireInfoTest(t *testing.T) { actualQuestionnaire, actualTargets, actualTargetGroups, actualAdministrators, actualAdministratorGroups, actualRespondents, err := questionnaireImpl.GetQuestionnaireInfo(ctx, testCase.questionnaireID) + _ = actualTargetGroups + _ = actualAdministratorGroups + if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") } else if testCase.expect.err != nil { diff --git a/model/respondents_test.go b/model/respondents_test.go index a9f353bc..97aa7105 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) @@ -391,9 +391,9 @@ func TestGetRespondentInfos(t *testing.T) { args expect } - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, true) require.NoError(t, err) - questionnaireID2, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true) + questionnaireID2, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第2回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, true) require.NoError(t, err) questionnaire := Questionnaires{} @@ -523,7 +523,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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, true) require.NoError(t, err) questionnaire := Questionnaires{} @@ -620,7 +620,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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, true) require.NoError(t, err) questionnaire := Questionnaires{} @@ -637,6 +637,8 @@ func TestGetRespondentDetails(t *testing.T) { type args struct { questionnaireID int sort string + onlyMyResponse bool + userID string } type expect struct { isErr bool @@ -683,7 +685,6 @@ func TestGetRespondentDetails(t *testing.T) { questionID, err := questionImpl.InsertQuestion(ctx, question.QuestionnaireID, question.PageNum, question.QuestionNum, question.Type, question.Body, question.IsRequired) require.NoError(t, err) questionIDs = append(questionIDs, questionID) - } respondents := []Respondents{ @@ -750,6 +751,150 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "traqid", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{0, 1, 2}, + }, + }, + { + description: "-traqid", + args: args{ + questionnaireID: questionnaireID, + sort: "-traqid", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{2, 1, 0}, + }, + }, + { + description: "submitted_at", + args: args{ + questionnaireID: questionnaireID, + sort: "submitted_at", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{0, 2, 1}, + }, + }, + { + description: "-submitted_at", + args: args{ + questionnaireID: questionnaireID, + sort: "-submitted_at", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{1, 2, 0}, + }, + }, + { + description: "questionnaire does not exist", + args: args{ + questionnaireID: -1, + sort: "1", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 0, + sortIdx: []int{}, + }, + }, + { + description: "sortNum Number", + args: args{ + questionnaireID: questionnaireID, + sort: "3", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{2, 1, 0}, + }, + }, + { + description: "sortNum Number", + args: args{ + questionnaireID: questionnaireID, + sort: "-3", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{0, 1, 2}, + }, + }, + { + description: "sortNum Text", + args: args{ + questionnaireID: questionnaireID, + sort: "1", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{0, 1, 2}, + }, + }, + { + description: "sortNum Text desc", + args: args{ + questionnaireID: questionnaireID, + sort: "-1", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{2, 1, 0}, + }, + }, + { + description: "invalid sortnum", + args: args{ + questionnaireID: questionnaireID, + sort: "a", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + isErr: true, + }, + }, + { + description: "empty sortnum", + args: args{ + questionnaireID: questionnaireID, + sort: "", + onlyMyResponse: false, + userID: userOne, + }, + expect: expect{ + length: 3, + sortIdx: []int{0, 1, 2}, + }, + }, + { + description: "traqid", + args: args{ + questionnaireID: questionnaireID, + sort: "traqid", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -761,6 +906,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "-traqid", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -772,6 +919,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "submitted_at", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -783,6 +932,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "-submitted_at", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -794,6 +945,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: -1, sort: "1", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 0, @@ -805,6 +958,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "3", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -816,6 +971,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "-3", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -827,6 +984,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "1", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -838,6 +997,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "-1", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -849,6 +1010,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "a", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ isErr: true, @@ -859,6 +1022,8 @@ func TestGetRespondentDetails(t *testing.T) { args: args{ questionnaireID: questionnaireID, sort: "", + onlyMyResponse: true, + userID: userOne, }, expect: expect{ length: 3, @@ -868,7 +1033,7 @@ func TestGetRespondentDetails(t *testing.T) { } for _, testCase := range testCases { - respondentDetails, err := respondentImpl.GetRespondentDetails(ctx, testCase.args.questionnaireID, testCase.args.sort) + respondentDetails, err := respondentImpl.GetRespondentDetails(ctx, testCase.args.questionnaireID, testCase.args.sort, testCase.args.onlyMyResponse, testCase.args.userID) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") } else if testCase.expect.err != nil { @@ -909,7 +1074,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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, true) require.NoError(t, err) questionnaireIDs = append(questionnaireIDs, questionnaireID) } @@ -997,7 +1162,7 @@ func TestGetMyResponseIDs(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, true) require.NoError(t, err) respondents := []Respondents{ @@ -1092,7 +1257,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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, 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 ec0b5b1d..60e64a64 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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 ad5ea98e..cbbbbc6e 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) diff --git a/model/targets_test.go b/model/targets_test.go index 79adf7c6..e55fca6b 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -318,7 +318,7 @@ func TestIsTargetingMe(t *testing.T) { assertion := assert.New(t) ctx := context.Background() - questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "private", true, true) require.NoError(t, err) err = targetImpl.InsertTargets(ctx, questionnaireID, []string{userOne}) diff --git a/model/validations_test.go b/model/validations_test.go index 54ab6548..b150805d 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, 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", true) + questionnaireID, err := questionnaireImpl.InsertQuestionnaire(ctx, "第1回集会らん☆ぷろ募集アンケート", "第1回メンバー集会でのらん☆ぷろで発表したい人を募集します らん☆ぷろで発表したい人あつまれー!", null.NewTime(time.Now(), false), "public", true, true) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) From 934657455a883617d21f8d55d07fefba625f3034 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Sat, 2 Nov 2024 18:39:56 +0000 Subject: [PATCH 48/83] feat: add IsDuplicateAnswerAllowed parameter for db migration --- model/v3.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/model/v3.go b/model/v3.go index 1935453a..dce971e5 100644 --- a/model/v3.go +++ b/model/v3.go @@ -21,22 +21,23 @@ func v3() *gormigrate.Migration { } type v3Questionnaires 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"` - TargetGroups []TargetGroups `json:"-" gorm:"foreignKey:QuestionnaireID"` - Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` - Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` - IsPublished bool `json:"is_published" gorm:"type:boolean;default:false"` + 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"` + TargetGroups []TargetGroups `json:"-" gorm:"foreignKey:QuestionnaireID"` + Questions []Questions `json:"-" gorm:"foreignKey:QuestionnaireID"` + Respondents []Respondents `json:"-" gorm:"foreignKey:QuestionnaireID"` + IsPublished bool `json:"is_published" gorm:"type:boolean;default:false"` + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed" gorm:"type:tinyint(4);size:4;not null;default:0"` } func (*v3Questionnaires) TableName() string { return "questionnaires" -} \ No newline at end of file +} From 84a33d2ffd07b8acd99cbfcf935230c56cc84472 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Sat, 2 Nov 2024 19:20:28 +0000 Subject: [PATCH 49/83] feat: unify IsAllowingMultipleResponses to IsDuplicateAnswerAllowed and update controller with new parameter --- controller/adapter.go | 24 +++---- controller/questionnaire.go | 20 +++--- docs/swagger/swagger.yaml | 10 +-- openapi/spec.go | 140 ++++++++++++++++++------------------ openapi/types.go | 36 +++++----- 5 files changed, 115 insertions(+), 115 deletions(-) diff --git a/controller/adapter.go b/controller/adapter.go index 4284a385..020bd885 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -11,14 +11,14 @@ import ( func questionnaireInfo2questionnaireSummary(questionnaireInfo model.QuestionnaireInfo, allResponded bool, hasMyDraft bool, hasMyResponse bool, respondedDateTimeByMe null.Time) *openapi.QuestionnaireSummary { res := openapi.QuestionnaireSummary{ - AllResponded: allResponded, - CreatedAt: questionnaireInfo.CreatedAt, - Description: questionnaireInfo.Description, - HasMyDraft: hasMyDraft, - HasMyResponse: hasMyResponse, - // IsAllowingMultipleResponses: questionnaireInfo.IsAllowingMultipleResponses, + AllResponded: allResponded, + CreatedAt: questionnaireInfo.CreatedAt, + Description: questionnaireInfo.Description, + HasMyDraft: hasMyDraft, + HasMyResponse: hasMyResponse, + IsDuplicateAnswerAllowed: questionnaireInfo.IsDuplicateAnswerAllowed, // IsAnonymous: questionnaireInfo.IsAnonymous, - // IsPublished: questionnaireInfo.IsPublished, + IsPublished: questionnaireInfo.IsPublished, IsTargetingMe: questionnaireInfo.IsTargeted, ModifiedAt: questionnaireInfo.ModifiedAt, QuestionnaireId: questionnaireInfo.ID, @@ -149,12 +149,12 @@ func convertRespondents(respondents []model.Respondents) []string { func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, adminUsers []string, adminGroups []string, targetUsers []string, targetGroups []string, respondents []string) openapi.QuestionnaireDetail { res := openapi.QuestionnaireDetail{ - Admins: createUsersAndGroups(adminUsers, adminGroups), - CreatedAt: questionnaires.CreatedAt, - Description: questionnaires.Description, - // IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, + Admins: createUsersAndGroups(adminUsers, adminGroups), + CreatedAt: questionnaires.CreatedAt, + Description: questionnaires.Description, + IsDuplicateAnswerAllowed: questionnaires.IsDuplicateAnswerAllowed, // IsAnonymous: questionnaires.IsAnonymous, - // IsPublished: questionnaires.IsPublished, + IsPublished: questionnaires.IsPublished, ModifiedAt: questionnaires.ModifiedAt, QuestionnaireId: questionnaires.ID, Questions: convertQuestions(questionnaires.Questions), diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 622a658f..74bf5962 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -111,7 +111,7 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o questionnaireID := 0 err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy), params.IsPublished) + questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy), params.IsPublished, params.IsDuplicateAnswerAllowed) if err != nil { c.Logger().Errorf("failed to insert questionnaire: %+v", err) return err @@ -214,12 +214,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") } - err = q.IScaleLabel.InsertScaleLabel(c.Request().Context(), question.ID, + err = q.IScaleLabel.InsertScaleLabel(c.Request().Context(), question.ID, model.ScaleLabels{ - ScaleLabelLeft: *b.MinLabel, + ScaleLabelLeft: *b.MinLabel, ScaleLabelRight: *b.MaxLabel, - ScaleMax: b.MaxValue, - ScaleMin: b.MinValue, + ScaleMax: b.MaxValue, + ScaleMin: b.MinValue, }) if err != nil { c.Logger().Errorf("failed to insert scale label: %+v", err) @@ -302,7 +302,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa responseDueDateTime.Time = *params.ResponseDueDateTime } err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID, params.IsPublished) + err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID, params.IsPublished, params.IsDuplicateAnswerAllowed) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to update questionnaire: %+v", err) return err @@ -397,12 +397,12 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa 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, + err = q.IScaleLabel.UpdateScaleLabel(c.Request().Context(), question.ID, model.ScaleLabels{ - ScaleLabelLeft: *b.MinLabel, + ScaleLabelLeft: *b.MinLabel, ScaleLabelRight: *b.MaxLabel, - ScaleMax: b.MaxValue, - ScaleMin: b.MinValue, + ScaleMax: b.MaxValue, + ScaleMin: b.MinValue, }) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to insert scale label: %+v", err) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index fe99b014..b02dd05e 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -480,7 +480,7 @@ components: - $ref: "#/components/schemas/QuestionnaireResponseDueDateTime" - $ref: "#/components/schemas/QuestionnaireResponseViewableBy" - $ref: "#/components/schemas/QuestionnaireIsAnonymous" - - $ref: "#/components/schemas/QuestionnaireIsAllowingMultipleResponses" + - $ref: "#/components/schemas/QuestionnaireIsDuplicateAnswerAllowed" - $ref: "#/components/schemas/QuestionnaireIsPublished" - $ref: "#/components/schemas/QuestionnaireTargetsAndAdmins" NewQuestionnaire: @@ -519,7 +519,7 @@ components: - $ref: "#/components/schemas/QuestionnaireResponseDueDateTime" - $ref: "#/components/schemas/QuestionnaireResponseViewableBy" - $ref: "#/components/schemas/QuestionnaireIsAnonymous" - - $ref: "#/components/schemas/QuestionnaireIsAllowingMultipleResponses" + - $ref: "#/components/schemas/QuestionnaireIsDuplicateAnswerAllowed" - $ref: "#/components/schemas/QuestionnaireIsPublished" - $ref: "#/components/schemas/QuestionnaireIsTargetingMe" - $ref: "#/components/schemas/QuestionnaireCreatedAt" @@ -608,16 +608,16 @@ components: 匿名回答かどうか required: - is_anonymous - QuestionnaireIsAllowingMultipleResponses: + QuestionnaireIsDuplicateAnswerAllowed: type: object properties: - is_allowing_multiple_responses: + is_duplicate_answer_allowed: type: boolean example: true description: | 一人が複数回回答できるかどうか required: - - is_allowing_multiple_responses + - is_duplicate_answer_allowed QuestionnaireIsPublished: type: object properties: diff --git a/openapi/spec.go b/openapi/spec.go index 6b165df9..44a370ac 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -18,76 +18,76 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index b0ca79bc..350002bc 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -151,12 +151,12 @@ type NewQuestionnaire struct { Admins UsersAndGroups `json:"admins"` Description string `json:"description"` - // IsAllowingMultipleResponses 一人が複数回回答できるかどうか - IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` - // IsAnonymous 匿名回答かどうか IsAnonymous bool `json:"is_anonymous"` + // IsDuplicateAnswerAllowed 一人が複数回回答できるかどうか + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed"` + // IsPublished アンケートが公開されているかどうか IsPublished bool `json:"is_published"` Questions []NewQuestion `json:"questions"` @@ -315,12 +315,12 @@ type QuestionnaireBase struct { Admins UsersAndGroups `json:"admins"` Description string `json:"description"` - // IsAllowingMultipleResponses 一人が複数回回答できるかどうか - IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` - // IsAnonymous 匿名回答かどうか IsAnonymous bool `json:"is_anonymous"` + // IsDuplicateAnswerAllowed 一人が複数回回答できるかどうか + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed"` + // IsPublished アンケートが公開されているかどうか IsPublished bool `json:"is_published"` @@ -349,12 +349,12 @@ type QuestionnaireDetail struct { CreatedAt time.Time `json:"created_at"` Description string `json:"description"` - // IsAllowingMultipleResponses 一人が複数回回答できるかどうか - IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` - // IsAnonymous 匿名回答かどうか IsAnonymous bool `json:"is_anonymous"` + // IsDuplicateAnswerAllowed 一人が複数回回答できるかどうか + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed"` + // IsPublished アンケートが公開されているかどうか IsPublished bool `json:"is_published"` ModifiedAt time.Time `json:"modified_at"` @@ -389,18 +389,18 @@ type QuestionnaireInfo struct { Title string `json:"title"` } -// QuestionnaireIsAllowingMultipleResponses defines model for QuestionnaireIsAllowingMultipleResponses. -type QuestionnaireIsAllowingMultipleResponses struct { - // IsAllowingMultipleResponses 一人が複数回回答できるかどうか - IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` -} - // QuestionnaireIsAnonymous defines model for QuestionnaireIsAnonymous. type QuestionnaireIsAnonymous struct { // IsAnonymous 匿名回答かどうか IsAnonymous bool `json:"is_anonymous"` } +// QuestionnaireIsDuplicateAnswerAllowed defines model for QuestionnaireIsDuplicateAnswerAllowed. +type QuestionnaireIsDuplicateAnswerAllowed struct { + // IsDuplicateAnswerAllowed 一人が複数回回答できるかどうか + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed"` +} + // QuestionnaireIsPublished defines model for QuestionnaireIsPublished. type QuestionnaireIsPublished struct { // IsPublished アンケートが公開されているかどうか @@ -456,12 +456,12 @@ type QuestionnaireSummary struct { // HasMyResponse 回答が存在する HasMyResponse bool `json:"has_my_response"` - // IsAllowingMultipleResponses 一人が複数回回答できるかどうか - IsAllowingMultipleResponses bool `json:"is_allowing_multiple_responses"` - // IsAnonymous 匿名回答かどうか IsAnonymous bool `json:"is_anonymous"` + // IsDuplicateAnswerAllowed 一人が複数回回答できるかどうか + IsDuplicateAnswerAllowed bool `json:"is_duplicate_answer_allowed"` + // IsPublished アンケートが公開されているかどうか IsPublished bool `json:"is_published"` From a3fe0998a5dff5e18f71a80406bf2c831593aa8b Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 08:54:55 +0900 Subject: [PATCH 50/83] impl: add is_canceled to targets --- docs/db_schema.md | 1 + model/targets.go | 1 + model/targets_impl.go | 20 ++++++++ model/targets_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++ model/v3.go | 28 ++++++++++ 5 files changed, 166 insertions(+) create mode 100644 model/v3.go diff --git a/docs/db_schema.md b/docs/db_schema.md index 45f30206..5327ee87 100644 --- a/docs/db_schema.md +++ b/docs/db_schema.md @@ -107,3 +107,4 @@ | ---------------- | -------- | ---- | --- | ------- | ----- | -------- | | questionnaire_id | int(11) | NO | PRI | _NULL_ | | user_traqid | char(32) | NO | PRI | _NULL_ | +| is_canceled | boolean | NO | | false | | アンケートの対象者がキャンセルしたかどうか | diff --git a/model/targets.go b/model/targets.go index a1bb5506..e6d4cc17 100644 --- a/model/targets.go +++ b/model/targets.go @@ -10,4 +10,5 @@ type ITarget interface { DeleteTargets(ctx context.Context, questionnaireID int) error GetTargets(ctx context.Context, questionnaireIDs []int) ([]Targets, error) IsTargetingMe(ctx context.Context, quesionnairID int, userID string) (bool, error) + CancelTargets(ctx context.Context, questionnaireID int, targets []string) error } diff --git a/model/targets_impl.go b/model/targets_impl.go index 6e046217..65196014 100644 --- a/model/targets_impl.go +++ b/model/targets_impl.go @@ -17,6 +17,7 @@ func NewTarget() *Target { type Targets struct { QuestionnaireID int `gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` UserTraqid string `gorm:"type:varchar(32);size:32;not null;primaryKey"` + IsCanceled bool `gorm:"type:tinyint(1);not null;default:0"` } // InsertTargets アンケートの対象を追加 @@ -35,6 +36,7 @@ func (*Target) InsertTargets(ctx context.Context, questionnaireID int, targets [ dbTargets = append(dbTargets, Targets{ QuestionnaireID: questionnaireID, UserTraqid: target, + IsCanceled: false, }) } @@ -101,3 +103,21 @@ func (*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID str } return false, nil } + +// CancelTargets アンケートの対象をキャンセル(削除しない) +func (*Target) CancelTargets(ctx context.Context, questionnaireID int, targets []string) error { + db, err := getTx(ctx) + if err != nil { + return fmt.Errorf("failed to get transaction: %w", err) + } + + err = db. + Model(&Targets{}). + Where("questionnaire_id = ? AND user_traqid IN (?)", questionnaireID, targets). + Update("is_canceled", true).Error + if err != nil { + return fmt.Errorf("failed to cancel targets: %w", err) + } + + return nil +} \ No newline at end of file diff --git a/model/targets_test.go b/model/targets_test.go index 71404545..4b60e83b 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -376,3 +376,119 @@ func TestIsTargetingMe(t *testing.T) { assertion.Equal(testCase.expect.isTargeted, isTargeted, testCase.description, "isTargeted") } } + +func TestCancelTargets(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + type test struct { + description string + beforeValidTargets []string + beforeInvalidTargets []string + afterValidTargets []string + afterInvalidTargets []string + argCancelTargets []string + isErr bool + err error + } + + testCases := []test{ + { + description: "キャンセルするtargetが1人でエラーなし", + beforeValidTargets: []string{"a"}, + beforeInvalidTargets: []string{}, + afterValidTargets: []string{}, + afterInvalidTargets: []string{"a"}, + argCancelTargets: []string{"a"}, + }, + { + description: "キャンセルするtargetが複数でエラーなし", + beforeValidTargets: []string{"a", "b"}, + beforeInvalidTargets: []string{}, + afterValidTargets: []string{}, + afterInvalidTargets: []string{"a", "b"}, + argCancelTargets: []string{"a", "b"}, + }, + { + description: "キャンセルするtargetがないときエラーなし", + beforeValidTargets: []string{"a"}, + beforeInvalidTargets: []string{}, + afterValidTargets: []string{"a"}, + afterInvalidTargets: []string{}, + argCancelTargets: []string{}, + }, + { + description: "キャンセルするtargetが見つからないときエラー", + beforeValidTargets: []string{"a"}, + beforeInvalidTargets: []string{}, + afterValidTargets: []string{"a"}, + afterInvalidTargets: []string{}, + argCancelTargets: []string{"b"}, + isErr: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + targets := make([]Targets, 0, len(testCase.beforeValidTargets)+len(testCase.beforeInvalidTargets)) + for _, target := range testCase.beforeValidTargets { + targets = append(targets, Targets{ + UserTraqid: target, + IsCanceled: false, + }) + } + for _, target := range testCase.beforeInvalidTargets { + targets = append(targets, Targets{ + UserTraqid: target, + IsCanceled: true, + }) + } + questionnaire := Questionnaires{ + Targets: targets, + } + err := db. + Session(&gorm.Session{}). + Create(&questionnaire).Error + if err != nil { + t.Errorf("failed to create questionnaire: %v", err) + } + + err = targetImpl.CancelTargets(ctx, questionnaire.ID, testCase.argCancelTargets) + if err != nil { + if !testCase.isErr { + t.Errorf("unexpected error: %v", err) + } else if !errors.Is(err, testCase.err) { + t.Errorf("invalid error: expected: %+v, actual: %+v", testCase.err, err) + } + return + } + + afterTargets := make([]Targets, 0, len(testCase.afterValidTargets)+len(testCase.afterInvalidTargets)) + for _, afterTarget := range testCase.afterInvalidTargets { + afterTargets = append(afterTargets, Targets{ + UserTraqid: afterTarget, + IsCanceled: false, + }) + } + for _, afterTarget := range testCase.afterValidTargets { + afterTargets = append(afterTargets, Targets{ + UserTraqid: afterTarget, + IsCanceled: true, + }) + } + + actualTargets := make([]Targets, 0, len(testCase.afterValidTargets)+len(testCase.afterInvalidTargets)) + err = db. + Session(&gorm.Session{}). + Model(&Targets{}). + Where("questionnaire_id = ?", questionnaire.ID). + Find(&actualTargets).Error + if err != nil { + t.Errorf("failed to get targets: %v", err) + } + + assert.ElementsMatchf(t, afterTargets, actualTargets, "targets") + }) + } +} diff --git a/model/v3.go b/model/v3.go new file mode 100644 index 00000000..5aa35ba4 --- /dev/null +++ b/model/v3.go @@ -0,0 +1,28 @@ +package model + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +func v3() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "3", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&Targets{}); err != nil { + return err + } + return nil + }, + } +} + +type v3Targets struct { + QuestionnaireID int `gorm:"type:int(11) AUTO_INCREMENT;not null;primaryKey"` + UserTraqid string `gorm:"type:varchar(32);size:32;not null;primaryKey"` + IsCanceled bool `gorm:"type:tinyint(1);not null;default:0"` +} + +func (*v3Targets) TableName() string { + return "targets" +} \ No newline at end of file From 18465c964fefdd4be9ff6a863b54f050f729e667 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 15:02:27 +0900 Subject: [PATCH 51/83] impl: add reminder --- controller/questionnaire.go | 52 +++++++++++++ controller/reminder.go | 147 +++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 2 + main.go | 42 +++++++--- model/questionnaires.go | 1 + model/questionnaires_impl.go | 19 +++++ 7 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 controller/reminder.go diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 1bfb71f1..b9ab839c 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -171,6 +171,12 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o return err } + Jq.PushReminder(questionnaireID, params.ResponseDueDateTime) + if err != nil { + c.Logger().Errorf("failed to push reminder: %+v", err) + return err + } + return nil }) if err != nil { @@ -358,6 +364,17 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa return err } + err = Jq.DeleteReminder(questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete reminder: %+v", err) + return err + } + err = Jq.PushReminder(questionnaireID, params.ResponseDueDateTime) + if err != nil { + c.Logger().Errorf("failed to push reminder: %+v", err) + return err + } + return nil }) if err != nil { @@ -483,6 +500,12 @@ func (q Questionnaire) DeleteQuestionnaire(c echo.Context, questionnaireID int) return err } + err = Jq.DeleteReminder(questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete reminder: %+v", err) + return err + } + return nil }) if err != nil { @@ -706,6 +729,35 @@ https://anke-to.trap.jp/responses/new/%d`, ) } +func createReminderMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit time.Time, targets []string, leftTimeText string) string { + resTimeLimitText := resTimeLimit.Local().Format("2006/01/02 15:04") + targetsMentionText := "@" + strings.Join(targets, " @") + + return fmt.Sprintf( + `### アンケート『[%s](https://anke-to.trap.jp/questionnaires/%d)』の回答期限が迫っています! +==残り%sです!== +#### 管理者 +%s +#### 説明 +%s +#### 回答期限 +%s +#### 対象者 +%s +#### 回答リンク +https://anke-to.trap.jp/responses/new/%d +`, + title, + questionnaireID, + leftTimeText, + strings.Join(administrators, ","), + description, + resTimeLimitText, + targetsMentionText, + questionnaireID, + ) +} + func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID int, userID string) (openapi.Result, error) { res := openapi.Result{} diff --git a/controller/reminder.go b/controller/reminder.go new file mode 100644 index 00000000..d520b748 --- /dev/null +++ b/controller/reminder.go @@ -0,0 +1,147 @@ +package controller + +import ( + "context" + "slices" + "sort" + "sync" + "time" + + "github.com/traPtitech/anke-to/model" + "github.com/traPtitech/anke-to/traq" + "golang.org/x/sync/semaphore" +) + +type Job struct { + Timestamp time.Time + QuestionnaireID int + Action func() +} + +type JobQueue struct { + jobs []*Job + mu sync.Mutex +} + +var ( + sem = semaphore.NewWeighted(1) + Jq = &JobQueue{} + Wg = &sync.WaitGroup{} + reminderTimingMinutes = []int{5, 30, 60, 1440, 10080} + reminderTimingStrings = []string{"5分", "30分", "1時間", "1日", "1週間"} +) + +func (jq *JobQueue) Push(job *Job) { + jq.mu.Lock() + defer jq.mu.Unlock() + jq.jobs = append(jq.jobs, job) + sort.Slice(jq.jobs, func(i, j int) bool { + return jq.jobs[i].Timestamp.Before(jq.jobs[j].Timestamp) + }) +} + +func (jq *JobQueue) Pop() *Job { + jq.mu.Lock() + defer jq.mu.Unlock() + if len(jq.jobs) == 0 { + return nil + } + job := jq.jobs[0] + jq.jobs = jq.jobs[1:] + return job +} + +func (jq *JobQueue) PushReminder(questionnaireID int, limit *time.Time) error { + + for i, timing := range reminderTimingMinutes { + remindTimeStamp := limit.Add(-time.Duration(timing) * time.Minute) + if remindTimeStamp.Before(time.Now()) { + Jq.Push(&Job{ + Timestamp: remindTimeStamp, + QuestionnaireID: questionnaireID, + Action: func() { + reminderAction(questionnaireID, reminderTimingStrings[i]) + }, + }) + } + } + + return nil +} + +func (jq *JobQueue) DeleteReminder(questionnaireID int) error { + jq.mu.Lock() + defer jq.mu.Unlock() + if len(jq.jobs) == 1 && jq.jobs[0].QuestionnaireID == questionnaireID { + jq.jobs = []*Job{} + } + for i, job := range jq.jobs { + if job.QuestionnaireID == questionnaireID { + jq.jobs = append(jq.jobs[:i], jq.jobs[i+1:]...) + } + } + + return nil +} + +func reminderAction(questionnaireID int, leftTimeText string) error { + ctx := context.Background() + q := model.Questionnaire{} + questionnaire, _, _, administrators, _, respondants, err := q.GetQuestionnaireInfo(ctx, questionnaireID) + if err != nil { + return err + } + + var reminderTargets []string + for _, target := range questionnaire.Targets { + if target.IsCanceled { + continue + } + if slices.Contains(respondants, target.UserTraqid) { + continue + } + reminderTargets = append(reminderTargets, target.UserTraqid) + } + + reminderMessage := createReminderMessage(questionnaireID, questionnaire.Title, questionnaire.Description, administrators, questionnaire.ResTimeLimit.Time, reminderTargets, leftTimeText) + wh := traq.NewWebhook() + err = wh.PostMessage(reminderMessage) + if err != nil { + return err + } + + return nil +} + +func ReminderWorker() { + for { + job := Jq.Pop() + if job == nil { + time.Sleep(1 * time.Minute) + continue + } + + if time.Until(job.Timestamp) > 0 { + time.Sleep(time.Until(job.Timestamp)) + } + + Wg.Add(1) + go func() { + defer Wg.Done() + job.Action() + }() + } +} + +func ReminderInit() { + questionnaires, err := model.NewQuestionnaire().GetQuestionnairesInfoForReminder(context.Background()) + if err != nil { + panic(err) + } + for _, questionnaire := range questionnaires { + err := Jq.PushReminder(questionnaire.ID, &questionnaire.ResTimeLimit.Time) + if err != nil { + panic(err) + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 1ff36d4d..a17726c7 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.10.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.21.0 // indirect diff --git a/go.sum b/go.sum index 2c369dc4..0c26be87 100644 --- a/go.sum +++ b/go.sum @@ -575,6 +575,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/main.go b/main.go index fe08cd25..5133a54e 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" oapiMiddleware "github.com/oapi-codegen/echo-middleware" + "github.com/traPtitech/anke-to/controller" "github.com/traPtitech/anke-to/handler" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" @@ -57,17 +58,36 @@ func main() { panic("no PORT") } - e := echo.New() - swagger, err := openapi.GetSwagger() - if err != nil { - panic(err) - } - e.Use(oapiMiddleware.OapiRequestValidator(swagger)) - e.Use(handler.SetUserIDMiddleware) - e.Use(middleware.Logger()) - e.Use(middleware.Recover()) - openapi.RegisterHandlers(e, handler.Handler{}) - e.Logger.Fatal(e.Start(port)) + controller.Wg.Add(1) + go func() { + e := echo.New() + swagger, err := openapi.GetSwagger() + if err != nil { + panic(err) + } + e.Use(oapiMiddleware.OapiRequestValidator(swagger)) + e.Use(handler.SetUserIDMiddleware) + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + openapi.RegisterHandlers(e, handler.Handler{}) + e.Logger.Fatal(e.Start(port)) + controller.Wg.Done() + }() + + controller.Wg.Add(1) + go func () { + controller.ReminderInit() + controller.Wg.Done() + }() + + controller.Wg.Add(1) + go func() { + controller.ReminderWorker() + controller.Wg.Done() + }() + + controller.Wg.Wait() + // SetRouting(port) } diff --git a/model/questionnaires.go b/model/questionnaires.go index 8338abbf..010c5047 100644 --- a/model/questionnaires.go +++ b/model/questionnaires.go @@ -21,4 +21,5 @@ type IQuestionnaire interface { GetQuestionnaireLimitByResponseID(ctx context.Context, responseID int) (null.Time, error) GetResponseReadPrivilegeInfoByResponseID(ctx context.Context, userID string, responseID int) (*ResponseReadPrivilegeInfo, error) GetResponseReadPrivilegeInfoByQuestionnaireID(ctx context.Context, userID string, questionnaireID int) (*ResponseReadPrivilegeInfo, error) + GetQuestionnairesInfoForReminder(ctx context.Context) ([]Questionnaires, error) } diff --git a/model/questionnaires_impl.go b/model/questionnaires_impl.go index da751531..32512004 100755 --- a/model/questionnaires_impl.go +++ b/model/questionnaires_impl.go @@ -291,6 +291,7 @@ func (*Questionnaire) GetQuestionnaireInfo(ctx context.Context, questionnaireID err = db. Where("questionnaires.id = ?", questionnaireID). + Preload("Targets"). First(&questionnaire).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil, nil, nil, nil, nil, ErrRecordNotFound @@ -373,6 +374,24 @@ func (*Questionnaire) GetTargettedQuestionnaires(ctx context.Context, userID str return questionnaires, nil } +// GetQuestionnairesInfoForReminder 回答期限が7日以内のアンケートの詳細情報の取得 +func (*Questionnaire) GetQuestionnairesInfoForReminder(ctx context.Context) ([]Questionnaires, error) { + db, err := getTx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get tx: %w", err) + } + + questionnaires := []Questionnaires{} + err = db. + Where("res_time_limit > ? AND res_time_limit < ?", time.Now(), time.Now().AddDate(0, 0, 7)). + Find(&questionnaires).Error + if err != nil { + return nil, fmt.Errorf("failed to get the questionnaires: %w", err) + } + + return questionnaires, nil +} + // GetQuestionnaireLimit アンケートの回答期限の取得 func (*Questionnaire) GetQuestionnaireLimit(ctx context.Context, questionnaireID int) (null.Time, error) { db, err := getTx(ctx) From 83be06e0e8942d6af61e4f7f43ac4e00e5d1bf1f Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 15:05:40 +0900 Subject: [PATCH 52/83] fix: add migration --- model/current.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/model/current.go b/model/current.go index 9b3fea3e..973a7fda 100644 --- a/model/current.go +++ b/model/current.go @@ -6,7 +6,9 @@ import ( // Migrations is all db migrations func Migrations() []*gormigrate.Migration { - return []*gormigrate.Migration{} + return []*gormigrate.Migration{ + v3(), + } } func AllTables() []interface{} { From 023885199d5395494dfb724bb8d420927881954a Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 15:22:44 +0900 Subject: [PATCH 53/83] feat: add is_anonymous to questionnaires --- model/questionnaires.go | 5 +-- model/questionnaires_impl.go | 31 +++++++++++++++-- model/questionnaires_test.go | 66 ++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/model/questionnaires.go b/model/questionnaires.go index 8338abbf..9229cc2e 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, isAnonymous bool) (int, error) + UpdateQuestionnaire(ctx context.Context, title string, description string, resTimeLimit null.Time, resSharedTo string, questionnaireID int, isAnonymous bool) error DeleteQuestionnaire(ctx context.Context, questionnaireID int) error GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, onlyTargetingMe bool, onlyAdministratedByMe bool) ([]QuestionnaireInfo, int, error) GetAdminQuestionnaires(ctx context.Context, userID string) ([]Questionnaires, error) @@ -21,4 +21,5 @@ type IQuestionnaire interface { GetQuestionnaireLimitByResponseID(ctx context.Context, responseID int) (null.Time, error) GetResponseReadPrivilegeInfoByResponseID(ctx context.Context, userID string, responseID int) (*ResponseReadPrivilegeInfo, error) GetResponseReadPrivilegeInfoByQuestionnaireID(ctx context.Context, userID string, questionnaireID int) (*ResponseReadPrivilegeInfo, error) + GetResponseIsAnonymousByQuestionnaireID(ctx context.Context, questionnaireID int) (bool, error) } diff --git a/model/questionnaires_impl.go b/model/questionnaires_impl.go index da751531..6fa0b16d 100755 --- a/model/questionnaires_impl.go +++ b/model/questionnaires_impl.go @@ -28,6 +28,7 @@ type Questionnaires struct { 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"` + IsAnonymous bool `json:"is_anonymous" gorm:"type:boolean;not null;default:false"` 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"` @@ -81,7 +82,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, isAnonymous bool) (int, error) { db, err := getTx(ctx) if err != nil { return 0, fmt.Errorf("failed to get tx: %w", err) @@ -93,6 +94,7 @@ func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, des Title: title, Description: description, ResSharedTo: resSharedTo, + IsAnonymous: isAnonymous, } } else { questionnaire = Questionnaires{ @@ -100,6 +102,7 @@ func (*Questionnaire) InsertQuestionnaire(ctx context.Context, title string, des Description: description, ResTimeLimit: resTimeLimit, ResSharedTo: resSharedTo, + IsAnonymous: isAnonymous, } } @@ -112,7 +115,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, questionnaireID int, isAnonymous bool) error { db, err := getTx(ctx) if err != nil { return fmt.Errorf("failed to get tx: %w", err) @@ -125,6 +128,7 @@ func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, des Description: description, ResTimeLimit: resTimeLimit, ResSharedTo: resSharedTo, + IsAnonymous: isAnonymous, } } else { questionnaire = map[string]interface{}{ @@ -132,6 +136,7 @@ func (*Questionnaire) UpdateQuestionnaire(ctx context.Context, title string, des "description": description, "res_time_limit": gorm.Expr("NULL"), "res_shared_to": resSharedTo, + "is_anonymous": isAnonymous, } } @@ -471,6 +476,28 @@ func (*Questionnaire) GetResponseReadPrivilegeInfoByQuestionnaireID(ctx context. return &responseReadPrivilegeInfo, nil } +func (*Questionnaire) GetResponseIsAnonymousByQuestionnaireID(ctx context.Context, questionnaireID int) (bool, error) { + db, err := getTx(ctx) + if err != nil { + return true, fmt.Errorf("failed to get tx: %w", err) + } + + var isAnonymous bool + err = db. + Table("questionnaires"). + Where("questionnaires.id = ?", questionnaireID). + Select("questionnaires.is_anonymous"). + Take(&isAnonymous).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return true, ErrRecordNotFound + } + if err != nil { + return true, fmt.Errorf("failed to get is_anonymous: %w", err) + } + + return isAnonymous, nil +} + func setQuestionnairesOrder(query *gorm.DB, sort string) (*gorm.DB, error) { switch sort { case "created_at": diff --git a/model/questionnaires_test.go b/model/questionnaires_test.go index c3b716cf..cae14110 100644 --- a/model/questionnaires_test.go +++ b/model/questionnaires_test.go @@ -355,6 +355,7 @@ func insertQuestionnaireTest(t *testing.T) { description string resTimeLimit null.Time resSharedTo string + isAnonymous bool } type expect struct { isErr bool @@ -375,6 +376,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -384,6 +386,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -393,6 +396,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "respondents", + isAnonymous: false, }, }, { @@ -402,6 +406,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "administrators", + isAnonymous: false, }, }, { @@ -411,6 +416,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -420,6 +426,7 @@ func insertQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, expect: expect{ isErr: true, @@ -432,6 +439,7 @@ func insertQuestionnaireTest(t *testing.T) { description: strings.Repeat("a", 2000), resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -441,17 +449,28 @@ func insertQuestionnaireTest(t *testing.T) { description: strings.Repeat("a", 200000), resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, expect: expect{ isErr: true, }, }, + { + description: "anonymous questionnaire", + args: args{ + title: "第1回集会らん☆ぷろ募集アンケート", + description: "第1回集会らん☆ぷろ参加者募集", + resTimeLimit: null.NewTime(time.Time{}, false), + resSharedTo: "public", + isAnonymous: true, + }, + }, } 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.isAnonymous) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -491,6 +510,7 @@ func updateQuestionnaireTest(t *testing.T) { description string resTimeLimit null.Time resSharedTo string + isAnonymous bool } type expect struct { isErr bool @@ -512,12 +532,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "respondents", + isAnonymous: false, }, }, { @@ -527,12 +549,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第2回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -542,12 +566,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第2回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -557,12 +583,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "respondents", + isAnonymous: false, }, }, { @@ -572,12 +600,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第2回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -587,12 +617,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第2回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -602,12 +634,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -617,12 +651,14 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now().Add(time.Minute), true), resSharedTo: "public", + isAnonymous: false, }, }, { @@ -632,12 +668,31 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, after: args{ title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, + }, + }, + { + description: "update is_anonymous(false->true)", + before: args{ + title: "第1回集会らん☆ぷろ募集アンケート", + description: "第1回集会らん☆ぷろ参加者募集", + resTimeLimit: null.NewTime(time.Time{}, true), + resSharedTo: "public", + isAnonymous: false, + }, + after: args{ + title: "第1回集会らん☆ぷろ募集アンケート", + description: "第1回集会らん☆ぷろ参加者募集", + resTimeLimit: null.NewTime(time.Time{}, true), + resSharedTo: "public", + isAnonymous: true, }, }, } @@ -662,7 +717,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, questionnaireID, false) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") @@ -714,19 +769,21 @@ func updateQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, { title: "第1回集会らん☆ぷろ募集アンケート", description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Now(), true), resSharedTo: "public", + isAnonymous: false, }, } 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, invalidQuestionnaireID, arg.isAnonymous) if !errors.Is(err, ErrNoRecordUpdated) { if err == nil { t.Errorf("Succeeded with invalid questionnaireID") @@ -747,6 +804,7 @@ func deleteQuestionnaireTest(t *testing.T) { description string resTimeLimit null.Time resSharedTo string + isAnonymous bool } type expect struct { isErr bool @@ -764,6 +822,7 @@ func deleteQuestionnaireTest(t *testing.T) { description: "第1回集会らん☆ぷろ参加者募集", resTimeLimit: null.NewTime(time.Time{}, false), resSharedTo: "public", + isAnonymous: false, }, }, } @@ -776,6 +835,7 @@ func deleteQuestionnaireTest(t *testing.T) { Description: testCase.args.description, ResTimeLimit: testCase.args.resTimeLimit, ResSharedTo: testCase.args.resSharedTo, + IsAnonymous: testCase.args.isAnonymous, } err := db. Session(&gorm.Session{NewDB: true}). From 11f351c48c749028747b3116592bcf7f7516bc56 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 15:48:57 +0900 Subject: [PATCH 54/83] impl: add GetAnonymousRespondantDetails --- model/respondents.go | 1 + model/respondents_impl.go | 175 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/model/respondents.go b/model/respondents.go index e5cff356..5152a5d8 100644 --- a/model/respondents.go +++ b/model/respondents.go @@ -17,6 +17,7 @@ type IRespondent interface { GetRespondentInfos(ctx context.Context, userID string, questionnaireIDs ...int) ([]RespondentInfo, error) GetRespondentDetail(ctx context.Context, responseID int) (RespondentDetail, error) GetRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) + GetAnonymousRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) GetMyResponseIDs(ctx context.Context, sort string, userID string) ([]int, error) CheckRespondent(ctx context.Context, userID string, questionnaireID int) (bool, error) diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 2cbf4aaf..9a6de459 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -65,6 +65,15 @@ type RespondentDetail struct { Responses []ResponseBody `json:"body"` } +// AnonymousRespondantDetail 匿名の回答の詳細情報の構造体 +type AnonymousRenspondantDetail struct { + ResponseID int `json:"responseID,omitempty"` + QuestionnaireID int `json:"questionnaireID,omitempty"` + SubmittedAt null.Time `json:"submitted_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Responses []ResponseBody `json:"body"` +} + // InsertRespondent 回答の追加 func (*Respondent) InsertRespondent(ctx context.Context, userID string, questionnaireID int, submittedAt null.Time) (int, error) { db, err := getTx(ctx) @@ -366,6 +375,115 @@ func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int return respondentDetails, nil } +// GetAnonymousRespondantDetails アンケートの回答の匿名詳細情報一覧の取得 +func (*Respondent) GetAnonymousRespondantDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]AnonymousRenspondantDetail, error) { + db, err := getTx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get tx: %w", err) + } + + respondents := []Respondents{} + + // Note: respondents.submitted_at IS NOT NULLで一時保存の回答を除外している + query := db. + Session(&gorm.Session{}). + Where("respondants.questionnaire_id = ? AND respondents.submitted_at IS NOT NULL", questionnaireID). + Select("ResponseID", "UserTraqid", "ModifiedAt", "SubmittedAt") + + query, sortNum, err := setRespondentsOrder(query, sort) + if err != nil { + return nil, fmt.Errorf("failed to set order: %w", err) + } + + err = query. + Find(&respondents).Error + if err != nil { + return nil, fmt.Errorf("failed to get respondants: %w", err) + } + + if len(respondents) == 0 { + return []AnonymousRenspondantDetail{}, nil + } + + responseIDs := make([]int, 0, len(respondents)) + for _, respondent := range respondents { + responseIDs = append(responseIDs, respondent.ResponseID) + } + + respondantDetails := make([]AnonymousRenspondantDetail, 0, len(respondents)) + respondantDetailMap := make(map[int]*AnonymousRenspondantDetail, len(respondents)) + for i, respondent := range respondents { + respondantDetails = append(respondantDetails, AnonymousRenspondantDetail{ + ResponseID: respondent.ResponseID, + QuestionnaireID: questionnaireID, + SubmittedAt: respondent.SubmittedAt, + ModifiedAt: respondent.ModifiedAt, + }) + + respondantDetailMap[respondent.ResponseID] = &respondantDetails[i] + } + + questions := []Questions{} + query = db. + Preload("Responses", func(db *gorm.DB) *gorm.DB { + return db. + Select("ResponseID", "QuestionID", "Body"). + Where("response_id IN (?)", responseIDs) + }). + Where("questionnaire_id = ?", questionnaireID). + Order("question_num"). + Select("ID", "Type") + if onlyMyResponse { + query = query.Where("user_traqid = ?", userID) + } + err = query. + Find(&questions).Error + if err != nil { + return nil, fmt.Errorf("failed to get questions: &w", err) + } + + for _, question := range questions { + responseBodyMap := make(map[int][]string, len(respondents)) + for _, response := range question.Responses { + if response.Body.Valid { + responseBodyMap[response.ResponseID] = append(responseBodyMap[response.ResponseID], response.Body.String) + } + } + + for i := range respondantDetails { + responseBodies := responseBodyMap[respondantDetails[i].ResponseID] + responseBody := ResponseBody{ + QuestionID: question.ID, + QuestionType: question.Type, + } + + switch responseBody.QuestionType { + case "MultipleChoice", "Checkbox", "Dropdown": + if responseBodies == nil { + responseBody.OptionResponse = []string{} + } else { + responseBody.OptionResponse = responseBodies + } + default: + if len(responseBodies) == 0 { + responseBody.Body = null.NewString("", false) + } else { + responseBody.Body = null.NewString(responseBodies[0], true) + } + } + + respondantDetails[i].Responses = append(respondantDetails[i].Responses, responseBody) + } + } + + respondantDetails, err = sortAnonymousRespondantDetail(sortNum, len(questions), respondantDetails) + if err != nil { + return nil, fmt.Errorf("failed to sort RespondantDetails: %w", err) + } + + return respondantDetails, nil +} + // GetRespondentsUserIDs 回答者のユーザーID取得 func (*Respondent) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) { db, err := getTx(ctx) @@ -507,3 +625,60 @@ func sortRespondentDetail(sortNum int, questionNum int, respondentDetails []Resp return respondentDetails, nil } + +func sortAnonymousRespondantDetail(sortNum int, questionNum int, respondentDetails []AnonymousRenspondantDetail) ([]AnonymousRenspondantDetail, error) { + if sortNum == 0 { + return respondentDetails, nil + } + sortNumAbs := int(math.Abs(float64(sortNum))) + if sortNumAbs > questionNum { + return nil, fmt.Errorf("sort param is too large: %d", sortNum) + } + + sort.Slice(respondentDetails, func(i, j int) bool { + bodyI := respondentDetails[i].Responses[sortNumAbs-1] + bodyJ := respondentDetails[i].Responses[sortNumAbs-1] + if bodyI.QuestionType == "Number" { + numi, err := strconv.ParseFloat(bodyI.Body.String, 64) + if err != nil { + return true + } + numj, err := strconv.ParseFloat(bodyJ.Body.String, 64) + if err != nil { + return true + } + if sortNum < 0 { + return numi > numj + } + return numi < numj + } + if bodyI.QuestionType == "MultipleChoice" { + choiceI := "" + if len(bodyI.OptionResponse) > 0 { + choiceI = bodyI.OptionResponse[0] + } + choiceJ := "" + if len(bodyJ.OptionResponse) > 0 { + choiceJ = bodyJ.OptionResponse[0] + } + if sortNum < 0 { + return choiceI > choiceJ + } + return choiceI < choiceJ + } + if bodyI.QuestionType == "Checkbox" { + selectionsI := strings.Join(bodyI.OptionResponse, ", ") + selectionsJ := strings.Join(bodyJ.OptionResponse, ", ") + if sortNum < 0 { + return selectionsI > selectionsJ + } + return selectionsI < selectionsJ + } + if sortNum < 0 { + return bodyI.Body.String > bodyJ.Body.String + } + return bodyI.Body.String < bodyJ.Body.String + }) + + return respondentDetails, nil +} From 75d79a60327e786d18f65e05d7ab6c90abad67e8 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 15:54:30 +0900 Subject: [PATCH 55/83] fix: add isAnonymous to tests --- model/respondents_test.go | 22 +++++++++++----------- model/responses_test.go | 4 ++-- model/scale_labels_test.go | 10 +++++----- model/targets_test.go | 2 +- model/validations_test.go | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/model/respondents_test.go b/model/respondents_test.go index cb38c8e9..b170d24f 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", false) 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", false) 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", false) 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", false) 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", false) 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", false) 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", false) require.NoError(t, err) questionnaire := Questionnaires{} @@ -867,7 +867,7 @@ func TestGetRespondentDetails(t *testing.T) { } for _, testCase := range testCases { - respondentDetails, err := respondentImpl.GetRespondentDetails(ctx, testCase.args.questionnaireID, testCase.args.sort) + respondentDetails, err := respondentImpl.GetRespondentDetails(ctx, testCase.args.questionnaireID, testCase.args.sort, false, "") if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") } else if testCase.expect.err != nil { @@ -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", false) require.NoError(t, err) questionnaireIDs = append(questionnaireIDs, questionnaireID) } @@ -996,7 +996,7 @@ func TestGetMyResponseIDs(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", false) require.NoError(t, err) respondents := []Respondents{ @@ -1091,7 +1091,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", false) 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..aa753311 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", false) 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", false) 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..52470a13 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", false) 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", false) 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", false) 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", false) 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", false) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) diff --git a/model/targets_test.go b/model/targets_test.go index 71404545..fe402677 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -318,7 +318,7 @@ func TestIsTargetingMe(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", false) require.NoError(t, err) err = targetImpl.InsertTargets(ctx, questionnaireID, []string{userOne}) diff --git a/model/validations_test.go b/model/validations_test.go index a56434a3..6e6e3fca 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", false) 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", false) 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", false) 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", false) require.NoError(t, err) err = administratorImpl.InsertAdministrators(ctx, questionnaireID, []string{userOne}) From 4af03eb436043395678f06cc21f8d472046e460f Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 16:06:31 +0900 Subject: [PATCH 56/83] fix: add db migration --- controller/adapter.go | 6 +++--- controller/questionnaire.go | 4 ++-- model/v3.go | 41 +++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 model/v3.go diff --git a/controller/adapter.go b/controller/adapter.go index 4284a385..ad496372 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -152,9 +152,9 @@ func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, admi Admins: createUsersAndGroups(adminUsers, adminGroups), CreatedAt: questionnaires.CreatedAt, Description: questionnaires.Description, - // IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, - // IsAnonymous: questionnaires.IsAnonymous, - // IsPublished: questionnaires.IsPublished, + IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, + IsAnonymous: questionnaires.IsAnonymous, + IsPublished: questionnaires.IsPublished, ModifiedAt: questionnaires.ModifiedAt, QuestionnaireId: questionnaires.ID, Questions: convertQuestions(questionnaires.Questions), diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 1bfb71f1..3a8b10a8 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -111,7 +111,7 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o questionnaireID := 0 err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy)) + questionnaireID, err := q.InsertQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, convertResponseViewableBy(params.ResponseViewableBy), params.IsAnonymous) if err != nil { c.Logger().Errorf("failed to insert questionnaire: %+v", err) return err @@ -302,7 +302,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa responseDueDateTime.Time = *params.ResponseDueDateTime } err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID) + err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID, params.IsAnonymous) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to update questionnaire: %+v", err) return err diff --git a/model/v3.go b/model/v3.go new file mode 100644 index 00000000..76fc2262 --- /dev/null +++ b/model/v3.go @@ -0,0 +1,41 @@ +package model + +import ( + "time" + + "github.com/go-gormigrate/gormigrate/v2" + "gopkg.in/guregu/null.v4" + "gorm.io/gorm" +) + +func v3() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "3", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&v3Questionnaires{}); err != nil { + return err + } + return nil + }, + } +} + +type v3Questionnaires 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"` + IsAnonymous bool `json:"is_anonymous" gorm:"type:boolean;not null;default:false"` + 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"` +} + +func (*v3Questionnaires) TableName() string { + return "questionnaires" +} From ce7cee4b7a44f7d6b1b70cc2b257a7a31211315c Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 19:11:49 +0900 Subject: [PATCH 57/83] impl: GetQuestionnaireMyRemindStatus --- controller/questionnaire.go | 11 ++++++++--- controller/reminder.go | 11 +++++++++++ model/v3.go | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index b9ab839c..b908c948 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -521,11 +521,16 @@ func (q Questionnaire) DeleteQuestionnaire(c echo.Context, questionnaireID int) } func (q Questionnaire) GetQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int) (bool, error) { - // todo: check remind status - return false, nil + status, err := Jq.CheckRemindStatus(questionnaireID) + if err != nil { + c.Logger().Errorf("failed to check remind status: %+v", err) + return false, echo.NewHTTPError(http.StatusInternalServerError, "failed to check remind status") + } + + return status, nil } -func (q Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int) error { +func (q Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int, isRemindEnabled bool) error { // todo: edit remind status return nil } diff --git a/controller/reminder.go b/controller/reminder.go index d520b748..3da0139f 100644 --- a/controller/reminder.go +++ b/controller/reminder.go @@ -84,6 +84,17 @@ func (jq *JobQueue) DeleteReminder(questionnaireID int) error { return nil } +func (jq *JobQueue) CheckRemindStatus(questionnaireID int) (bool, error) { + jq.mu.Lock() + defer jq.mu.Unlock() + for _, job := range jq.jobs { + if job.QuestionnaireID == questionnaireID { + return true, nil + } + } + return false, nil +} + func reminderAction(questionnaireID int, leftTimeText string) error { ctx := context.Background() q := model.Questionnaire{} diff --git a/model/v3.go b/model/v3.go index 5aa35ba4..b01ddd33 100644 --- a/model/v3.go +++ b/model/v3.go @@ -9,7 +9,7 @@ func v3() *gormigrate.Migration { return &gormigrate.Migration{ ID: "3", Migrate: func(tx *gorm.DB) error { - if err := tx.AutoMigrate(&Targets{}); err != nil { + if err := tx.AutoMigrate(&v3Targets{}); err != nil { return err } return nil From 0a92641600f3136f54b6f02bdd1679c651f4c093 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 19:18:17 +0900 Subject: [PATCH 58/83] impl: add EditQuestionnaireRemindStatus --- controller/questionnaire.go | 33 ++++++++++++++++++++++++++++++++- handler/questionnaire.go | 12 +++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index b908c948..f60d0043 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -531,7 +531,38 @@ func (q Questionnaire) GetQuestionnaireMyRemindStatus(c echo.Context, questionna } func (q Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int, isRemindEnabled bool) error { - // todo: edit remind status + if isRemindEnabled { + status, err := Jq.CheckRemindStatus(questionnaireID) + if err != nil { + c.Logger().Errorf("failed to check remind status: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to check remind status") + } + if status { + return nil + } + + questionnaire, _, _, _, _, _, err := q.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) + if err != nil { + if errors.Is(err, model.ErrRecordNotFound) { + c.Logger().Info("questionnaire not found") + return echo.NewHTTPError(http.StatusNotFound, "questionnaire not found") + } + c.Logger().Errorf("failed to get questionnaire: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get questionnaire") + } + + err = Jq.PushReminder(questionnaireID, &questionnaire.ResTimeLimit.Time) + if err != nil { + c.Logger().Errorf("failed to push reminder: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to push reminder") + } + } else { + err := Jq.DeleteReminder(questionnaireID) + if err != nil { + c.Logger().Errorf("failed to delete reminder: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to delete reminder") + } + } return nil } diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 7e7c0741..d30e9bb7 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -118,7 +118,7 @@ func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireI status, err := q.GetQuestionnaireMyRemindStatus(ctx, questionnaireID) if err != nil { ctx.Logger().Errorf("failed to get questionnaire my remind status: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire my remind status: %w", err)) + return err } res.IsRemindEnabled = status @@ -127,11 +127,17 @@ func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireI // (PATCH /questionnaires/{questionnaireID}/myRemindStatus) func (h Handler) EditQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + params := openapi.EditQuestionnaireMyRemindStatusJSONRequestBody{} + if err := ctx.Bind(¶ms); err != nil { + ctx.Logger().Errorf("failed to bind request body: %+v", err) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) + } + q := controller.NewQuestionnaire() - err := q.EditQuestionnaireMyRemindStatus(ctx, questionnaireID) + err := q.EditQuestionnaireMyRemindStatus(ctx, questionnaireID, params.IsRemindEnabled) if err != nil { ctx.Logger().Errorf("failed to edit questionnaire my remind status: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to edit questionnaire my remind status: %w", err)) + return err } return ctx.NoContent(200) } From 19635584e0f3395a9db5db08710e4d633693139f Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Fri, 8 Nov 2024 09:34:31 +0900 Subject: [PATCH 59/83] synchronize openapi schema --- openapi/server.go | 24 +------- openapi/spec.go | 140 +++++++++++++++++++++++----------------------- openapi/types.go | 21 ++----- 3 files changed, 75 insertions(+), 110 deletions(-) diff --git a/openapi/server.go b/openapi/server.go index 177c413d..323878be 100644 --- a/openapi/server.go +++ b/openapi/server.go @@ -1,6 +1,6 @@ // Package openapi provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. package openapi import ( @@ -41,9 +41,6 @@ type ServerInterface interface { // (POST /questionnaires/{questionnaireID}/responses) PostQuestionnaireResponse(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error - // (GET /questionnaires/{questionnaireID}/result) - GetQuestionnaireResult(ctx echo.Context, questionnaireID QuestionnaireIDInPath) error - // (GET /responses/myResponses) GetMyResponses(ctx echo.Context, params GetMyResponsesParams) error @@ -263,24 +260,6 @@ func (w *ServerInterfaceWrapper) PostQuestionnaireResponse(ctx echo.Context) err return err } -// GetQuestionnaireResult converts echo context to params. -func (w *ServerInterfaceWrapper) GetQuestionnaireResult(ctx echo.Context) error { - var err error - // ------------- Path parameter "questionnaireID" ------------- - var questionnaireID QuestionnaireIDInPath - - err = runtime.BindStyledParameterWithOptions("simple", "questionnaireID", ctx.Param("questionnaireID"), &questionnaireID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter questionnaireID: %s", err)) - } - - ctx.Set(ApplicationScopes, []string{"read", "write"}) - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetQuestionnaireResult(ctx, questionnaireID) - return err -} - // GetMyResponses converts echo context to params. func (w *ServerInterfaceWrapper) GetMyResponses(ctx echo.Context) error { var err error @@ -392,7 +371,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PATCH(baseURL+"/questionnaires/:questionnaireID/myRemindStatus", wrapper.EditQuestionnaireMyRemindStatus) router.GET(baseURL+"/questionnaires/:questionnaireID/responses", wrapper.GetQuestionnaireResponses) router.POST(baseURL+"/questionnaires/:questionnaireID/responses", wrapper.PostQuestionnaireResponse) - router.GET(baseURL+"/questionnaires/:questionnaireID/result", wrapper.GetQuestionnaireResult) router.GET(baseURL+"/responses/myResponses", wrapper.GetMyResponses) router.DELETE(baseURL+"/responses/:responseID", wrapper.DeleteResponse) router.GET(baseURL+"/responses/:responseID", wrapper.GetResponse) diff --git a/openapi/spec.go b/openapi/spec.go index 6b165df9..a0e26554 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -1,6 +1,6 @@ // Package openapi provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. package openapi import ( @@ -18,76 +18,74 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index b0ca79bc..0ba3e351 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -1,6 +1,6 @@ // Package openapi provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. package openapi import ( @@ -496,12 +496,13 @@ type ResShareType string // Response defines model for Response. type Response struct { Body []ResponseBody `json:"body"` + IsAnonymous *bool `json:"is_anonymous,omitempty"` IsDraft bool `json:"is_draft"` ModifiedAt time.Time `json:"modified_at"` QuestionnaireId int `json:"questionnaire_id"` // Respondent traQ ID - Respondent TraqId `json:"respondent"` + Respondent *TraqId `json:"respondent,omitempty"` ResponseId int `json:"response_id"` SubmittedAt time.Time `json:"submitted_at"` } @@ -586,13 +587,14 @@ type ResponseSortType string // ResponseWithQuestionnaireInfoItem defines model for ResponseWithQuestionnaireInfoItem. type ResponseWithQuestionnaireInfoItem struct { Body []ResponseBody `json:"body"` + IsAnonymous *bool `json:"is_anonymous,omitempty"` IsDraft bool `json:"is_draft"` ModifiedAt time.Time `json:"modified_at"` QuestionnaireId int `json:"questionnaire_id"` QuestionnaireInfo *QuestionnaireInfo `json:"questionnaire_info,omitempty"` // Respondent traQ ID - Respondent TraqId `json:"respondent"` + Respondent *TraqId `json:"respondent,omitempty"` ResponseId int `json:"response_id"` SubmittedAt time.Time `json:"submitted_at"` } @@ -603,19 +605,6 @@ type Responses = []Response // ResponsesWithQuestionnaireInfo defines model for ResponsesWithQuestionnaireInfo. type ResponsesWithQuestionnaireInfo = []ResponseWithQuestionnaireInfoItem -// Result defines model for Result. -type Result = []ResultItem - -// ResultItem defines model for ResultItem. -type ResultItem struct { - Body []ResponseBody `json:"body"` - IsDraft bool `json:"is_draft"` - ModifiedAt time.Time `json:"modified_at"` - QuestionnaireId int `json:"questionnaire_id"` - ResponseId int `json:"response_id"` - SubmittedAt time.Time `json:"submitted_at"` -} - // SortType question、questionnaire用のソートの種類 type SortType string From 1cc27440274634bd63db493ae960926bd60eda42 Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Fri, 8 Nov 2024 09:43:38 +0900 Subject: [PATCH 60/83] delete result endpoint --- controller/questionnaire.go | 44 +++++++------------------------------ 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 622a658f..f36d2bfc 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -214,12 +214,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") } - err = q.IScaleLabel.InsertScaleLabel(c.Request().Context(), question.ID, + err = q.IScaleLabel.InsertScaleLabel(c.Request().Context(), question.ID, model.ScaleLabels{ - ScaleLabelLeft: *b.MinLabel, + ScaleLabelLeft: *b.MinLabel, ScaleLabelRight: *b.MaxLabel, - ScaleMax: b.MaxValue, - ScaleMin: b.MinValue, + ScaleMax: b.MaxValue, + ScaleMin: b.MinValue, }) if err != nil { c.Logger().Errorf("failed to insert scale label: %+v", err) @@ -397,12 +397,12 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa 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, + err = q.IScaleLabel.UpdateScaleLabel(c.Request().Context(), question.ID, model.ScaleLabels{ - ScaleLabelLeft: *b.MinLabel, + ScaleLabelLeft: *b.MinLabel, ScaleLabelRight: *b.MaxLabel, - ScaleMax: b.MaxValue, - ScaleMin: b.MinValue, + ScaleMax: b.MaxValue, + ScaleMin: b.MinValue, }) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to insert scale label: %+v", err) @@ -705,31 +705,3 @@ https://anke-to.trap.jp/responses/new/%d`, questionnaireID, ) } - -func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID int, userID string) (openapi.Result, error) { - res := openapi.Result{} - - params := openapi.GetQuestionnaireResponsesParams{} - responses, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) - if err != nil { - if errors.Is(echo.ErrNotFound, err) { - return openapi.Result{}, err - } - ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) - return openapi.Result{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %w", err)) - } - - for _, response := range responses { - tmp := openapi.ResultItem{ - Body: response.Body, - IsDraft: response.IsDraft, - ModifiedAt: response.ModifiedAt, - QuestionnaireId: response.QuestionnaireId, - ResponseId: response.ResponseId, - SubmittedAt: response.SubmittedAt, - } - res = append(res, tmp) - } - - return res, nil -} From 1018409c403c2dd4f76df761dd38e5aec84495a5 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Fri, 8 Nov 2024 02:17:41 +0000 Subject: [PATCH 61/83] fix: delete function in handler for result --- handler/questionnaire.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 7e7c0741..15f7bcf7 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -188,25 +188,3 @@ func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID ope return ctx.JSON(201, res) } - -// (GET /questionnaires/{questionnaireID}/result) -func (h Handler) GetQuestionnaireResult(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { - res := openapi.Result{} - userID, err := getUserID(ctx) - if err != nil { - ctx.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - q := controller.NewQuestionnaire() - res, err = q.GetQuestionnaireResult(ctx, questionnaireID, userID) - if err != nil { - if errors.Is(err, echo.ErrNotFound) { - return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("questionnaire result not found: %w", err)) - } - ctx.Logger().Errorf("failed to get questionnaire result: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire result: %w", err)) - } - - return ctx.JSON(200, res) -} From 9a438ca78f7af25f4e426335555e523b1ca0d108 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Fri, 8 Nov 2024 02:28:05 +0000 Subject: [PATCH 62/83] fix: update usage of Response.Respondent to handle its type change to nullable --- controller/adapter.go | 2 +- controller/questionnaire.go | 2 +- controller/response.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/adapter.go b/controller/adapter.go index 4284a385..44794d72 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -269,7 +269,7 @@ func respondentDetail2Response(ctx echo.Context, respondentDetail model.Responde IsDraft: respondentDetail.SubmittedAt.Valid, ModifiedAt: respondentDetail.ModifiedAt, QuestionnaireId: respondentDetail.QuestionnaireID, - Respondent: respondentDetail.TraqID, + Respondent: &respondentDetail.TraqID, ResponseId: respondentDetail.ResponseID, SubmittedAt: respondentDetail.SubmittedAt.Time, } diff --git a/controller/questionnaire.go b/controller/questionnaire.go index f36d2bfc..2341e42b 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -659,7 +659,7 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID res = openapi.Response{ QuestionnaireId: questionnaireID, ResponseId: resopnseID, - Respondent: userID, + Respondent: &userID, SubmittedAt: submittedAt, ModifiedAt: modifiedAt, IsDraft: params.IsDraft, diff --git a/controller/response.go b/controller/response.go index e344f968..346f548e 100644 --- a/controller/response.go +++ b/controller/response.go @@ -76,7 +76,7 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses ModifiedAt: response.ModifiedAt, QuestionnaireId: response.QuestionnaireId, QuestionnaireInfo: &questionnaireInfo, - Respondent: userID, + Respondent: &userID, ResponseId: response.ResponseId, SubmittedAt: response.SubmittedAt, } From f39b8aef97a30157ad791f3d027a88a24c9cec54 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Sat, 9 Nov 2024 09:07:29 +0900 Subject: [PATCH 63/83] fix: remove GetAnonymousRespondantDetails --- model/respondents.go | 1 - model/respondents_impl.go | 190 +++----------------------------------- 2 files changed, 12 insertions(+), 179 deletions(-) diff --git a/model/respondents.go b/model/respondents.go index 5152a5d8..e5cff356 100644 --- a/model/respondents.go +++ b/model/respondents.go @@ -17,7 +17,6 @@ type IRespondent interface { GetRespondentInfos(ctx context.Context, userID string, questionnaireIDs ...int) ([]RespondentInfo, error) GetRespondentDetail(ctx context.Context, responseID int) (RespondentDetail, error) GetRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) - GetAnonymousRespondentDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]RespondentDetail, error) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) GetMyResponseIDs(ctx context.Context, sort string, userID string) ([]int, error) CheckRespondent(ctx context.Context, userID string, questionnaireID int) (bool, error) diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 9a6de459..70c500e9 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -65,15 +65,6 @@ type RespondentDetail struct { Responses []ResponseBody `json:"body"` } -// AnonymousRespondantDetail 匿名の回答の詳細情報の構造体 -type AnonymousRenspondantDetail struct { - ResponseID int `json:"responseID,omitempty"` - QuestionnaireID int `json:"questionnaireID,omitempty"` - SubmittedAt null.Time `json:"submitted_at,omitempty"` - ModifiedAt time.Time `json:"modified_at,omitempty"` - Responses []ResponseBody `json:"body"` -} - // InsertRespondent 回答の追加 func (*Respondent) InsertRespondent(ctx context.Context, userID string, questionnaireID int, submittedAt null.Time) (int, error) { db, err := getTx(ctx) @@ -300,16 +291,25 @@ func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int responseIDs = append(responseIDs, respondent.ResponseID) } + isAnonymous, err := NewQuestionnaire().GetResponseIsAnonymousByQuestionnaireID(ctx, questionnaireID) + respondentDetails := make([]RespondentDetail, 0, len(respondents)) respondentDetailMap := make(map[int]*RespondentDetail, len(respondents)) for i, respondent := range respondents { - respondentDetails = append(respondentDetails, RespondentDetail{ + r := RespondentDetail{ ResponseID: respondent.ResponseID, - TraqID: respondent.UserTraqid, QuestionnaireID: questionnaireID, SubmittedAt: respondent.SubmittedAt, ModifiedAt: respondent.ModifiedAt, - }) + } + + if !isAnonymous { + r.TraqID = respondent.UserTraqid + } else { + r.TraqID = "" + } + + respondentDetails = append(respondentDetails, r) respondentDetailMap[respondent.ResponseID] = &respondentDetails[i] } @@ -375,115 +375,6 @@ func (*Respondent) GetRespondentDetails(ctx context.Context, questionnaireID int return respondentDetails, nil } -// GetAnonymousRespondantDetails アンケートの回答の匿名詳細情報一覧の取得 -func (*Respondent) GetAnonymousRespondantDetails(ctx context.Context, questionnaireID int, sort string, onlyMyResponse bool, userID string) ([]AnonymousRenspondantDetail, error) { - db, err := getTx(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get tx: %w", err) - } - - respondents := []Respondents{} - - // Note: respondents.submitted_at IS NOT NULLで一時保存の回答を除外している - query := db. - Session(&gorm.Session{}). - Where("respondants.questionnaire_id = ? AND respondents.submitted_at IS NOT NULL", questionnaireID). - Select("ResponseID", "UserTraqid", "ModifiedAt", "SubmittedAt") - - query, sortNum, err := setRespondentsOrder(query, sort) - if err != nil { - return nil, fmt.Errorf("failed to set order: %w", err) - } - - err = query. - Find(&respondents).Error - if err != nil { - return nil, fmt.Errorf("failed to get respondants: %w", err) - } - - if len(respondents) == 0 { - return []AnonymousRenspondantDetail{}, nil - } - - responseIDs := make([]int, 0, len(respondents)) - for _, respondent := range respondents { - responseIDs = append(responseIDs, respondent.ResponseID) - } - - respondantDetails := make([]AnonymousRenspondantDetail, 0, len(respondents)) - respondantDetailMap := make(map[int]*AnonymousRenspondantDetail, len(respondents)) - for i, respondent := range respondents { - respondantDetails = append(respondantDetails, AnonymousRenspondantDetail{ - ResponseID: respondent.ResponseID, - QuestionnaireID: questionnaireID, - SubmittedAt: respondent.SubmittedAt, - ModifiedAt: respondent.ModifiedAt, - }) - - respondantDetailMap[respondent.ResponseID] = &respondantDetails[i] - } - - questions := []Questions{} - query = db. - Preload("Responses", func(db *gorm.DB) *gorm.DB { - return db. - Select("ResponseID", "QuestionID", "Body"). - Where("response_id IN (?)", responseIDs) - }). - Where("questionnaire_id = ?", questionnaireID). - Order("question_num"). - Select("ID", "Type") - if onlyMyResponse { - query = query.Where("user_traqid = ?", userID) - } - err = query. - Find(&questions).Error - if err != nil { - return nil, fmt.Errorf("failed to get questions: &w", err) - } - - for _, question := range questions { - responseBodyMap := make(map[int][]string, len(respondents)) - for _, response := range question.Responses { - if response.Body.Valid { - responseBodyMap[response.ResponseID] = append(responseBodyMap[response.ResponseID], response.Body.String) - } - } - - for i := range respondantDetails { - responseBodies := responseBodyMap[respondantDetails[i].ResponseID] - responseBody := ResponseBody{ - QuestionID: question.ID, - QuestionType: question.Type, - } - - switch responseBody.QuestionType { - case "MultipleChoice", "Checkbox", "Dropdown": - if responseBodies == nil { - responseBody.OptionResponse = []string{} - } else { - responseBody.OptionResponse = responseBodies - } - default: - if len(responseBodies) == 0 { - responseBody.Body = null.NewString("", false) - } else { - responseBody.Body = null.NewString(responseBodies[0], true) - } - } - - respondantDetails[i].Responses = append(respondantDetails[i].Responses, responseBody) - } - } - - respondantDetails, err = sortAnonymousRespondantDetail(sortNum, len(questions), respondantDetails) - if err != nil { - return nil, fmt.Errorf("failed to sort RespondantDetails: %w", err) - } - - return respondantDetails, nil -} - // GetRespondentsUserIDs 回答者のユーザーID取得 func (*Respondent) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs []int) ([]Respondents, error) { db, err := getTx(ctx) @@ -625,60 +516,3 @@ func sortRespondentDetail(sortNum int, questionNum int, respondentDetails []Resp return respondentDetails, nil } - -func sortAnonymousRespondantDetail(sortNum int, questionNum int, respondentDetails []AnonymousRenspondantDetail) ([]AnonymousRenspondantDetail, error) { - if sortNum == 0 { - return respondentDetails, nil - } - sortNumAbs := int(math.Abs(float64(sortNum))) - if sortNumAbs > questionNum { - return nil, fmt.Errorf("sort param is too large: %d", sortNum) - } - - sort.Slice(respondentDetails, func(i, j int) bool { - bodyI := respondentDetails[i].Responses[sortNumAbs-1] - bodyJ := respondentDetails[i].Responses[sortNumAbs-1] - if bodyI.QuestionType == "Number" { - numi, err := strconv.ParseFloat(bodyI.Body.String, 64) - if err != nil { - return true - } - numj, err := strconv.ParseFloat(bodyJ.Body.String, 64) - if err != nil { - return true - } - if sortNum < 0 { - return numi > numj - } - return numi < numj - } - if bodyI.QuestionType == "MultipleChoice" { - choiceI := "" - if len(bodyI.OptionResponse) > 0 { - choiceI = bodyI.OptionResponse[0] - } - choiceJ := "" - if len(bodyJ.OptionResponse) > 0 { - choiceJ = bodyJ.OptionResponse[0] - } - if sortNum < 0 { - return choiceI > choiceJ - } - return choiceI < choiceJ - } - if bodyI.QuestionType == "Checkbox" { - selectionsI := strings.Join(bodyI.OptionResponse, ", ") - selectionsJ := strings.Join(bodyJ.OptionResponse, ", ") - if sortNum < 0 { - return selectionsI > selectionsJ - } - return selectionsI < selectionsJ - } - if sortNum < 0 { - return bodyI.Body.String > bodyJ.Body.String - } - return bodyI.Body.String < bodyJ.Body.String - }) - - return respondentDetails, nil -} From 4c36179b13e88555db45e14ea7ce1a6a0f6e62e9 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Sat, 9 Nov 2024 09:38:28 +0900 Subject: [PATCH 64/83] fix: add is_anonymous to responsebody --- controller/adapter.go | 9 ++++++++- controller/questionnaire.go | 3 +++ controller/response.go | 5 +++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/controller/adapter.go b/controller/adapter.go index 23128efe..4a4a2633 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -152,7 +152,7 @@ func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, admi Admins: createUsersAndGroups(adminUsers, adminGroups), CreatedAt: questionnaires.CreatedAt, Description: questionnaires.Description, - IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, + // IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, IsAnonymous: questionnaires.IsAnonymous, IsPublished: questionnaires.IsPublished, ModifiedAt: questionnaires.ModifiedAt, @@ -264,6 +264,12 @@ func respondentDetail2Response(ctx echo.Context, respondentDetail model.Responde oResponseBodies = append(oResponseBodies, oResponseBody) } + isAnonymous, err := model.NewQuestionnaire().GetResponseIsAnonymousByQuestionnaireID(ctx.Request().Context(), respondentDetail.QuestionnaireID) + if err != nil { + ctx.Logger().Errorf("failed to get response is anonymous: %+v", err) + return openapi.Response{}, err + } + res := openapi.Response{ Body: oResponseBodies, IsDraft: respondentDetail.SubmittedAt.Valid, @@ -272,6 +278,7 @@ func respondentDetail2Response(ctx echo.Context, respondentDetail model.Responde Respondent: &(respondentDetail.TraqID), ResponseId: respondentDetail.ResponseID, SubmittedAt: respondentDetail.SubmittedAt.Time, + IsAnonymous: &isAnonymous, } return res, nil diff --git a/controller/questionnaire.go b/controller/questionnaire.go index c907fb19..8d45d139 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -656,6 +656,8 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID } } + isAnonymous, err := q.GetResponseIsAnonymousByQuestionnaireID(c.Request().Context(), questionnaireID) + res = openapi.Response{ QuestionnaireId: questionnaireID, ResponseId: resopnseID, @@ -663,6 +665,7 @@ func (q Questionnaire) PostQuestionnaireResponse(c echo.Context, questionnaireID SubmittedAt: submittedAt, ModifiedAt: modifiedAt, IsDraft: params.IsDraft, + IsAnonymous: &isAnonymous, Body: params.Body, } diff --git a/controller/response.go b/controller/response.go index 346f548e..425b874c 100644 --- a/controller/response.go +++ b/controller/response.go @@ -79,6 +79,7 @@ func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponses Respondent: &userID, ResponseId: response.ResponseId, SubmittedAt: response.SubmittedAt, + IsAnonymous: response.IsAnonymous, } res = append(res, tmp) } @@ -244,6 +245,6 @@ func (r Response) EditResponse(ctx echo.Context, responseID openapi.ResponseIDIn return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to insert responses: %w", err)) } } - + return nil -} \ No newline at end of file +} From 005db480b35c4866e51d758a38d315a63115b8d2 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:16:59 +0000 Subject: [PATCH 65/83] fix: prevent changing anonymous questionnaires to non-anonymous --- controller/questionnaire.go | 13 +++- docs/swagger/swagger.yaml | 4 +- handler/questionnaire.go | 2 +- openapi/spec.go | 137 ++++++++++++++++++------------------ 4 files changed, 85 insertions(+), 71 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 8d45d139..867f550d 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -296,12 +296,23 @@ func (q Questionnaire) GetQuestionnaire(ctx echo.Context, questionnaireID int) ( } func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, params openapi.EditQuestionnaireJSONRequestBody) error { + // unable to change the questionnaire from anoymous to non-anonymous + isAnonymous, err := q.GetResponseIsAnonymousByQuestionnaireID(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to get anonymous info: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to get anonymous info") + } + if isAnonymous && !params.IsAnonymous { + c.Logger().Info("unable to change the questionnaire from anoymous to non-anonymous") + return echo.NewHTTPError(http.StatusMethodNotAllowed, "unable to change the questionnaire from anoymous to non-anonymous") + } + responseDueDateTime := null.Time{} if params.ResponseDueDateTime != nil { responseDueDateTime.Valid = true responseDueDateTime.Time = *params.ResponseDueDateTime } - err := q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { + err = q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { err := q.UpdateQuestionnaire(ctx, params.Title, params.Description, responseDueDateTime, string(params.ResponseViewableBy), questionnaireID, params.IsPublished, params.IsAnonymous) if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { c.Logger().Errorf("failed to update questionnaire: %+v", err) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 919406ed..650f48b2 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -91,7 +91,7 @@ paths: # TODO 変数の命名を確認する operationId: editQuestionnaire tags: - questionnaire - description: アンケートの情報を変更します。 + description: アンケートの情報を変更します。匿名のアンケートを非匿名アンケートに変更することができません。 parameters: - $ref: "#/components/parameters/questionnaireIDInPath" requestBody: @@ -105,6 +105,8 @@ paths: # TODO 変数の命名を確認する description: 正常にアンケートを変更できました。 "400": description: アンケートのIDが無効です + "405": + description: 匿名のアンケートを非匿名アンケートに変更することができません "500": description: 正常にアンケートを変更できませんでした delete: diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 15f7bcf7..9f64f4c2 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -93,7 +93,7 @@ func (h Handler) EditQuestionnaire(ctx echo.Context, questionnaireID openapi.Que err := q.EditQuestionnaire(ctx, questionnaireID, params) if err != nil { ctx.Logger().Errorf("failed to edit questionnaire: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to edit questionnaire: %w", err)) + return err } return ctx.NoContent(200) diff --git a/openapi/spec.go b/openapi/spec.go index a0e26554..0e31e47a 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -18,74 +18,75 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file From 4ae896132e0cd4c10628b9ce454f8f5c6f5705cd Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Mon, 11 Nov 2024 07:43:30 +0000 Subject: [PATCH 66/83] fix: move questoinnaire_id from QuestionBase to Question as it is not required in NewQuestion --- docs/swagger/swagger.yaml | 8 +-- openapi/spec.go | 106 +++++++++++++++++++------------------- openapi/types.go | 24 ++------- 3 files changed, 62 insertions(+), 76 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 919406ed..d3658844 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -667,6 +667,9 @@ components: - $ref: "#/components/schemas/QuestionBase" - $ref: "#/components/schemas/QuestionSettingsByType" - properties: + questionnaire_id: + type: integer + example: 1 question_id: type: integer example: 1 @@ -675,14 +678,12 @@ components: format: date-time example: 2020-01-01T00:00:00+09:00 required: + - questionnaire_id - question_id - created_at QuestionBase: type: object properties: - questionnaire_id: - type: integer - example: 1 title: type: string description: @@ -692,7 +693,6 @@ components: description: | 回答必須かどうか required: - - questionnaire_id - title - description - is_required diff --git a/openapi/spec.go b/openapi/spec.go index a0e26554..603ecd54 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -33,59 +33,59 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index 0ba3e351..ab4f9a46 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -140,10 +140,9 @@ type NewQuestion struct { Description string `json:"description"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - QuestionnaireId int `json:"questionnaire_id"` - Title string `json:"title"` - union json.RawMessage + IsRequired bool `json:"is_required"` + Title string `json:"title"` + union json.RawMessage } // NewQuestionnaire defines model for NewQuestionnaire. @@ -194,9 +193,8 @@ type QuestionBase struct { Description string `json:"description"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - QuestionnaireId int `json:"questionnaire_id"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` + Title string `json:"title"` } // QuestionSettingsByType defines model for QuestionSettingsByType. @@ -874,11 +872,6 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } - object["questionnaire_id"], err = json.Marshal(t.QuestionnaireId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'questionnaire_id': %w", err) - } - object["title"], err = json.Marshal(t.Title) if err != nil { return nil, fmt.Errorf("error marshaling 'title': %w", err) @@ -913,13 +906,6 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { } } - if raw, found := object["questionnaire_id"]; found { - err = json.Unmarshal(raw, &t.QuestionnaireId) - if err != nil { - return fmt.Errorf("error reading 'questionnaire_id': %w", err) - } - } - if raw, found := object["title"]; found { err = json.Unmarshal(raw, &t.Title) if err != nil { From a7d31256581f2b17c76ce20d715ecb3ae51b8d9b Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:00:25 +0000 Subject: [PATCH 67/83] fix: combine title and description in QuestionBase into body --- docs/swagger/swagger.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index d3658844..45daecbc 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -684,17 +684,14 @@ components: QuestionBase: type: object properties: - title: - type: string - description: + body: type: string is_required: type: boolean description: | 回答必須かどうか required: - - title - - description + - body - is_required QuestionSettingsByType: oneOf: From 1c73233a5bab12c4f781a9c75ad65c65f33eb1bb Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 07:54:17 +0000 Subject: [PATCH 68/83] fix: add discriminator for QuestionSettingsByType --- docs/swagger/swagger.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 45daecbc..94c22e8f 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -701,6 +701,8 @@ components: - $ref: "#/components/schemas/QuestionSettingsSingleChoice" - $ref: "#/components/schemas/QuestionSettingsMultipleChoice" - $ref: "#/components/schemas/QuestionSettingsScale" + discriminator: + propertyName: question_type QuestionSettingsText: allOf: - $ref: "#/components/schemas/QuestionTypeText" From 6eb3498a736bdb97ca61351c12e752f691b94fd5 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:06:24 +0000 Subject: [PATCH 69/83] fix: make question_id of Question to be nullable in order to support the addition of questions --- docs/swagger/swagger.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 94c22e8f..7d459f16 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -672,6 +672,8 @@ components: example: 1 question_id: type: integer + description: | + 質問を追加する場合はnull。 example: 1 created_at: type: string @@ -679,7 +681,6 @@ components: example: 2020-01-01T00:00:00+09:00 required: - questionnaire_id - - question_id - created_at QuestionBase: type: object From 85c4c8951c07e73a173a7ea0d1e01d24b39d34ea Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:07:38 +0000 Subject: [PATCH 70/83] fix: update oapi-codegen files --- openapi/spec.go | 137 ++++++++++++++++++++++++----------------------- openapi/types.go | 129 +++++++++++++++++++++++++------------------- 2 files changed, 144 insertions(+), 122 deletions(-) diff --git a/openapi/spec.go b/openapi/spec.go index 603ecd54..a9d9e9d7 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -18,74 +18,75 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "czPjM/8XAAD//50rif3mXwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index ab4f9a46..e204de8f 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -5,6 +5,7 @@ package openapi import ( "encoding/json" + "errors" "fmt" "time" @@ -137,11 +138,10 @@ type Groups = []string // NewQuestion defines model for NewQuestion. type NewQuestion struct { - Description string `json:"description"` + Body string `json:"body"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` union json.RawMessage } @@ -177,24 +177,24 @@ type NewResponse struct { // Question defines model for Question. type Question struct { - CreatedAt time.Time `json:"created_at"` - Description string `json:"description"` + Body string `json:"body"` + CreatedAt time.Time `json:"created_at"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - QuestionId int `json:"question_id"` - QuestionnaireId int `json:"questionnaire_id"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` + + // QuestionId 質問を追加する場合はnull。 + QuestionId *int `json:"question_id,omitempty"` + QuestionnaireId int `json:"questionnaire_id"` union json.RawMessage } // QuestionBase defines model for QuestionBase. type QuestionBase struct { - Description string `json:"description"` + Body string `json:"body"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` } // QuestionSettingsByType defines model for QuestionSettingsByType. @@ -862,9 +862,9 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { } } - object["description"], err = json.Marshal(t.Description) + object["body"], err = json.Marshal(t.Body) if err != nil { - return nil, fmt.Errorf("error marshaling 'description': %w", err) + return nil, fmt.Errorf("error marshaling 'body': %w", err) } object["is_required"], err = json.Marshal(t.IsRequired) @@ -872,11 +872,6 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } - object["title"], err = json.Marshal(t.Title) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title': %w", err) - } - b, err = json.Marshal(object) return b, err } @@ -892,10 +887,10 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { return err } - if raw, found := object["description"]; found { - err = json.Unmarshal(raw, &t.Description) + if raw, found := object["body"]; found { + err = json.Unmarshal(raw, &t.Body) if err != nil { - return fmt.Errorf("error reading 'description': %w", err) + return fmt.Errorf("error reading 'body': %w", err) } } @@ -906,13 +901,6 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { } } - if raw, found := object["title"]; found { - err = json.Unmarshal(raw, &t.Title) - if err != nil { - return fmt.Errorf("error reading 'title': %w", err) - } - } - return err } @@ -1085,14 +1073,14 @@ func (t Question) MarshalJSON() ([]byte, error) { } } - object["created_at"], err = json.Marshal(t.CreatedAt) + object["body"], err = json.Marshal(t.Body) if err != nil { - return nil, fmt.Errorf("error marshaling 'created_at': %w", err) + return nil, fmt.Errorf("error marshaling 'body': %w", err) } - object["description"], err = json.Marshal(t.Description) + object["created_at"], err = json.Marshal(t.CreatedAt) if err != nil { - return nil, fmt.Errorf("error marshaling 'description': %w", err) + return nil, fmt.Errorf("error marshaling 'created_at': %w", err) } object["is_required"], err = json.Marshal(t.IsRequired) @@ -1100,9 +1088,11 @@ func (t Question) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } - object["question_id"], err = json.Marshal(t.QuestionId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'question_id': %w", err) + if t.QuestionId != nil { + object["question_id"], err = json.Marshal(t.QuestionId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'question_id': %w", err) + } } object["questionnaire_id"], err = json.Marshal(t.QuestionnaireId) @@ -1110,11 +1100,6 @@ func (t Question) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'questionnaire_id': %w", err) } - object["title"], err = json.Marshal(t.Title) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title': %w", err) - } - b, err = json.Marshal(object) return b, err } @@ -1130,17 +1115,17 @@ func (t *Question) UnmarshalJSON(b []byte) error { return err } - if raw, found := object["created_at"]; found { - err = json.Unmarshal(raw, &t.CreatedAt) + if raw, found := object["body"]; found { + err = json.Unmarshal(raw, &t.Body) if err != nil { - return fmt.Errorf("error reading 'created_at': %w", err) + return fmt.Errorf("error reading 'body': %w", err) } } - if raw, found := object["description"]; found { - err = json.Unmarshal(raw, &t.Description) + if raw, found := object["created_at"]; found { + err = json.Unmarshal(raw, &t.CreatedAt) if err != nil { - return fmt.Errorf("error reading 'description': %w", err) + return fmt.Errorf("error reading 'created_at': %w", err) } } @@ -1165,13 +1150,6 @@ func (t *Question) UnmarshalJSON(b []byte) error { } } - if raw, found := object["title"]; found { - err = json.Unmarshal(raw, &t.Title) - if err != nil { - return fmt.Errorf("error reading 'title': %w", err) - } - } - return err } @@ -1184,6 +1162,7 @@ func (t QuestionSettingsByType) AsQuestionSettingsText() (QuestionSettingsText, // FromQuestionSettingsText overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsText func (t *QuestionSettingsByType) FromQuestionSettingsText(v QuestionSettingsText) error { + v.QuestionType = "QuestionSettingsText" b, err := json.Marshal(v) t.union = b return err @@ -1191,6 +1170,7 @@ func (t *QuestionSettingsByType) FromQuestionSettingsText(v QuestionSettingsText // MergeQuestionSettingsText performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsText func (t *QuestionSettingsByType) MergeQuestionSettingsText(v QuestionSettingsText) error { + v.QuestionType = "QuestionSettingsText" b, err := json.Marshal(v) if err != nil { return err @@ -1210,6 +1190,7 @@ func (t QuestionSettingsByType) AsQuestionSettingsTextLong() (QuestionSettingsTe // FromQuestionSettingsTextLong overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsTextLong func (t *QuestionSettingsByType) FromQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + v.QuestionType = "QuestionSettingsTextLong" b, err := json.Marshal(v) t.union = b return err @@ -1217,6 +1198,7 @@ func (t *QuestionSettingsByType) FromQuestionSettingsTextLong(v QuestionSettings // MergeQuestionSettingsTextLong performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsTextLong func (t *QuestionSettingsByType) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) error { + v.QuestionType = "QuestionSettingsTextLong" b, err := json.Marshal(v) if err != nil { return err @@ -1236,6 +1218,7 @@ func (t QuestionSettingsByType) AsQuestionSettingsNumber() (QuestionSettingsNumb // FromQuestionSettingsNumber overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsNumber func (t *QuestionSettingsByType) FromQuestionSettingsNumber(v QuestionSettingsNumber) error { + v.QuestionType = "QuestionSettingsNumber" b, err := json.Marshal(v) t.union = b return err @@ -1243,6 +1226,7 @@ func (t *QuestionSettingsByType) FromQuestionSettingsNumber(v QuestionSettingsNu // MergeQuestionSettingsNumber performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsNumber func (t *QuestionSettingsByType) MergeQuestionSettingsNumber(v QuestionSettingsNumber) error { + v.QuestionType = "QuestionSettingsNumber" b, err := json.Marshal(v) if err != nil { return err @@ -1262,6 +1246,7 @@ func (t QuestionSettingsByType) AsQuestionSettingsSingleChoice() (QuestionSettin // FromQuestionSettingsSingleChoice overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsSingleChoice func (t *QuestionSettingsByType) FromQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + v.QuestionType = "QuestionSettingsSingleChoice" b, err := json.Marshal(v) t.union = b return err @@ -1269,6 +1254,7 @@ func (t *QuestionSettingsByType) FromQuestionSettingsSingleChoice(v QuestionSett // MergeQuestionSettingsSingleChoice performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsSingleChoice func (t *QuestionSettingsByType) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { + v.QuestionType = "QuestionSettingsSingleChoice" b, err := json.Marshal(v) if err != nil { return err @@ -1288,6 +1274,7 @@ func (t QuestionSettingsByType) AsQuestionSettingsMultipleChoice() (QuestionSett // FromQuestionSettingsMultipleChoice overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsMultipleChoice func (t *QuestionSettingsByType) FromQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + v.QuestionType = "QuestionSettingsMultipleChoice" b, err := json.Marshal(v) t.union = b return err @@ -1295,6 +1282,7 @@ func (t *QuestionSettingsByType) FromQuestionSettingsMultipleChoice(v QuestionSe // MergeQuestionSettingsMultipleChoice performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsMultipleChoice func (t *QuestionSettingsByType) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { + v.QuestionType = "QuestionSettingsMultipleChoice" b, err := json.Marshal(v) if err != nil { return err @@ -1314,6 +1302,7 @@ func (t QuestionSettingsByType) AsQuestionSettingsScale() (QuestionSettingsScale // FromQuestionSettingsScale overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsScale func (t *QuestionSettingsByType) FromQuestionSettingsScale(v QuestionSettingsScale) error { + v.QuestionType = "QuestionSettingsScale" b, err := json.Marshal(v) t.union = b return err @@ -1321,6 +1310,7 @@ func (t *QuestionSettingsByType) FromQuestionSettingsScale(v QuestionSettingsSca // MergeQuestionSettingsScale performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsScale func (t *QuestionSettingsByType) MergeQuestionSettingsScale(v QuestionSettingsScale) error { + v.QuestionType = "QuestionSettingsScale" b, err := json.Marshal(v) if err != nil { return err @@ -1331,6 +1321,37 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsScale(v QuestionSettingsSc return err } +func (t QuestionSettingsByType) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"question_type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t QuestionSettingsByType) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "QuestionSettingsMultipleChoice": + return t.AsQuestionSettingsMultipleChoice() + case "QuestionSettingsNumber": + return t.AsQuestionSettingsNumber() + case "QuestionSettingsScale": + return t.AsQuestionSettingsScale() + case "QuestionSettingsSingleChoice": + return t.AsQuestionSettingsSingleChoice() + case "QuestionSettingsText": + return t.AsQuestionSettingsText() + case "QuestionSettingsTextLong": + return t.AsQuestionSettingsTextLong() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + func (t QuestionSettingsByType) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err From 47fe43d5acee57be0009dad1985acb2bfd156ff4 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:05:38 +0000 Subject: [PATCH 71/83] fix: add question operations in questionnaire handling --- controller/questionnaire.go | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 2341e42b..89b09db7 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -2,6 +2,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "net/http" @@ -156,6 +157,25 @@ func (q Questionnaire) PostQuestionnaire(c echo.Context, userID string, params o c.Logger().Errorf("failed to insert administrator groups: %+v", err) return err } + for questoinNum, question := range params.Questions { + b, err := question.MarshalJSON() + if err != nil { + c.Logger().Errorf("failed to marshal new question: %+v", err) + return err + } + var questionParsed map[string]interface{} + err = json.Unmarshal([]byte(b), &questionParsed) + if err != nil { + c.Logger().Errorf("failed to unmarshal new question: %+v", err) + return err + } + questionType := questionParsed["question_type"].(string) + _, err = q.InsertQuestion(ctx, questionnaireID, 1, questoinNum+1, questionType, question.Body, question.IsRequired) + if err != nil { + c.Logger().Errorf("failed to insert question: %+v", err) + return err + } + } message := createQuestionnaireMessage( questionnaireID, @@ -357,6 +377,33 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa c.Logger().Errorf("failed to insert administrator groups: %+v", err) return err } + for questoinNum, question := range params.Questions { + b, err := question.MarshalJSON() + if err != nil { + c.Logger().Errorf("failed to marshal new question: %+v", err) + return err + } + var questionParsed map[string]interface{} + err = json.Unmarshal([]byte(b), &questionParsed) + if err != nil { + c.Logger().Errorf("failed to unmarshal new question: %+v", err) + return err + } + questionType := questionParsed["question_type"].(string) + if question.QuestionId == nil { + _, err = q.InsertQuestion(ctx, questionnaireID, 1, questoinNum+1, questionType, question.Body, question.IsRequired) + if err != nil { + c.Logger().Errorf("failed to insert question: %+v", err) + return err + } + } else { + err = q.UpdateQuestion(ctx, questionnaireID, 1, questoinNum+1, questionType, question.Body, question.IsRequired, *question.QuestionId) + if err != nil { + c.Logger().Errorf("failed to update question: %+v", err) + return err + } + } + } return nil }) @@ -483,6 +530,19 @@ func (q Questionnaire) DeleteQuestionnaire(c echo.Context, questionnaireID int) return err } + questions, err := q.GetQuestions(c.Request().Context(), questionnaireID) + if err != nil { + c.Logger().Errorf("failed to get questions: %+v", err) + return err + } + for _, question := range questions { + err = q.DeleteQuestion(c.Request().Context(), question.ID) + if err != nil { + c.Logger().Errorf("failed to delete administrators: %+v", err) + return err + } + } + return nil }) if err != nil { From 8462d621802495f122a28726f88f3c2f4a104802 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:17:35 +0000 Subject: [PATCH 72/83] Revert "fix: add discriminator for QuestionSettingsByType" This reverts commit 1c73233a5bab12c4f781a9c75ad65c65f33eb1bb. --- docs/swagger/swagger.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 7d459f16..7a241024 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -702,8 +702,6 @@ components: - $ref: "#/components/schemas/QuestionSettingsSingleChoice" - $ref: "#/components/schemas/QuestionSettingsMultipleChoice" - $ref: "#/components/schemas/QuestionSettingsScale" - discriminator: - propertyName: question_type QuestionSettingsText: allOf: - $ref: "#/components/schemas/QuestionTypeText" From 1e969e8e7d231d042f2bd04b9c2defb33bcea810 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:17:49 +0000 Subject: [PATCH 73/83] Revert "fix: update oapi-codegen files" This reverts commit 85c4c8951c07e73a173a7ea0d1e01d24b39d34ea. --- openapi/spec.go | 137 +++++++++++++++++++++++------------------------ openapi/types.go | 129 +++++++++++++++++++------------------------- 2 files changed, 122 insertions(+), 144 deletions(-) diff --git a/openapi/spec.go b/openapi/spec.go index a9d9e9d7..603ecd54 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -18,75 +18,74 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "czPjM/8XAAD//50rif3mXwAA", + "H4sIAAAAAAAC/+xc628TyZb/V6ze/QDaDjaBK931t0BGV5GGu0DC7gcSWR27kvRcu9t0twEvipRq8wjE", + "QMQjTAhDCJOBAEPCDCyb4fm/bKUd+9P+C6uqflX1ux0bZkYrIRR316k6dc6vzjl16lRf4IpypSpLQNJU", + "Ln+BqwqKUAEaUMgvWSrXh0oVURJVTRE0UDpSPwZGpBM1oNTx+xJQi4pY1URZ4vJc+8pzY/4ygs3dzbXd", + "xcvtuUsIvkDwOYI/IvgEwYvk74tI1xHcJP8+GzeXjE/3Mvs0pQb285lQQn3BotJ1Y/EF0iF5vozgbwg+", + "sTuZEsoq2I/mdNS4ghp3kf4MNV6gxjyCW+QVmtPHJY7nRMzsGTIHnpOECuDywTPleE4tzoCKgOeq1au4", + "4aQsl4EgcbOzPCE6Vj8J1KosqfFy2TRWHu6+vBM4c0+bnfc/GetLfZ2ty3iSaY4JyjTQRGk6if6R/hk1", + "PiD9V9RoEI5ClBkkiKS0/RQNNdk42VSF6XCB7Hy8ixr3yXS2d1c2EVzI7Gs9eG5s3t/99Azr+tEbYxFz", + "dZButj+EMzxUEDuipIFpoBB2ztSAigeXBFEBI8Mj0nFBm/EzhvTHqPEa6b/gQRvzI8OuOKqYwBnT0x/H", + "cwo4UxMVUOLyWGEx7Cj20gjlxIR7OANuD92OPSorWriGtp8g+Lrz6HJm387HB635xda9n1rLOoLN1tIr", + "BO8heDEzzqm1yYqoaaBUELRxjs94mho31812A96GGMz6OkZe4wWCm63vr+ChxjlN1MogoEFn+brZYMBp", + "0Vp501p6FchWRS6JU6IzmKelyxXTLhMGL1VWNAZe/6yAKS7P/VPW9RBZ862aPUkJdwzLHktcBYJSnAmV", + "tVcY6w923zwOY4Z0FYR2VVNEadocb++aLSpASKBXttmfVquUNmdtGhIH/E2Ra1Xyl6iBiuqXNmmQOXXK", + "WsjgvFCplgGXP8h79eY8EBRFqOPffwfnTlhmBncslMv/NsXlT0ezalMcEVTAzfLJGo8CDVt19UjdnOUE", + "OzoxculZIGQOH1VFrgJFEwGRkm0/WdlF9UpLwyesWdoGnqZ6n7Dn4vj0vJeVSblUT8yF3c0RTBSgM1Et", + "lBRhSsP9OMo2jXKAk6R5dih5k6MJh0Ke/A4UNdz7V4KDT3vusmfmyQ3mBnMDuYMDuYNjuVye/PuX3L/m", + "czmO56ZkpYLbcyVBAwOaWME+27cGbM0VxBLTtbteHFfmceoJKEJQ4pCzw/P0NE0cMdL0AYlZ+Rf8cxPV", + "gjt8sL83Pl/qPJpHcAHBZwheRnCBmA0vdHjTpAZbf3qKZjOeGYtlJApmHhyQfQ9IATqbfgyc11KDDxN9", + "KxPJpSP8e60ySfCRjmxUlKbL4OiMLBbTr5RjtbImVrsmHy0KZcvsxvSceuFjzQVwxyJXrvptcYx78gDN", + "7sEPp6BJWSrqajKUetlJVITzhbNCuQaC4l6eq4hS+OvZRGybauqKa0vDgUyXhUlQDhR5gilFEMdMmFaf", + "25YeNJkymYXTnXC8S+/ropOYq64m4hi6ACUDadrc53WHPcccds2YY0wTMCdZiyyatwDbkg8L9Aqa5UKA", + "VKtgnXgIJ/gYV8Z2FOW2KCuRnB2LoMdsOBYjIRdm+14z4VmgSXmhyXrMkr3EErJCmveBBXtBpWCDkPSQ", + "FXeT1N3+aoxEeEkDDkIyTAWDqQjtzc9wDQwLGhjDMXxXHfy7CM4Jk2VwpJ6OfkQdkmSpXpFramrCclk+", + "J0rTttmxWUnd0fHaZFlUZ0ApHaGZOlWHpBLJpqusJSVNjprbjaGAldGvDZcHtvSGJw6zw+xuJ3Ir5DK8", + "+/PPB42Vh6ixhhqvUWMRNT50Vi7vfLiP4FOSvb6K9Nv/8/1lBP8b6dcRfLq7/K69tkGSQKsIXtx59w7p", + "t4yF1c7KZfLwE4LLmURkUEdwHRPoTdT48L8fYKw46FkkkIcmiOUuV/HIcDo0pdrOe8CViuyYlUwb0nqS", + "xAnP4Nh56ZJ94BbVzSkVKGpE1oftbMK/1EaGw81+r7IJsYgZkabkL2f192y8e4OgFJaWPm2ajZdmhIn3", + "6VpUC4LVulCxmhcUur03aT5HTEizvX6ldfeVsfLQPrJ8iuB1pC/4Ujapk39R/CSZvOMYAydLv/Uknpqf", + "jcXr9nT2PAtnoAQ8u640iOcq/TbymA7BpnHp587SAoJ3sXV3TkT3PB2XhwTTOQkqolT6RsKRTfCUFNKi", + "ANwmwQfFL4wt7NfwFBrPUeMhOTR5jRpXEWy2Hlw1rv1GT42c5z4hx6Rv8f+waWx9av+yRsoFnmK/p18j", + "7ddtLd+jyw6sw9Y5iOBtBDeNuXUEm1hANnETwVd+PjpzcOfzmiVxfcE+Oo6XqUcICQRLG4IgsWr2+0IF", + "7On0fc94YTiJndm3ohoQ61WFaVCoCOcDlurifHtjHodJ9ql46+6rsHOlsDx5emdNyEZrlYqg1GNTLQ73", + "vmFjxUF5Cp9QqEO9vkbA9DixDAe5VB/nthUvlGqggBkpaGIQTM212Xqw2llexCuaLEbreFO/1YE3EP63", + "ivSrjOvBCL6J/ydLUKqVy3T9BNMpbnrPXqc9lWAyMVG7vnApnbUaFSbrCc7iRmcEBTjn7LQiAzuM1aiN", + "8i8Txf//3v2PsXf3hKJfe9sllMsFe38TFBzZVV/YELiBQNOyBdvzCH52bEQGe7YM0m+1P9/BhNjw/ID0", + "plVnB7cypDCMaZHZx3T78nvjgbnTpqIJuOXpeX8Sh8pzM4JaqNTdM3RvGL7QWtkmZs8ZF0dJXERXCnXw", + "H2Rxk3TlSNs14IXJuhVtdOFlmFn6OeU9Kg7YwPrSST6LKjjPY3fSQ1LJKmTBttzsOS2h9+DZ6oW3+Yg1", + "vWP2iTY7Deeg25dDsvJGbOrHygyx+4NYfZiDBLHI+Jj4fcjm7n8tth4+QPotPtOBC8bSWwS32k8WCJc4", + "TM7sG7ckMs7t5zOeNUn2mL72VC5jnNufaT9/hX2/rvv6leqyBMY5a6lZSWtL/mxKhLca4zm7grWeBZRl", + "0NUz3TvG6G1prG3oWwxIyyYO92OKcGakxFF1lIlKVegCyL4GsDRXnmH5uOg2tgDLgQExSEwtVOLiEJoq", + "UWGIlyBRUQhNlLAghCZJVQxCE6YsBGHGdItA6MdHBBWMWEDyG3lJPWc+j8lOWg1DTBwzWNjRqW8s6pC4", + "y6FGTQzHDxUC9oRD9amGhvUFHbjduvawrT8mxxhmnuQKajSQvoX03xDc7Fy6bszfI7Y5bLbe8gbKgERu", + "ukPl4AVTjwpvkkKagpOPlZ4U06RhxF5Efk56X8DSFV8pITUu+afSg9KVNKxb6zeQjR4Vq+yVHad02xe8", + "2Z5y984GgpuqrGg4ftvY7Kw9pMInjwcd8Py2CywH7D9oF8szxej+qgGeOz+Axxk4KyiSUMHW4DQ3ag8w", + "pA2NHuV4+sHwN+QJCZWH3D+tx+6mccjzmzSgpfIfojbjO4sa0UAlucbcWICPOUmzjrmSR4uYIKj0KMLM", + "q6kruIOOIJ3eAuWTeohwKQeMHQ5UW5xoDjKSNYGL9I/u7sOLXupAn6dva/QFuU6uw4Sf83PPqLVibp9g", + "NEU4kfHerOCK1cFcUIxvHhon1aEb53sV5dn5+oKXaed5VP/uhrtm85X+xNsk5e0h/QuEXD4q1hRRq4/i", + "nqyAo1oti0XBrs6YKsvnzOc1bUZWxP8kb47KJeB7eEopc3luRtOqaj6bPXNAU4Tqge+qWaEqZs8eysq4", + "8WDWJjFv5clVO8UrlLAcy3i4DP4lStMZBahyTSkCPI1ziqgBtwlBYJ1thPUh/wMkZIQ0ZUwJeW5e47Ht", + "UlGWNKFI3KZ1N0hThOMcz9WYMaZFbaY2eaAoV7L4vSZqoDiTFaR/gAFNxnyx2LReZIaOjziLzfv0LFBU", + "s/XBA7kDuQFZUA/hnuQqkISqyOW5Q/g5DhgFbYYIMes/y5kGgZmyGwjOW+kBuNr6YW3n/Vuk32q9myMV", + "McuDuZ33b3fe/7SzvUCMiDeRgRrPcdjamEf6LfP6qFNug+Z0jjCpEEzglcn9DWgnWM545sp0iDNxm2Tp", + "S2thjp9uztypS0BAX0hN0Dzkbm9CytC74diJMXUGg7mcDUIr+0Ctzux3qrlEk11T8x8uEqCzyGi9/NHY", + "3kbwha1V8xjpk1UrNaf7sWDuXOxULoWCWZ47bPIfjb7GJePRLwhuGh8fGx9uItjcvfuKnFldM/vCHf0l", + "qCMvL/qtcPZXkH6b/MTzMHs85O9x9MS3mJHN1fZas7Wsd5ZuI9g8pOLJvb2EmYar9oG4vrP9HsEXrZc/", + "tp/cbK9t7N78hGDTuLFqrDwisyfZ0mnVV/1DnFZVVgPWpXOh0T8z85Jl5Co7LqvsMrPu/gJVs5MwPQGS", + "7+7fLOt5NKUGZn1APtgfIFuFdVFQDhemF9z2c7NOZNVP+HuCuG8SHohH4G+W97qK7AXPnfVZk5cy0EAS", + "royr1zrL65HwHCadeQGazg0EX9QPM5uJ8WBz78VDqH79VnBkGGv14hqpvnmaRqVw0x6+2ZUy+WAf7x/G", + "QmFXHrvPmvoqdiHMxe1V64dzhxMVpblHo5a+++Hoor2QoBVn0kFn/Wpr5U0kdL4piX3ETu/dWShs4jxa", + "ShtjSe6L2Jg0fPTWbWQrdbPScVQTtFr4FqTLYsaujNcxlqU/minz1o7+SWwaq+72xktj836/LVv3qOvC", + "7vURdn22goGI24s57Jfx+xqw7LP9ZKSaMLIzLm1YFV1m0VKQlWQr+d0i0JNOcQOW77N3raUrxst7eEdv", + "VkDbW5QkhtbNt/cI7PH5lKDvVSVMw/i/QddXm07VJf4pDLiDtR5Z7OiMiDOaPxHin4VzD2f35R3j5la7", + "8ZFwwdxn6MAbxo331OcFm/Y2zNz9J86yUN8D/D0beKZA58smbNhxw4DvVXBMcsZp38+czJ5WzeHBwZhC", + "fth0K/fhKtIhW7wftwK9soPNPeaEHASQKD7ODzkf32Rqip2PdaY+IjhW795/BLqBvVrzVMe5IafC/uKc", + "9LbfkWlUqj0QIQHfUA1QSwK0ONXHXqBccD81GZkydCMTNlMYkibs2qj6vp2ZMjno45NRRlLj4ojbtjLB", + "xuVQAjnhTUpr45lpLuzreraubHN3OFGU6DBlWx9yO7Xpsz5h9iz3ly7tWYA0Iw2bq8SuURqSFY0IkINM", + "Uv9h+AUdbNrIktJCUDwZBd7O0q/tJ0+7B28MEnsfjzLICUkeuNhJlgzoNXh6HxOmCwiTmc2gnf6XNJvW", + "+FHI+0PYzC9nKuk6IAJPpgLotFmaY9ffTGAsqkA5a2OZZaeqyKVa0fqSJFsiY1W30MU4AVWtZbkolBna", + "fDZLHs7Iqpb/a+6vOZNywpnLhcDPbpO+PZ/D5mYnZv8vAAD//69Dqo+7XwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index e204de8f..ab4f9a46 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -5,7 +5,6 @@ package openapi import ( "encoding/json" - "errors" "fmt" "time" @@ -138,10 +137,11 @@ type Groups = []string // NewQuestion defines model for NewQuestion. type NewQuestion struct { - Body string `json:"body"` + Description string `json:"description"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` + IsRequired bool `json:"is_required"` + Title string `json:"title"` union json.RawMessage } @@ -177,24 +177,24 @@ type NewResponse struct { // Question defines model for Question. type Question struct { - Body string `json:"body"` - CreatedAt time.Time `json:"created_at"` + CreatedAt time.Time `json:"created_at"` + Description string `json:"description"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - - // QuestionId 質問を追加する場合はnull。 - QuestionId *int `json:"question_id,omitempty"` - QuestionnaireId int `json:"questionnaire_id"` + IsRequired bool `json:"is_required"` + QuestionId int `json:"question_id"` + QuestionnaireId int `json:"questionnaire_id"` + Title string `json:"title"` union json.RawMessage } // QuestionBase defines model for QuestionBase. type QuestionBase struct { - Body string `json:"body"` + Description string `json:"description"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` + IsRequired bool `json:"is_required"` + Title string `json:"title"` } // QuestionSettingsByType defines model for QuestionSettingsByType. @@ -862,9 +862,9 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { } } - object["body"], err = json.Marshal(t.Body) + object["description"], err = json.Marshal(t.Description) if err != nil { - return nil, fmt.Errorf("error marshaling 'body': %w", err) + return nil, fmt.Errorf("error marshaling 'description': %w", err) } object["is_required"], err = json.Marshal(t.IsRequired) @@ -872,6 +872,11 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } + object["title"], err = json.Marshal(t.Title) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title': %w", err) + } + b, err = json.Marshal(object) return b, err } @@ -887,10 +892,10 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { return err } - if raw, found := object["body"]; found { - err = json.Unmarshal(raw, &t.Body) + if raw, found := object["description"]; found { + err = json.Unmarshal(raw, &t.Description) if err != nil { - return fmt.Errorf("error reading 'body': %w", err) + return fmt.Errorf("error reading 'description': %w", err) } } @@ -901,6 +906,13 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { } } + if raw, found := object["title"]; found { + err = json.Unmarshal(raw, &t.Title) + if err != nil { + return fmt.Errorf("error reading 'title': %w", err) + } + } + return err } @@ -1073,14 +1085,14 @@ func (t Question) MarshalJSON() ([]byte, error) { } } - object["body"], err = json.Marshal(t.Body) + object["created_at"], err = json.Marshal(t.CreatedAt) if err != nil { - return nil, fmt.Errorf("error marshaling 'body': %w", err) + return nil, fmt.Errorf("error marshaling 'created_at': %w", err) } - object["created_at"], err = json.Marshal(t.CreatedAt) + object["description"], err = json.Marshal(t.Description) if err != nil { - return nil, fmt.Errorf("error marshaling 'created_at': %w", err) + return nil, fmt.Errorf("error marshaling 'description': %w", err) } object["is_required"], err = json.Marshal(t.IsRequired) @@ -1088,11 +1100,9 @@ func (t Question) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } - if t.QuestionId != nil { - object["question_id"], err = json.Marshal(t.QuestionId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'question_id': %w", err) - } + object["question_id"], err = json.Marshal(t.QuestionId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'question_id': %w", err) } object["questionnaire_id"], err = json.Marshal(t.QuestionnaireId) @@ -1100,6 +1110,11 @@ func (t Question) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'questionnaire_id': %w", err) } + object["title"], err = json.Marshal(t.Title) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title': %w", err) + } + b, err = json.Marshal(object) return b, err } @@ -1115,17 +1130,17 @@ func (t *Question) UnmarshalJSON(b []byte) error { return err } - if raw, found := object["body"]; found { - err = json.Unmarshal(raw, &t.Body) + if raw, found := object["created_at"]; found { + err = json.Unmarshal(raw, &t.CreatedAt) if err != nil { - return fmt.Errorf("error reading 'body': %w", err) + return fmt.Errorf("error reading 'created_at': %w", err) } } - if raw, found := object["created_at"]; found { - err = json.Unmarshal(raw, &t.CreatedAt) + if raw, found := object["description"]; found { + err = json.Unmarshal(raw, &t.Description) if err != nil { - return fmt.Errorf("error reading 'created_at': %w", err) + return fmt.Errorf("error reading 'description': %w", err) } } @@ -1150,6 +1165,13 @@ func (t *Question) UnmarshalJSON(b []byte) error { } } + if raw, found := object["title"]; found { + err = json.Unmarshal(raw, &t.Title) + if err != nil { + return fmt.Errorf("error reading 'title': %w", err) + } + } + return err } @@ -1162,7 +1184,6 @@ func (t QuestionSettingsByType) AsQuestionSettingsText() (QuestionSettingsText, // FromQuestionSettingsText overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsText func (t *QuestionSettingsByType) FromQuestionSettingsText(v QuestionSettingsText) error { - v.QuestionType = "QuestionSettingsText" b, err := json.Marshal(v) t.union = b return err @@ -1170,7 +1191,6 @@ func (t *QuestionSettingsByType) FromQuestionSettingsText(v QuestionSettingsText // MergeQuestionSettingsText performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsText func (t *QuestionSettingsByType) MergeQuestionSettingsText(v QuestionSettingsText) error { - v.QuestionType = "QuestionSettingsText" b, err := json.Marshal(v) if err != nil { return err @@ -1190,7 +1210,6 @@ func (t QuestionSettingsByType) AsQuestionSettingsTextLong() (QuestionSettingsTe // FromQuestionSettingsTextLong overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsTextLong func (t *QuestionSettingsByType) FromQuestionSettingsTextLong(v QuestionSettingsTextLong) error { - v.QuestionType = "QuestionSettingsTextLong" b, err := json.Marshal(v) t.union = b return err @@ -1198,7 +1217,6 @@ func (t *QuestionSettingsByType) FromQuestionSettingsTextLong(v QuestionSettings // MergeQuestionSettingsTextLong performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsTextLong func (t *QuestionSettingsByType) MergeQuestionSettingsTextLong(v QuestionSettingsTextLong) error { - v.QuestionType = "QuestionSettingsTextLong" b, err := json.Marshal(v) if err != nil { return err @@ -1218,7 +1236,6 @@ func (t QuestionSettingsByType) AsQuestionSettingsNumber() (QuestionSettingsNumb // FromQuestionSettingsNumber overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsNumber func (t *QuestionSettingsByType) FromQuestionSettingsNumber(v QuestionSettingsNumber) error { - v.QuestionType = "QuestionSettingsNumber" b, err := json.Marshal(v) t.union = b return err @@ -1226,7 +1243,6 @@ func (t *QuestionSettingsByType) FromQuestionSettingsNumber(v QuestionSettingsNu // MergeQuestionSettingsNumber performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsNumber func (t *QuestionSettingsByType) MergeQuestionSettingsNumber(v QuestionSettingsNumber) error { - v.QuestionType = "QuestionSettingsNumber" b, err := json.Marshal(v) if err != nil { return err @@ -1246,7 +1262,6 @@ func (t QuestionSettingsByType) AsQuestionSettingsSingleChoice() (QuestionSettin // FromQuestionSettingsSingleChoice overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsSingleChoice func (t *QuestionSettingsByType) FromQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { - v.QuestionType = "QuestionSettingsSingleChoice" b, err := json.Marshal(v) t.union = b return err @@ -1254,7 +1269,6 @@ func (t *QuestionSettingsByType) FromQuestionSettingsSingleChoice(v QuestionSett // MergeQuestionSettingsSingleChoice performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsSingleChoice func (t *QuestionSettingsByType) MergeQuestionSettingsSingleChoice(v QuestionSettingsSingleChoice) error { - v.QuestionType = "QuestionSettingsSingleChoice" b, err := json.Marshal(v) if err != nil { return err @@ -1274,7 +1288,6 @@ func (t QuestionSettingsByType) AsQuestionSettingsMultipleChoice() (QuestionSett // FromQuestionSettingsMultipleChoice overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsMultipleChoice func (t *QuestionSettingsByType) FromQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { - v.QuestionType = "QuestionSettingsMultipleChoice" b, err := json.Marshal(v) t.union = b return err @@ -1282,7 +1295,6 @@ func (t *QuestionSettingsByType) FromQuestionSettingsMultipleChoice(v QuestionSe // MergeQuestionSettingsMultipleChoice performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsMultipleChoice func (t *QuestionSettingsByType) MergeQuestionSettingsMultipleChoice(v QuestionSettingsMultipleChoice) error { - v.QuestionType = "QuestionSettingsMultipleChoice" b, err := json.Marshal(v) if err != nil { return err @@ -1302,7 +1314,6 @@ func (t QuestionSettingsByType) AsQuestionSettingsScale() (QuestionSettingsScale // FromQuestionSettingsScale overwrites any union data inside the QuestionSettingsByType as the provided QuestionSettingsScale func (t *QuestionSettingsByType) FromQuestionSettingsScale(v QuestionSettingsScale) error { - v.QuestionType = "QuestionSettingsScale" b, err := json.Marshal(v) t.union = b return err @@ -1310,7 +1321,6 @@ func (t *QuestionSettingsByType) FromQuestionSettingsScale(v QuestionSettingsSca // MergeQuestionSettingsScale performs a merge with any union data inside the QuestionSettingsByType, using the provided QuestionSettingsScale func (t *QuestionSettingsByType) MergeQuestionSettingsScale(v QuestionSettingsScale) error { - v.QuestionType = "QuestionSettingsScale" b, err := json.Marshal(v) if err != nil { return err @@ -1321,37 +1331,6 @@ func (t *QuestionSettingsByType) MergeQuestionSettingsScale(v QuestionSettingsSc return err } -func (t QuestionSettingsByType) Discriminator() (string, error) { - var discriminator struct { - Discriminator string `json:"question_type"` - } - err := json.Unmarshal(t.union, &discriminator) - return discriminator.Discriminator, err -} - -func (t QuestionSettingsByType) ValueByDiscriminator() (interface{}, error) { - discriminator, err := t.Discriminator() - if err != nil { - return nil, err - } - switch discriminator { - case "QuestionSettingsMultipleChoice": - return t.AsQuestionSettingsMultipleChoice() - case "QuestionSettingsNumber": - return t.AsQuestionSettingsNumber() - case "QuestionSettingsScale": - return t.AsQuestionSettingsScale() - case "QuestionSettingsSingleChoice": - return t.AsQuestionSettingsSingleChoice() - case "QuestionSettingsText": - return t.AsQuestionSettingsText() - case "QuestionSettingsTextLong": - return t.AsQuestionSettingsTextLong() - default: - return nil, errors.New("unknown discriminator value: " + discriminator) - } -} - func (t QuestionSettingsByType) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err From 7cbaa2b2e069313ac2ce9ef411f0f115eaf85f02 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:18:45 +0000 Subject: [PATCH 74/83] fix: update oapi-codegen files --- openapi/spec.go | 136 +++++++++++++++++++++++------------------------ openapi/types.go | 85 +++++++++++------------------ 2 files changed, 99 insertions(+), 122 deletions(-) diff --git a/openapi/spec.go b/openapi/spec.go index 603ecd54..04cf80a6 100644 --- a/openapi/spec.go +++ b/openapi/spec.go @@ -18,74 +18,74 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi/types.go b/openapi/types.go index ab4f9a46..f033c075 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -137,11 +137,10 @@ type Groups = []string // NewQuestion defines model for NewQuestion. type NewQuestion struct { - Description string `json:"description"` + Body string `json:"body"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` union json.RawMessage } @@ -177,24 +176,24 @@ type NewResponse struct { // Question defines model for Question. type Question struct { - CreatedAt time.Time `json:"created_at"` - Description string `json:"description"` + Body string `json:"body"` + CreatedAt time.Time `json:"created_at"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - QuestionId int `json:"question_id"` - QuestionnaireId int `json:"questionnaire_id"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` + + // QuestionId 質問を追加する場合はnull。 + QuestionId *int `json:"question_id,omitempty"` + QuestionnaireId int `json:"questionnaire_id"` union json.RawMessage } // QuestionBase defines model for QuestionBase. type QuestionBase struct { - Description string `json:"description"` + Body string `json:"body"` // IsRequired 回答必須かどうか - IsRequired bool `json:"is_required"` - Title string `json:"title"` + IsRequired bool `json:"is_required"` } // QuestionSettingsByType defines model for QuestionSettingsByType. @@ -862,9 +861,9 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { } } - object["description"], err = json.Marshal(t.Description) + object["body"], err = json.Marshal(t.Body) if err != nil { - return nil, fmt.Errorf("error marshaling 'description': %w", err) + return nil, fmt.Errorf("error marshaling 'body': %w", err) } object["is_required"], err = json.Marshal(t.IsRequired) @@ -872,11 +871,6 @@ func (t NewQuestion) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } - object["title"], err = json.Marshal(t.Title) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title': %w", err) - } - b, err = json.Marshal(object) return b, err } @@ -892,10 +886,10 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { return err } - if raw, found := object["description"]; found { - err = json.Unmarshal(raw, &t.Description) + if raw, found := object["body"]; found { + err = json.Unmarshal(raw, &t.Body) if err != nil { - return fmt.Errorf("error reading 'description': %w", err) + return fmt.Errorf("error reading 'body': %w", err) } } @@ -906,13 +900,6 @@ func (t *NewQuestion) UnmarshalJSON(b []byte) error { } } - if raw, found := object["title"]; found { - err = json.Unmarshal(raw, &t.Title) - if err != nil { - return fmt.Errorf("error reading 'title': %w", err) - } - } - return err } @@ -1085,14 +1072,14 @@ func (t Question) MarshalJSON() ([]byte, error) { } } - object["created_at"], err = json.Marshal(t.CreatedAt) + object["body"], err = json.Marshal(t.Body) if err != nil { - return nil, fmt.Errorf("error marshaling 'created_at': %w", err) + return nil, fmt.Errorf("error marshaling 'body': %w", err) } - object["description"], err = json.Marshal(t.Description) + object["created_at"], err = json.Marshal(t.CreatedAt) if err != nil { - return nil, fmt.Errorf("error marshaling 'description': %w", err) + return nil, fmt.Errorf("error marshaling 'created_at': %w", err) } object["is_required"], err = json.Marshal(t.IsRequired) @@ -1100,9 +1087,11 @@ func (t Question) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'is_required': %w", err) } - object["question_id"], err = json.Marshal(t.QuestionId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'question_id': %w", err) + if t.QuestionId != nil { + object["question_id"], err = json.Marshal(t.QuestionId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'question_id': %w", err) + } } object["questionnaire_id"], err = json.Marshal(t.QuestionnaireId) @@ -1110,11 +1099,6 @@ func (t Question) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'questionnaire_id': %w", err) } - object["title"], err = json.Marshal(t.Title) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title': %w", err) - } - b, err = json.Marshal(object) return b, err } @@ -1130,17 +1114,17 @@ func (t *Question) UnmarshalJSON(b []byte) error { return err } - if raw, found := object["created_at"]; found { - err = json.Unmarshal(raw, &t.CreatedAt) + if raw, found := object["body"]; found { + err = json.Unmarshal(raw, &t.Body) if err != nil { - return fmt.Errorf("error reading 'created_at': %w", err) + return fmt.Errorf("error reading 'body': %w", err) } } - if raw, found := object["description"]; found { - err = json.Unmarshal(raw, &t.Description) + if raw, found := object["created_at"]; found { + err = json.Unmarshal(raw, &t.CreatedAt) if err != nil { - return fmt.Errorf("error reading 'description': %w", err) + return fmt.Errorf("error reading 'created_at': %w", err) } } @@ -1165,13 +1149,6 @@ func (t *Question) UnmarshalJSON(b []byte) error { } } - if raw, found := object["title"]; found { - err = json.Unmarshal(raw, &t.Title) - if err != nil { - return fmt.Errorf("error reading 'title': %w", err) - } - } - return err } From e624d35e1574b8cd55f11be644f134a3747ad1bf Mon Sep 17 00:00:00 2001 From: kaitoyama <45167401+kaitoyama@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:40:49 +0900 Subject: [PATCH 75/83] change status code --- controller/questionnaire.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 867f550d..c6c0464e 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -304,7 +304,7 @@ func (q Questionnaire) EditQuestionnaire(c echo.Context, questionnaireID int, pa } if isAnonymous && !params.IsAnonymous { c.Logger().Info("unable to change the questionnaire from anoymous to non-anonymous") - return echo.NewHTTPError(http.StatusMethodNotAllowed, "unable to change the questionnaire from anoymous to non-anonymous") + return echo.NewHTTPError(http.StatusBadRequest, "unable to change the questionnaire from anoymous to non-anonymous") } responseDueDateTime := null.Time{} From 3639cb36c33bc908756136ef95b3f0b2f04bf8dd Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Sun, 1 Dec 2024 14:16:36 +0900 Subject: [PATCH 76/83] update spec --- openapi/spec.gen.go | 162 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 openapi/spec.gen.go diff --git a/openapi/spec.gen.go b/openapi/spec.gen.go new file mode 100644 index 00000000..0e31e47a --- /dev/null +++ b/openapi/spec.gen.go @@ -0,0 +1,162 @@ +// Package openapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. +package openapi + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "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", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} From 3278678073392b9d83c39fca10ad77286c8c6d72 Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Sun, 1 Dec 2024 14:22:08 +0900 Subject: [PATCH 77/83] fix name --- openapi/spec.gen.go | 162 -------------------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 openapi/spec.gen.go diff --git a/openapi/spec.gen.go b/openapi/spec.gen.go deleted file mode 100644 index 0e31e47a..00000000 --- a/openapi/spec.gen.go +++ /dev/null @@ -1,162 +0,0 @@ -// Package openapi provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. -package openapi - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "fmt" - "net/url" - "path" - "strings" - - "github.com/getkin/kin-openapi/openapi3" -) - -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ - - "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", -} - -// GetSwagger returns the content of the embedded swagger specification file -// or error if failed to decode -func decodeSpec() ([]byte, error) { - zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) - if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %w", err) - } - zr, err := gzip.NewReader(bytes.NewReader(zipped)) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %w", err) - } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %w", err) - } - - return buf.Bytes(), nil -} - -var rawSpec = decodeSpecCached() - -// a naive cached of a decoded swagger spec -func decodeSpecCached() func() ([]byte, error) { - data, err := decodeSpec() - return func() ([]byte, error) { - return data, err - } -} - -// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. -func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - res := make(map[string]func() ([]byte, error)) - if len(pathToFile) > 0 { - res[pathToFile] = rawSpec - } - - return res -} - -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. The external references of Swagger specification are resolved. -// The logic of resolving external references is tightly connected to "import-mapping" feature. -// Externally referenced files must be embedded in the corresponding golang packages. -// Urls can be supported but this task was out of the scope. -func GetSwagger() (swagger *openapi3.T, err error) { - resolvePath := PathToRawSpec("") - - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - pathToFile := url.String() - pathToFile = path.Clean(pathToFile) - getSpec, ok := resolvePath[pathToFile] - if !ok { - err1 := fmt.Errorf("path not found: %s", pathToFile) - return nil, err1 - } - return getSpec() - } - var specData []byte - specData, err = rawSpec() - if err != nil { - return - } - swagger, err = loader.LoadFromData(specData) - if err != nil { - return - } - return -} From eecf167dbedd1cdd7c7177aabd5d42dcb9caf3c1 Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Sun, 1 Dec 2024 14:37:58 +0900 Subject: [PATCH 78/83] delete result controller --- controller/questionnaire.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index e64417eb..43d7bc56 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -793,31 +793,3 @@ https://anke-to.trap.jp/responses/new/%d questionnaireID, ) } - -func (q Questionnaire) GetQuestionnaireResult(ctx echo.Context, questionnaireID int, userID string) (openapi.Result, error) { - res := openapi.Result{} - - params := openapi.GetQuestionnaireResponsesParams{} - responses, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) - if err != nil { - if errors.Is(echo.ErrNotFound, err) { - return openapi.Result{}, err - } - ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) - return openapi.Result{}, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaire responses: %w", err)) - } - - for _, response := range responses { - tmp := openapi.ResultItem{ - Body: response.Body, - IsDraft: response.IsDraft, - ModifiedAt: response.ModifiedAt, - QuestionnaireId: response.QuestionnaireId, - ResponseId: response.ResponseId, - SubmittedAt: response.SubmittedAt, - } - res = append(res, tmp) - } - - return res, nil -} From 02243eeab05822c60b7ac3291f6e7bc320062caf Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Fri, 6 Dec 2024 14:07:54 +0900 Subject: [PATCH 79/83] remove router --- router/api.go | 23 - router/api_test.go | 51 - router/middleware.go | 384 ------- router/middleware_test.go | 637 ----------- router/questionnaires.go | 666 ----------- router/questionnaires_test.go | 1946 --------------------------------- router/questions.go | 158 --- router/questions_test.go | 1433 ------------------------ router/responses.go | 426 -------- router/responses_test.go | 1879 ------------------------------- router/results.go | 43 - router/results_test.go | 176 --- router/users.go | 268 ----- router/users_test.go | 951 ---------------- 14 files changed, 9041 deletions(-) delete mode 100644 router/api.go delete mode 100644 router/api_test.go delete mode 100644 router/middleware.go delete mode 100644 router/middleware_test.go delete mode 100644 router/questionnaires.go delete mode 100644 router/questionnaires_test.go delete mode 100644 router/questions.go delete mode 100644 router/questions_test.go delete mode 100644 router/responses.go delete mode 100644 router/responses_test.go delete mode 100644 router/results.go delete mode 100644 router/results_test.go delete mode 100644 router/users.go delete mode 100644 router/users_test.go diff --git a/router/api.go b/router/api.go deleted file mode 100644 index 3981794b..00000000 --- a/router/api.go +++ /dev/null @@ -1,23 +0,0 @@ -package router - -// API api全体の構造体 -type API struct { - *Middleware - *Questionnaire - *Question - *Response - *Result - *User -} - -// NewAPI APIのコンストラクタ -func NewAPI(middleware *Middleware, questionnaire *Questionnaire, question *Question, response *Response, result *Result, user *User) *API { - return &API{ - Middleware: middleware, - Questionnaire: questionnaire, - Question: question, - Response: response, - Result: result, - User: user, - } -} diff --git a/router/api_test.go b/router/api_test.go deleted file mode 100644 index 67e903db..00000000 --- a/router/api_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package router - -import ( - "errors" - "net/http" - "net/http/httptest" - "strings" - - "github.com/labstack/echo/v4" -) - -type users string -type httpMethods string -type contentTypes string - -const ( - rootPath = "/api" - userHeader = "X-Showcase-User" - userUnAuthorized = "-" - userOne users = "mazrean" - userTwo users = "ryoha" - //userThree users = "YumizSui" - methodGet httpMethods = http.MethodGet - methodPost httpMethods = http.MethodPost - methodPatch httpMethods = http.MethodPatch - methodDelete httpMethods = http.MethodDelete - typeNone contentTypes = "" - typeJSON contentTypes = echo.MIMEApplicationJSON -) - -var ( - errMock = errors.New("Mock Error") -) - -func makePath(path string) string { - return rootPath + path -} - -func createRecorder(e *echo.Echo, user users, method httpMethods, path string, contentType contentTypes, body string) *httptest.ResponseRecorder { - req := httptest.NewRequest(string(method), path, strings.NewReader(body)) - if contentType != typeNone { - req.Header.Set(echo.HeaderContentType, string(contentType)) - } - req.Header.Set(userHeader, string(user)) - - rec := httptest.NewRecorder() - - e.ServeHTTP(rec, req) - - return rec -} diff --git a/router/middleware.go b/router/middleware.go deleted file mode 100644 index c1ee10f5..00000000 --- a/router/middleware.go +++ /dev/null @@ -1,384 +0,0 @@ -package router - -import ( - "errors" - "fmt" - "net/http" - "strconv" - - "github.com/go-playground/validator/v10" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "github.com/traPtitech/anke-to/model" -) - -// Middleware Middlewareの構造体 -type Middleware struct { - model.IAdministrator - model.IRespondent - model.IQuestion - model.IQuestionnaire -} - -// NewMiddleware Middlewareのコンストラクタ -func NewMiddleware(administrator model.IAdministrator, respondent model.IRespondent, question model.IQuestion, questionnaire model.IQuestionnaire) *Middleware { - return &Middleware{ - IAdministrator: administrator, - IRespondent: respondent, - IQuestion: question, - IQuestionnaire: questionnaire, - } -} - -const ( - validatorKey = "validator" - userIDKey = "userID" - questionnaireIDKey = "questionnaireID" - responseIDKey = "responseID" - questionIDKey = "questionID" -) - -func (*Middleware) SetValidatorMiddleware(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - validate := validator.New() - c.Set(validatorKey, validate) - - return next(c) - } -} - -/* 消せないアンケートの発生を防ぐための管理者 -暫定的にハードコーディングで対応*/ -var adminUserIDs = []string{"ryoha", "xxarupakaxx", "kaitoyama", "cp20", "itzmeowww"} - -// SetUserIDMiddleware X-Showcase-UserからユーザーIDを取得しセットする -func (*Middleware) SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID := c.Request().Header.Get("X-Showcase-User") - if userID == "" { - userID = "mds_boy" - } - - c.Set(userIDKey, userID) - - return next(c) - } -} - -// TraPMemberAuthenticate traP部員かの認証 -func (*Middleware) TraPMemberAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - // トークンを持たないユーザはアクセスできない - if userID == "-" { - c.Logger().Info("not logged in") - return echo.NewHTTPError(http.StatusUnauthorized, "You are not logged in") - } - - return next(c) - } -} - -// TrapRateLimitMiddlewareFunc traP IDベースのリクエスト制限 -func (*Middleware) TrapRateLimitMiddlewareFunc() echo.MiddlewareFunc { - config := middleware.RateLimiterConfig{ - Store: middleware.NewRateLimiterMemoryStore(5), - IdentifierExtractor: func(c echo.Context) (string, error) { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return "", echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - return userID, nil - }, - } - - return middleware.RateLimiterWithConfig(config) -} - -// QuestionnaireAdministratorAuthenticate アンケートの管理者かどうかの認証 -func (m *Middleware) QuestionnaireAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - strQuestionnaireID := c.Param("questionnaireID") - questionnaireID, err := strconv.Atoi(strQuestionnaireID) - if err != nil { - c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) - } - - for _, adminID := range adminUserIDs { - if userID == adminID { - c.Set(questionnaireIDKey, questionnaireID) - - return next(c) - } - } - isAdmin, err := m.CheckQuestionnaireAdmin(c.Request().Context(), userID, questionnaireID) - if err != nil { - c.Logger().Errorf("failed to check questionnaire admin: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are administrator: %w", err)) - } - if !isAdmin { - return c.String(http.StatusForbidden, "You are not a administrator of this questionnaire.") - } - - c.Set(questionnaireIDKey, questionnaireID) - - return next(c) - } -} - -// ResponseReadAuthenticate 回答閲覧権限があるかの認証 -func (m *Middleware) ResponseReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - strResponseID := c.Param("responseID") - responseID, err := strconv.Atoi(strResponseID) - if err != nil { - c.Logger().Info("failed to convert responseID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid responseID:%s(error: %w)", strResponseID, err)) - } - - // 回答者ならOK - respondent, err := m.GetRespondent(c.Request().Context(), responseID) - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("response not found: %+v", err) - return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("response not found:%d", responseID)) - } - if err != nil { - c.Logger().Errorf("failed to check if you are a respondent: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are a respondent: %w", err)) - } - if respondent == nil { - c.Logger().Error("respondent is nil") - return echo.NewHTTPError(http.StatusInternalServerError) - } - if respondent.UserTraqid == userID { - return next(c) - } - - // 回答者以外は一時保存の回答は閲覧できない - if !respondent.SubmittedAt.Valid { - c.Logger().Info("not submitted") - - // Note: 一時保存の回答の存在もわかってはいけないので、Respondentが見つからない時と全く同じエラーを返す - return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("response not found:%d", responseID)) - } - - // アンケートごとの回答閲覧権限チェック - responseReadPrivilegeInfo, err := m.GetResponseReadPrivilegeInfoByResponseID(c.Request().Context(), userID, responseID) - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("response not found: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid responseID: %d", responseID)) - } else if err != nil { - c.Logger().Errorf("failed to get response read privilege info: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response read privilege info: %w", err)) - } - - haveReadPrivilege, err := checkResponseReadPrivilege(responseReadPrivilegeInfo) - if err != nil { - c.Logger().Errorf("failed to check response read privilege: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check response read privilege: %w", err)) - } - if !haveReadPrivilege { - return c.String(http.StatusForbidden, "You do not have permission to view this response.") - } - - return next(c) - } -} - -// RespondentAuthenticate 回答者かどうかの認証 -func (m *Middleware) RespondentAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - strResponseID := c.Param("responseID") - responseID, err := strconv.Atoi(strResponseID) - if err != nil { - c.Logger().Infof("failed to convert responseID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid responseID:%s(error: %w)", strResponseID, err)) - } - - respondent, err := m.GetRespondent(c.Request().Context(), responseID) - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("response not found: %+v", err) - return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("response not found:%d", responseID)) - } - if err != nil { - c.Logger().Errorf("failed to check if you are a respondent: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are a respondent: %w", err)) - } - if respondent == nil { - c.Logger().Error("respondent is nil") - return echo.NewHTTPError(http.StatusInternalServerError) - } - if respondent.UserTraqid != userID { - return c.String(http.StatusForbidden, "You are not a respondent of this response.") - } - - c.Set(responseIDKey, responseID) - - return next(c) - } -} - -// QuestionAdministratorAuthenticate アンケートの管理者かどうかの認証 -func (m *Middleware) QuestionAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - strQuestionID := c.Param("questionID") - questionID, err := strconv.Atoi(strQuestionID) - if err != nil { - c.Logger().Infof("failed to convert questionID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionID:%s(error: %w)", strQuestionID, err)) - } - - for _, adminID := range adminUserIDs { - if userID == adminID { - c.Set(questionIDKey, questionID) - - return next(c) - } - } - isAdmin, err := m.CheckQuestionAdmin(c.Request().Context(), userID, questionID) - if err != nil { - c.Logger().Errorf("failed to check if you are a question administrator: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check if you are administrator: %w", err)) - } - if !isAdmin { - return c.String(http.StatusForbidden, "You are not a administrator of this questionnaire.") - } - - c.Set(questionIDKey, questionID) - - return next(c) - } -} - -// ResultAuthenticate アンケートの回答を確認できるかの認証 -func (m *Middleware) ResultAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - strQuestionnaireID := c.Param("questionnaireID") - questionnaireID, err := strconv.Atoi(strQuestionnaireID) - if err != nil { - c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) - } - - responseReadPrivilegeInfo, err := m.GetResponseReadPrivilegeInfoByQuestionnaireID(c.Request().Context(), userID, questionnaireID) - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("response not found: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid responseID: %d", questionnaireID)) - } else if err != nil { - c.Logger().Errorf("failed to get responseReadPrivilegeInfo: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get response read privilege info: %w", err)) - } - - haveReadPrivilege, err := checkResponseReadPrivilege(responseReadPrivilegeInfo) - if err != nil { - c.Logger().Errorf("failed to check response read privilege: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to check response read privilege: %w", err)) - } - if !haveReadPrivilege { - return c.String(http.StatusForbidden, "You do not have permission to view this response.") - } - - return next(c) - } -} - -func checkResponseReadPrivilege(responseReadPrivilegeInfo *model.ResponseReadPrivilegeInfo) (bool, error) { - switch responseReadPrivilegeInfo.ResSharedTo { - case "administrators": - return responseReadPrivilegeInfo.IsAdministrator, nil - case "respondents": - return responseReadPrivilegeInfo.IsAdministrator || responseReadPrivilegeInfo.IsRespondent, nil - case "public": - return true, nil - } - - return false, errors.New("invalid resSharedTo") -} - -func getValidator(c echo.Context) (*validator.Validate, error) { - rowValidate := c.Get(validatorKey) - validate, ok := rowValidate.(*validator.Validate) - if !ok { - return nil, fmt.Errorf("failed to get validator") - } - - return validate, nil -} - -func getUserID(c echo.Context) (string, error) { - rowUserID := c.Get(userIDKey) - userID, ok := rowUserID.(string) - if !ok { - return "", errors.New("invalid context userID") - } - - return userID, nil -} - -func getQuestionnaireID(c echo.Context) (int, error) { - rowQuestionnaireID := c.Get(questionnaireIDKey) - questionnaireID, ok := rowQuestionnaireID.(int) - if !ok { - return 0, errors.New("invalid context questionnaireID") - } - - return questionnaireID, nil -} - -func getResponseID(c echo.Context) (int, error) { - rowResponseID := c.Get(responseIDKey) - responseID, ok := rowResponseID.(int) - if !ok { - return 0, errors.New("invalid context responseID") - } - - return responseID, nil -} - -func getQuestionID(c echo.Context) (int, error) { - rowQuestionID := c.Get(questionIDKey) - questionID, ok := rowQuestionID.(int) - if !ok { - return 0, errors.New("invalid context questionID") - } - - return questionID, nil -} diff --git a/router/middleware_test.go b/router/middleware_test.go deleted file mode 100644 index 84503255..00000000 --- a/router/middleware_test.go +++ /dev/null @@ -1,637 +0,0 @@ -package router - -import ( - "errors" - "fmt" - "net/http" - "net/http/httptest" - "strconv" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/model/mock_model" - "gopkg.in/guregu/null.v4" -) - -type CallChecker struct { - IsCalled bool -} - -func (cc *CallChecker) Handler(c echo.Context) error { - cc.IsCalled = true - - return c.NoContent(http.StatusOK) -} - -func TestSetUserIDMiddleware(t *testing.T) { - t.Parallel() - - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - middleware := NewMiddleware(mockAdministrator, mockRespondent, mockQuestion, mockQuestionnaire) - - type args struct { - userID string - } - type expect struct { - userID interface{} - } - type test struct { - description string - args - expect - } - - testCases := []test{ - { - description: "正常なユーザーIDなのでユーザーID取得", - args: args{ - userID: "mazrean", - }, - expect: expect{ - userID: "mazrean", - }, - }, - { - description: "ユーザーIDが空なのでmds_boy", - args: args{ - userID: "", - }, - expect: expect{ - userID: "mds_boy", - }, - }, - { - description: "ユーザーIDが-なので-", - args: args{ - userID: "-", - }, - expect: expect{ - userID: "-", - }, - }, - } - - for _, testCase := range testCases { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - req.Header.Set("X-Showcase-User", testCase.args.userID) - - e.HTTPErrorHandler(middleware.SetUserIDMiddleware(func(c echo.Context) error { - assertion.Equal(testCase.expect.userID, c.Get(userIDKey), testCase.description, "userID") - return c.NoContent(http.StatusOK) - })(c), c) - - assertion.Equal(http.StatusOK, rec.Code, testCase.description, "status code") - } -} - -func TestTraPMemberAuthenticate(t *testing.T) { - t.Parallel() - - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - middleware := NewMiddleware(mockAdministrator, mockRespondent, mockQuestion, mockQuestionnaire) - - type args struct { - userID string - } - type expect struct { - statusCode int - isCalled bool - } - type test struct { - description string - args - expect - } - - testCases := []test{ - { - description: "正常なユーザーIDなので通す", - args: args{ - userID: "mazrean", - }, - expect: expect{ - statusCode: http.StatusOK, - isCalled: true, - }, - }, - { - description: "ユーザーIDが-なので401", - args: args{ - userID: "-", - }, - expect: expect{ - statusCode: http.StatusUnauthorized, - isCalled: false, - }, - }, - } - - for _, testCase := range testCases { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - c.Set(userIDKey, testCase.args.userID) - - callChecker := CallChecker{} - - e.HTTPErrorHandler(middleware.TraPMemberAuthenticate(callChecker.Handler)(c), c) - - assertion.Equal(testCase.expect.statusCode, rec.Code, testCase.description, "status code") - assertion.Equal(testCase.expect.isCalled, testCase.expect.statusCode == http.StatusOK, testCase.description, "isCalled") - } -} - -func TestResponseReadAuthenticate(t *testing.T) { - t.Parallel() - - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - middleware := NewMiddleware(mockAdministrator, mockRespondent, mockQuestion, mockQuestionnaire) - - type args struct { - userID string - respondent *model.Respondents - GetRespondentError error - ExecutesResponseReadPrivilegeCheck bool - haveReadPrivilege bool - GetResponseReadPrivilegeInfoByResponseIDError error - checkResponseReadPrivilegeError error - } - type expect struct { - statusCode int - isCalled bool - } - type test struct { - description string - args - expect - } - - testCases := []test{ - { - description: "この回答の回答者である場合通す", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user1", - }, - }, - expect: expect{ - statusCode: http.StatusOK, - isCalled: true, - }, - }, - { - description: "GetRespondentがErrRecordNotFoundの場合404", - args: args{ - userID: "user1", - GetRespondentError: model.ErrRecordNotFound, - }, - expect: expect{ - statusCode: http.StatusNotFound, - isCalled: false, - }, - }, - { - description: "respondentがnilの場合500", - args: args{ - userID: "user1", - respondent: nil, - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - isCalled: false, - }, - }, - { - description: "GetRespondentがエラー(ErrRecordNotFound以外)の場合500", - args: args{ - GetRespondentError: errors.New("error"), - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - isCalled: false, - }, - }, - { - description: "responseがsubmitされていない場合404", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user2", - SubmittedAt: null.Time{}, - }, - }, - expect: expect{ - statusCode: http.StatusNotFound, - isCalled: false, - }, - }, - { - description: "この回答の回答者でなくてもsubmitされていてhaveReadPrivilegeがtrueの場合通す", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user2", - SubmittedAt: null.NewTime(time.Now(), true), - }, - ExecutesResponseReadPrivilegeCheck: true, - haveReadPrivilege: true, - }, - expect: expect{ - statusCode: http.StatusOK, - isCalled: true, - }, - }, - { - description: "この回答の回答者でなく、submitされていてhaveReadPrivilegeがfalseの場合403", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user2", - SubmittedAt: null.NewTime(time.Now(), true), - }, - ExecutesResponseReadPrivilegeCheck: true, - haveReadPrivilege: false, - }, - expect: expect{ - statusCode: http.StatusForbidden, - isCalled: false, - }, - }, - { - description: "GetResponseReadPrivilegeInfoByResponseIDがErrRecordNotFoundの場合400", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user2", - SubmittedAt: null.NewTime(time.Now(), true), - }, - ExecutesResponseReadPrivilegeCheck: true, - haveReadPrivilege: false, - GetResponseReadPrivilegeInfoByResponseIDError: model.ErrRecordNotFound, - }, - expect: expect{ - statusCode: http.StatusBadRequest, - isCalled: false, - }, - }, - { - description: "GetResponseReadPrivilegeInfoByResponseIDがエラー(ErrRecordNotFound以外)の場合500", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user2", - SubmittedAt: null.NewTime(time.Now(), true), - }, - ExecutesResponseReadPrivilegeCheck: true, - haveReadPrivilege: false, - GetResponseReadPrivilegeInfoByResponseIDError: errors.New("error"), - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - isCalled: false, - }, - }, - { - description: "checkResponseReadPrivilegeがエラーの場合500", - args: args{ - userID: "user1", - respondent: &model.Respondents{ - UserTraqid: "user2", - SubmittedAt: null.NewTime(time.Now(), true), - }, - ExecutesResponseReadPrivilegeCheck: true, - haveReadPrivilege: false, - checkResponseReadPrivilegeError: errors.New("error"), - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - isCalled: false, - }, - }, - } - - for _, testCase := range testCases { - responseID := 1 - - var responseReadPrivilegeInfo model.ResponseReadPrivilegeInfo - if testCase.args.checkResponseReadPrivilegeError != nil { - responseReadPrivilegeInfo = model.ResponseReadPrivilegeInfo{ - ResSharedTo: "invalid value", - } - } else if testCase.args.haveReadPrivilege { - responseReadPrivilegeInfo = model.ResponseReadPrivilegeInfo{ - ResSharedTo: "public", - } - } else { - responseReadPrivilegeInfo = model.ResponseReadPrivilegeInfo{ - ResSharedTo: "administrators", - } - } - - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - c.SetPath("/responses/:responseID") - c.SetParamNames("responseID") - c.SetParamValues(strconv.Itoa(responseID)) - c.Set(userIDKey, testCase.args.userID) - - mockRespondent. - EXPECT(). - GetRespondent(c.Request().Context(), responseID). - Return(testCase.args.respondent, testCase.args.GetRespondentError) - if testCase.args.ExecutesResponseReadPrivilegeCheck { - mockQuestionnaire. - EXPECT(). - GetResponseReadPrivilegeInfoByResponseID(c.Request().Context(), testCase.args.userID, responseID). - Return(&responseReadPrivilegeInfo, testCase.args.GetResponseReadPrivilegeInfoByResponseIDError) - } - - callChecker := CallChecker{} - - e.HTTPErrorHandler(middleware.ResponseReadAuthenticate(callChecker.Handler)(c), c) - - assertion.Equalf(testCase.expect.statusCode, rec.Code, testCase.description, "status code") - assertion.Equalf(testCase.expect.isCalled, callChecker.IsCalled, testCase.description, "isCalled") - } -} - -func TestResultAuthenticate(t *testing.T) { - t.Parallel() - - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - middleware := NewMiddleware(mockAdministrator, mockRespondent, mockQuestion, mockQuestionnaire) - - type args struct { - haveReadPrivilege bool - GetResponseReadPrivilegeInfoByResponseIDError error - checkResponseReadPrivilegeError error - } - type expect struct { - statusCode int - isCalled bool - } - type test struct { - description string - args - expect - } - - testCases := []test{ - { - description: "haveReadPrivilegeがtrueの場合通す", - args: args{ - haveReadPrivilege: true, - }, - expect: expect{ - statusCode: http.StatusOK, - isCalled: true, - }, - }, - { - description: "haveReadPrivilegeがfalseの場合403", - args: args{ - haveReadPrivilege: false, - }, - expect: expect{ - statusCode: http.StatusForbidden, - isCalled: false, - }, - }, - { - description: "GetResponseReadPrivilegeInfoByResponseIDがErrRecordNotFoundの場合400", - args: args{ - haveReadPrivilege: false, - GetResponseReadPrivilegeInfoByResponseIDError: model.ErrRecordNotFound, - }, - expect: expect{ - statusCode: http.StatusBadRequest, - isCalled: false, - }, - }, - { - description: "GetResponseReadPrivilegeInfoByResponseIDがエラー(ErrRecordNotFound以外)の場合500", - args: args{ - haveReadPrivilege: false, - GetResponseReadPrivilegeInfoByResponseIDError: errors.New("error"), - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - isCalled: false, - }, - }, - { - description: "checkResponseReadPrivilegeがエラーの場合500", - args: args{ - haveReadPrivilege: false, - checkResponseReadPrivilegeError: errors.New("error"), - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - isCalled: false, - }, - }, - } - - for _, testCase := range testCases { - userID := "testUser" - questionnaireID := 1 - var responseReadPrivilegeInfo model.ResponseReadPrivilegeInfo - if testCase.args.checkResponseReadPrivilegeError != nil { - responseReadPrivilegeInfo = model.ResponseReadPrivilegeInfo{ - ResSharedTo: "invalid value", - } - } else if testCase.args.haveReadPrivilege { - responseReadPrivilegeInfo = model.ResponseReadPrivilegeInfo{ - ResSharedTo: "public", - } - } else { - responseReadPrivilegeInfo = model.ResponseReadPrivilegeInfo{ - ResSharedTo: "administrators", - } - } - - e := echo.New() - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/results/%d", questionnaireID), nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - c.SetPath("/results/:questionnaireID") - c.SetParamNames("questionnaireID") - c.SetParamValues(strconv.Itoa(questionnaireID)) - c.Set(userIDKey, userID) - - mockQuestionnaire. - EXPECT(). - GetResponseReadPrivilegeInfoByQuestionnaireID(c.Request().Context(), userID, questionnaireID). - Return(&responseReadPrivilegeInfo, testCase.args.GetResponseReadPrivilegeInfoByResponseIDError) - - callChecker := CallChecker{} - - e.HTTPErrorHandler(middleware.ResultAuthenticate(callChecker.Handler)(c), c) - - assertion.Equalf(testCase.expect.statusCode, rec.Code, testCase.description, "status code") - assertion.Equalf(testCase.expect.isCalled, callChecker.IsCalled, testCase.description, "isCalled") - } -} - -func TestCheckResponseReadPrivilege(t *testing.T) { - t.Parallel() - - assertion := assert.New(t) - - type args struct { - responseReadPrivilegeInfo model.ResponseReadPrivilegeInfo - } - type expect struct { - haveReadPrivilege bool - isErr bool - err error - } - type test struct { - description string - args - expect - } - - testCases := []test{ - { - description: "res_shared_toがpublic、administrators、respondentsのいずれでもない場合エラー", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "invalid value", - }, - }, - expect: expect{ - isErr: true, - }, - }, - { - description: "res_shared_toがpublicの場合true", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "public", - }, - }, - expect: expect{ - haveReadPrivilege: true, - }, - }, - { - description: "res_shared_toがadministratorsかつadministratorの場合true", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "administrators", - IsAdministrator: true, - }, - }, - expect: expect{ - haveReadPrivilege: true, - }, - }, - { - description: "res_shared_toがadministratorsかつadministratorでない場合false", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "administrators", - IsAdministrator: false, - }, - }, - }, - { - description: "res_shared_toがrespondentsかつadministratorの場合true", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "respondents", - IsAdministrator: true, - }, - }, - expect: expect{ - haveReadPrivilege: true, - }, - }, - { - description: "res_shared_toがrespondentsかつrespondentの場合true", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "respondents", - IsRespondent: true, - }, - }, - expect: expect{ - haveReadPrivilege: true, - }, - }, - { - description: "res_shared_toがrespondentsかつ、administratorでもrespondentでない場合false", - args: args{ - responseReadPrivilegeInfo: model.ResponseReadPrivilegeInfo{ - ResSharedTo: "respondents", - IsAdministrator: false, - IsRespondent: false, - }, - }, - expect: expect{ - haveReadPrivilege: false, - }, - }, - } - - for _, testCase := range testCases { - haveReadPrivilege, err := checkResponseReadPrivilege(&testCase.args.responseReadPrivilegeInfo) - - if testCase.expect.isErr { - assertion.Errorf(err, testCase.description, "error") - } else { - assertion.NoErrorf(err, testCase.description, "no error") - assertion.Equalf(testCase.expect.haveReadPrivilege, haveReadPrivilege, testCase.description, "haveReadPrivilege") - } - } -} diff --git a/router/questionnaires.go b/router/questionnaires.go deleted file mode 100644 index b33db7f4..00000000 --- a/router/questionnaires.go +++ /dev/null @@ -1,666 +0,0 @@ -package router - -import ( - "context" - "errors" - "fmt" - "net/http" - "regexp" - "strconv" - "strings" - "time" - - "github.com/labstack/echo/v4" - "gopkg.in/guregu/null.v4" - - "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/traq" -) - -// Questionnaire Questionnaireの構造体 -type Questionnaire struct { - model.IQuestionnaire - model.ITarget - model.IAdministrator - model.IQuestion - model.IOption - model.IScaleLabel - model.IValidation - model.ITransaction - traq.IWebhook -} - -const MaxTitleLength = 50 - -// NewQuestionnaire Questionnaireのコンストラクタ -func NewQuestionnaire( - questionnaire model.IQuestionnaire, - target model.ITarget, - administrator model.IAdministrator, - question model.IQuestion, - option model.IOption, - scaleLabel model.IScaleLabel, - validation model.IValidation, - transaction model.ITransaction, - webhook traq.IWebhook, -) *Questionnaire { - return &Questionnaire{ - IQuestionnaire: questionnaire, - ITarget: target, - IAdministrator: administrator, - IQuestion: question, - IOption: option, - IScaleLabel: scaleLabel, - IValidation: validation, - ITransaction: transaction, - IWebhook: webhook, - } -} - -type GetQuestionnairesQueryParam struct { - Sort string `validate:"omitempty,oneof=created_at -created_at title -title modified_at -modified_at"` - Search string `validate:"omitempty"` - Page string `validate:"omitempty,number,min=0"` - Nontargeted string `validate:"omitempty,boolean"` -} - -// GetQuestionnaires GET /questionnaires -func (q *Questionnaire) GetQuestionnaires(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - sort := c.QueryParam("sort") - search := c.QueryParam("search") - page := c.QueryParam("page") - nontargeted := c.QueryParam("nontargeted") - - p := GetQuestionnairesQueryParam{ - Sort: sort, - Search: search, - Page: page, - Nontargeted: nontargeted, - } - - validate, err := getValidator(c) - if err != nil { - c.Logger().Errorf("failed to get validator: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError) - } - - err = validate.StructCtx(c.Request().Context(), p) - if err != nil { - c.Logger().Infof("failed to validate: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - if len(page) == 0 { - page = "1" - } - pageNum, err := strconv.Atoi(page) - if err != nil { - c.Logger().Infof("failed to convert page to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to convert the string query parameter 'page'(%s) to integer: %w", page, err)) - } - if pageNum <= 0 { - c.Logger().Info("page must be greater than 0") - return echo.NewHTTPError(http.StatusBadRequest, errors.New("page cannot be less than 0")) - } - - var nontargetedBool bool - if len(nontargeted) != 0 { - nontargetedBool, err = strconv.ParseBool(nontargeted) - if err != nil { - c.Logger().Infof("failed to convert nontargeted to bool: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to convert the string query parameter 'nontargeted'(%s) to bool: %w", nontargeted, err)) - } - } else { - nontargetedBool = false - } - - questionnaires, pageMax, err := q.IQuestionnaire.GetQuestionnaires(c.Request().Context(), userID, sort, search, pageNum, nontargetedBool) - if err != nil { - if errors.Is(err, model.ErrTooLargePageNum) || errors.Is(err, model.ErrInvalidRegex) { - c.Logger().Infof("failed to get questionnaires: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - if errors.Is(err, model.ErrDeadlineExceeded) { - c.Logger().Errorf("failed to get questionnaires (deadline exceeded): %+v", err) - return echo.NewHTTPError(http.StatusServiceUnavailable, "deadline exceeded") - } - c.Logger().Errorf("failed to get questionnaires: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "page_max": pageMax, - "questionnaires": questionnaires, - }) -} - -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"` -} - -// PostQuestionnaire POST /questionnaires -func (q *Questionnaire) PostQuestionnaire(c echo.Context) error { - req := PostAndEditQuestionnaireRequest{} - - // JSONを構造体につける - err := c.Bind(&req) - if err != nil { - c.Logger().Infof("failed to bind PostAndEditQuestionnaireRequest: %+v", err) - 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 = validate.StructCtx(c.Request().Context(), req) - if err != nil { - c.Logger().Infof("failed to validate: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - if req.ResTimeLimit.Valid { - isBefore := req.ResTimeLimit.ValueOrZero().Before(time.Now()) - if isBefore { - c.Logger().Infof("invalid resTimeLimit: %+v", req.ResTimeLimit) - return echo.NewHTTPError(http.StatusBadRequest, "res time limit is before now") - } - } - - 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) - if err != nil { - c.Logger().Errorf("failed to insert a questionnaire: %+v", err) - return err - } - - err := q.InsertTargets(ctx, questionnaireID, req.Targets) - if err != nil { - c.Logger().Errorf("failed to insert targets: %+v", err) - return err - } - - err = q.InsertAdministrators(ctx, questionnaireID, req.Administrators) - if err != nil { - c.Logger().Errorf("failed to insert administrators: %+v", err) - return err - } - - message := createQuestionnaireMessage( - questionnaireID, - req.Title, - req.Description, - req.Administrators, - req.ResTimeLimit, - req.Targets, - ) - err = q.PostMessage(message) - if err != nil { - c.Logger().Errorf("failed to post message: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to post message to traQ") - } - - return nil - }) - if err != nil { - var httpError *echo.HTTPError - if errors.As(err, &httpError) { - return httpError - } - - c.Logger().Errorf("failed to create questionnaire: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to create a questionnaire") - } - - 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, - }) -} - -// GetQuestionnaire GET /questionnaires/:questionnaireID -func (q *Questionnaire) GetQuestionnaire(c echo.Context) error { - strQuestionnaireID := c.Param("questionnaireID") - questionnaireID, err := strconv.Atoi(strQuestionnaireID) - if err != nil { - c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) - } - - questionnaire, targets, administrators, respondents, err := q.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) - if err != nil { - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("questionnaire not found: %+v", err) - return echo.NewHTTPError(http.StatusNotFound, err) - } - c.Logger().Errorf("failed to get questionnaire: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - 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, - }) -} - -// PostQuestionByQuestionnaireID POST /questionnaires/:questionnaireID/questions -func (q *Questionnaire) PostQuestionByQuestionnaireID(c echo.Context) error { - strQuestionnaireID := c.Param("questionnaireID") - questionnaireID, err := strconv.Atoi(strQuestionnaireID) - if err != nil { - c.Logger().Info("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) - } - req := PostAndEditQuestionRequest{} - if err := c.Bind(&req); err != nil { - c.Logger().Info("failed to bind PostAndEditQuestionRequest: %+v", err) - 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 = validate.StructCtx(c.Request().Context(), req) - if err != nil { - c.Logger().Infof("failed to validate: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - // 重複したquestionNumを持つ質問をPOSTできないように - questionNumAlreadyExists, err := q.CheckQuestionNum(c.Request().Context(), questionnaireID, req.QuestionNum) - if err != nil { - c.Logger().Errorf("failed to check questionNum: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } else if questionNumAlreadyExists { - c.Logger().Info("questionNum already exists") - return echo.NewHTTPError(http.StatusBadRequest) - } - - switch req.QuestionType { - case "Text": - // 正規表現のチェック - if _, err := regexp.Compile(req.RegexPattern); err != nil { - c.Logger().Info("invalid regex pattern: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest) - } - case "Number": - // 数字か,min<=maxになってるか - if err := q.CheckNumberValid(req.MinBound, req.MaxBound); err != nil { - c.Logger().Info("invalid number: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - } - - lastID, err := q.InsertQuestion(c.Request().Context(), questionnaireID, req.PageNum, req.QuestionNum, req.QuestionType, req.Body, req.IsRequired) - if err != nil { - c.Logger().Errorf("failed to insert question: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - switch req.QuestionType { - case "MultipleChoice", "Checkbox", "Dropdown": - for i, v := range req.Options { - if err := q.InsertOption(c.Request().Context(), lastID, i+1, v); err != nil { - c.Logger().Errorf("failed to insert option: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - } - case "LinearScale": - if err := q.InsertScaleLabel(c.Request().Context(), lastID, - model.ScaleLabels{ - ScaleLabelLeft: req.ScaleLabelLeft, - ScaleLabelRight: req.ScaleLabelRight, - ScaleMax: req.ScaleMax, - ScaleMin: req.ScaleMin, - }); err != nil { - c.Logger().Errorf("failed to insert scale label: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - case "Text", "Number": - if err := q.InsertValidation(c.Request().Context(), lastID, - model.Validations{ - RegexPattern: req.RegexPattern, - MinBound: req.MinBound, - MaxBound: req.MaxBound, - }); err != nil { - c.Logger().Errorf("failed to insert validation: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - } - - return c.JSON(http.StatusCreated, map[string]interface{}{ - "questionID": int(lastID), - "question_type": req.QuestionType, - "question_num": req.QuestionNum, - "page_num": req.PageNum, - "body": req.Body, - "is_required": req.IsRequired, - "options": req.Options, - "scale_label_right": req.ScaleLabelRight, - "scale_label_left": req.ScaleLabelLeft, - "scale_max": req.ScaleMax, - "scale_min": req.ScaleMin, - "regex_pattern": req.RegexPattern, - "min_bound": req.MinBound, - "max_bound": req.MaxBound, - }) -} - -// EditQuestionnaire PATCH /questionnaires/:questionnaireID -func (q *Questionnaire) EditQuestionnaire(c echo.Context) error { - questionnaireID, err := getQuestionnaireID(c) - if err != nil { - c.Logger().Errorf("failed to get questionnaireID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError) - } - - req := PostAndEditQuestionnaireRequest{} - - err = c.Bind(&req) - if err != nil { - c.Logger().Infof("failed to bind PostAndEditQuestionnaireRequest: %+v", err) - 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 = validate.StructCtx(c.Request().Context(), req) - if err != nil { - c.Logger().Infof("failed to validate: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.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) - if err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { - c.Logger().Errorf("failed to update questionnaire: %+v", err) - return err - } - - err = q.DeleteTargets(ctx, questionnaireID) - if err != nil { - c.Logger().Errorf("failed to delete targets: %+v", err) - return err - } - - err = q.InsertTargets(ctx, questionnaireID, req.Targets) - if err != nil { - c.Logger().Errorf("failed to insert targets: %+v", err) - return err - } - - err = q.DeleteAdministrators(ctx, questionnaireID) - if err != nil { - c.Logger().Errorf("failed to delete administrators: %+v", err) - return err - } - - err = q.InsertAdministrators(ctx, questionnaireID, req.Administrators) - if err != nil { - c.Logger().Errorf("failed to insert administrators: %+v", err) - return err - } - - return nil - }) - if err != nil { - var httpError *echo.HTTPError - if errors.As(err, &httpError) { - return httpError - } - - c.Logger().Errorf("failed to update questionnaire: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to update a questionnaire") - } - - return c.NoContent(http.StatusOK) -} - -// DeleteQuestionnaire DELETE /questionnaires/:questionnaireID -func (q *Questionnaire) DeleteQuestionnaire(c echo.Context) error { - questionnaireID, err := getQuestionnaireID(c) - if err != nil { - c.Logger().Errorf("failed to get questionnaireID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError) - } - - err = q.ITransaction.Do(c.Request().Context(), nil, func(ctx context.Context) error { - err = q.IQuestionnaire.DeleteQuestionnaire(c.Request().Context(), questionnaireID) - if err != nil { - c.Logger().Errorf("failed to delete questionnaire: %+v", err) - return err - } - - err = q.DeleteTargets(c.Request().Context(), questionnaireID) - if err != nil { - c.Logger().Errorf("failed to delete targets: %+v", err) - return err - } - - err = q.DeleteAdministrators(c.Request().Context(), questionnaireID) - if err != nil { - c.Logger().Errorf("failed to delete administrators: %+v", err) - return err - } - - return nil - }) - if err != nil { - var httpError *echo.HTTPError - if errors.As(err, &httpError) { - return httpError - } - - c.Logger().Errorf("failed to delete questionnaire: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to delete a questionnaire") - } - - return c.NoContent(http.StatusOK) -} - -// GetQuestions GET /questionnaires/:questionnaireID/questions -func (q *Questionnaire) GetQuestions(c echo.Context) error { - strQuestionnaireID := c.Param("questionnaireID") - questionnaireID, err := strconv.Atoi(strQuestionnaireID) - if err != nil { - c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("invalid questionnaireID:%s(error: %w)", strQuestionnaireID, err)) - } - - allquestions, err := q.IQuestion.GetQuestions(c.Request().Context(), questionnaireID) - if err != nil { - c.Logger().Errorf("failed to get questions: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - if len(allquestions) == 0 { - c.Logger().Info("no questions") - return echo.NewHTTPError(http.StatusNotFound) - } - - type questionInfo struct { - QuestionID int `json:"questionID"` - PageNum int `json:"page_num"` - QuestionNum int `json:"question_num"` - QuestionType string `json:"question_type"` - Body string `json:"body"` - IsRequired bool `json:"is_required"` - CreatedAt string `json:"created_at"` - Options []string `json:"options"` - ScaleLabelRight string `json:"scale_label_right"` - ScaleLabelLeft string `json:"scale_label_left"` - ScaleMin int `json:"scale_min"` - ScaleMax int `json:"scale_max"` - RegexPattern string `json:"regex_pattern"` - MinBound string `json:"min_bound"` - MaxBound string `json:"max_bound"` - } - var ret []questionInfo - - optionIDs := []int{} - scaleLabelIDs := []int{} - validationIDs := []int{} - for _, question := range allquestions { - switch question.Type { - case "MultipleChoice", "Checkbox", "Dropdown": - optionIDs = append(optionIDs, question.ID) - case "LinearScale": - scaleLabelIDs = append(scaleLabelIDs, question.ID) - case "Text", "Number": - validationIDs = append(validationIDs, question.ID) - } - } - - options, err := q.GetOptions(c.Request().Context(), optionIDs) - if err != nil { - c.Logger().Errorf("failed to get options: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - optionMap := make(map[int][]string, len(options)) - for _, option := range options { - optionMap[option.QuestionID] = append(optionMap[option.QuestionID], option.Body) - } - - scaleLabels, err := q.GetScaleLabels(c.Request().Context(), scaleLabelIDs) - if err != nil { - c.Logger().Errorf("failed to get scale labels: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - scaleLabelMap := make(map[int]model.ScaleLabels, len(scaleLabels)) - for _, label := range scaleLabels { - scaleLabelMap[label.QuestionID] = label - } - - validations, err := q.GetValidations(c.Request().Context(), validationIDs) - if err != nil { - c.Logger().Errorf("failed to get validations: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - validationMap := make(map[int]model.Validations, len(validations)) - for _, validation := range validations { - validationMap[validation.QuestionID] = validation - } - - for _, v := range allquestions { - options := []string{} - scalelabel := model.ScaleLabels{} - validation := model.Validations{} - switch v.Type { - case "MultipleChoice", "Checkbox", "Dropdown": - var ok bool - options, ok = optionMap[v.ID] - if !ok { - options = []string{} - } - case "LinearScale": - var ok bool - scalelabel, ok = scaleLabelMap[v.ID] - if !ok { - scalelabel = model.ScaleLabels{} - } - case "Text", "Number": - var ok bool - validation, ok = validationMap[v.ID] - if !ok { - validation = model.Validations{} - } - } - - ret = append(ret, - questionInfo{ - QuestionID: v.ID, - PageNum: v.PageNum, - QuestionNum: v.QuestionNum, - QuestionType: v.Type, - Body: v.Body, - IsRequired: v.IsRequired, - CreatedAt: v.CreatedAt.Format(time.RFC3339), - Options: options, - ScaleLabelRight: scalelabel.ScaleLabelRight, - ScaleLabelLeft: scalelabel.ScaleLabelLeft, - ScaleMin: scalelabel.ScaleMin, - ScaleMax: scalelabel.ScaleMax, - RegexPattern: validation.RegexPattern, - MinBound: validation.MinBound, - MaxBound: validation.MaxBound, - }, - ) - } - - return c.JSON(http.StatusOK, ret) -} - -func createQuestionnaireMessage(questionnaireID int, title string, description string, administrators []string, resTimeLimit null.Time, targets []string) string { - var resTimeLimitText string - if resTimeLimit.Valid { - resTimeLimitText = resTimeLimit.Time.Local().Format("2006/01/02 15:04") - } else { - resTimeLimitText = "なし" - } - - var targetsMentionText string - if len(targets) == 0 { - targetsMentionText = "なし" - } else { - targetsMentionText = "@" + strings.Join(targets, " @") - } - - return fmt.Sprintf( - `### アンケート『[%s](https://anke-to.trap.jp/questionnaires/%d)』が作成されました -#### 管理者 -%s -#### 説明 -%s -#### 回答期限 -%s -#### 対象者 -%s -#### 回答リンク -https://anke-to.trap.jp/responses/new/%d`, - title, - questionnaireID, - strings.Join(administrators, ","), - description, - resTimeLimitText, - targetsMentionText, - questionnaireID, - ) -} diff --git a/router/questionnaires_test.go b/router/questionnaires_test.go deleted file mode 100644 index 8e934895..00000000 --- a/router/questionnaires_test.go +++ /dev/null @@ -1,1946 +0,0 @@ -package router - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strconv" - "strings" - "testing" - "time" - - "github.com/go-playground/validator/v10" - "github.com/golang/mock/gomock" - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/model/mock_model" - "github.com/traPtitech/anke-to/traq/mock_traq" - "gopkg.in/guregu/null.v4" -) - -func TestPostAndEditQuestionnaireValidate(t *testing.T) { - tests := []struct { - description string - request *PostAndEditQuestionnaireRequest - isErr bool - }{ - { - description: "旧クライアントの一般的なリクエストなのでエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "タイトルが空なのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - isErr: true, - }, - { - description: "タイトルが50文字なのでエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "アイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオ", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "タイトルが50文字を超えるのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "アイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオア", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - isErr: true, - }, - { - description: "descriptionが空でもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "resTimeLimitが設定されていてもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Now(), true), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "resSharedToがadministratorsでもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "administrators", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "resSharedToがrespondentsでもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "respondents", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "resSharedToがadministrators、respondents、publicのいずれでもないのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "test", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - isErr: true, - }, - { - description: "targetがnullでもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: nil, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "targetが32文字でもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{"01234567890123456789012345678901"}, - Administrators: []string{"mazrean"}, - }, - }, - { - description: "targetが32文字を超えるのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{"012345678901234567890123456789012"}, - Administrators: []string{"mazrean"}, - }, - isErr: true, - }, - { - description: "administratorsがいないのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{"01234567890123456789012345678901"}, - Administrators: []string{}, - }, - isErr: true, - }, - { - description: "administratorsがnullなのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: nil, - }, - isErr: true, - }, - { - description: "administratorsが32文字でもエラーなし", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"01234567890123456789012345678901"}, - }, - }, - { - description: "administratorsが32文字を超えるのでエラー", - request: &PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"012345678901234567890123456789012"}, - }, - isErr: true, - }, - } - - for _, test := range tests { - validate := validator.New() - - t.Run(test.description, func(t *testing.T) { - err := validate.Struct(test.request) - - if test.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestGetQuestionnaireValidate(t *testing.T) { - t.Parallel() - - tests := []struct { - description string - request *GetQuestionnairesQueryParam - isErr bool - }{ - { - description: "一般的なQueryParameterなのでエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Sortが-created_atでもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "-created_at", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Sortがtitleでもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "title", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Sortが-titleでもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "-title", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Sortがmodified_atでもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "modified_at", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Sortが-modified_atでもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "-modified_at", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Nontargetedをfalseにしてもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "a", - Page: "2", - Nontargeted: "false", - }, - }, - { - description: "Sortを空文字にしてもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "", - Search: "a", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Searchを空文字にしてもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "", - Page: "2", - Nontargeted: "true", - }, - }, - { - description: "Pageを空文字にしてもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "a", - Page: "", - Nontargeted: "true", - }, - }, - { - description: "Nontargetedを空文字にしてもエラーなし", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "a", - Page: "2", - Nontargeted: "", - }, - }, - { - description: "Pageが数字ではないのでエラー", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "a", - Page: "xx", - Nontargeted: "true", - }, - isErr: true, - }, - { - description: "Nontargetedがbool値ではないのでエラー", - request: &GetQuestionnairesQueryParam{ - Sort: "created_at", - Search: "a", - Page: "2", - Nontargeted: "arupaka", - }, - isErr: true, - }, - } - for _, test := range tests { - validate := validator.New() - - t.Run(test.description, func(t *testing.T) { - err := validate.Struct(test.request) - if test.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestPostQuestionnaire(t *testing.T) { - t.Parallel() - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - mockOption := mock_model.NewMockIOption(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockTransaction := &model.MockTransaction{} - mockWebhook := mock_traq.NewMockIWebhook(ctrl) - - questionnaire := NewQuestionnaire( - mockQuestionnaire, - mockTarget, - mockAdministrator, - mockQuestion, - mockOption, - mockScaleLabel, - mockValidation, - mockTransaction, - mockWebhook, - ) - - type expect struct { - statusCode int - } - type test struct { - description string - invalidRequest bool - request PostAndEditQuestionnaireRequest - ExecutesCreation bool - questionnaireID int - InsertQuestionnaireError error - InsertTargetsError error - InsertAdministratorsError error - PostMessageError error - expect - } - - testCases := []test{ - { - description: "リクエストの形式が誤っているので400", - invalidRequest: true, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "validationで落ちるので400", - request: PostAndEditQuestionnaireRequest{}, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "resTimeLimitが誤っているので400", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Now().Add(-24*time.Hour), true), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "InsertQuestionnaireがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - InsertQuestionnaireError: errors.New("InsertQuestionnaireError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "InsertTargetsがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - InsertTargetsError: errors.New("InsertTargetsError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "InsertAdministratorsがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - InsertAdministratorsError: errors.New("InsertAdministratorsError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "PostMessageがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - PostMessageError: errors.New("PostMessageError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "一般的なリクエストなので201", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "questionnaireIDが0でも201", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 0, - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "回答期限が設定されていてもでも201", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Now().Add(24*time.Hour), true), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.description, func(t *testing.T) { - var request io.Reader - if testCase.invalidRequest { - request = strings.NewReader("test") - } else { - buf := bytes.NewBuffer(nil) - err := json.NewEncoder(buf).Encode(testCase.request) - if err != nil { - t.Errorf("failed to encode request: %v", err) - } - - request = buf - } - - e := echo.New() - req := httptest.NewRequest(http.MethodPost, "/questionnaires", request) - rec := httptest.NewRecorder() - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - c := e.NewContext(req, rec) - - c.Set(validatorKey, validator.New()) - - if testCase.ExecutesCreation { - // 時刻は完全一致しないためその対応 - var mockTimeLimit interface{} - if testCase.request.ResTimeLimit.Valid { - mockTimeLimit = gomock.Any() - } else { - mockTimeLimit = testCase.request.ResTimeLimit - } - - mockQuestionnaire. - EXPECT(). - InsertQuestionnaire( - c.Request().Context(), - testCase.request.Title, - testCase.request.Description, - mockTimeLimit, - testCase.request.ResSharedTo, - ). - Return(testCase.questionnaireID, testCase.InsertQuestionnaireError) - - if testCase.InsertQuestionnaireError == nil { - mockTarget. - EXPECT(). - InsertTargets( - c.Request().Context(), - testCase.questionnaireID, - testCase.request.Targets, - ). - Return(testCase.InsertTargetsError) - - if testCase.InsertTargetsError == nil { - mockAdministrator. - EXPECT(). - InsertAdministrators( - c.Request().Context(), - testCase.questionnaireID, - testCase.request.Administrators, - ). - Return(testCase.InsertAdministratorsError) - - if testCase.InsertAdministratorsError == nil { - mockWebhook. - EXPECT(). - PostMessage(gomock.Any()). - Return(testCase.PostMessageError) - } - } - } - } - - e.HTTPErrorHandler(questionnaire.PostQuestionnaire(c), c) - - assert.Equal(t, testCase.expect.statusCode, rec.Code, "status code") - - if testCase.expect.statusCode == http.StatusCreated { - var questionnaire map[string]interface{} - err := json.NewDecoder(rec.Body).Decode(&questionnaire) - if err != nil { - t.Errorf("failed to decode response body: %v", err) - } - - assert.Equal(t, float64(testCase.questionnaireID), questionnaire["questionnaireID"], "questionnaireID") - assert.Equal(t, testCase.request.Title, questionnaire["title"], "title") - assert.Equal(t, testCase.request.Description, questionnaire["description"], "description") - if testCase.request.ResTimeLimit.Valid { - strResTimeLimit, ok := questionnaire["res_time_limit"].(string) - assert.True(t, ok, "res_time_limit convert") - resTimeLimit, err := time.Parse(time.RFC3339, strResTimeLimit) - assert.NoError(t, err, "res_time_limit parse") - - assert.WithinDuration(t, testCase.request.ResTimeLimit.Time, resTimeLimit, 2*time.Second, "resTimeLimit") - } else { - assert.Nil(t, questionnaire["res_time_limit"], "resTimeLimit nil") - } - assert.Equal(t, testCase.request.ResSharedTo, questionnaire["res_shared_to"], "resSharedTo") - - strCreatedAt, ok := questionnaire["created_at"].(string) - assert.True(t, ok, "created_at convert") - createdAt, err := time.Parse(time.RFC3339, strCreatedAt) - assert.NoError(t, err, "created_at parse") - assert.WithinDuration(t, time.Now(), createdAt, time.Second, "created_at") - - strModifiedAt, ok := questionnaire["modified_at"].(string) - assert.True(t, ok, "modified_at convert") - modifiedAt, err := time.Parse(time.RFC3339, strModifiedAt) - assert.NoError(t, err, "modified_at parse") - assert.WithinDuration(t, time.Now(), modifiedAt, time.Second, "modified_at") - - assert.ElementsMatch(t, testCase.request.Targets, questionnaire["targets"], "targets") - assert.ElementsMatch(t, testCase.request.Administrators, questionnaire["administrators"], "administrators") - } - }) - } -} - -func TestPostQuestionByQuestionnaireID(t *testing.T) { - t.Parallel() - - ctrl := gomock.NewController(t) - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockOption := mock_model.NewMockIOption(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockTransaction := &model.MockTransaction{} - mockWebhook := mock_traq.NewMockIWebhook(ctrl) - - questionnaire := NewQuestionnaire( - mockQuestionnaire, - mockTarget, - mockAdministrator, - mockQuestion, - mockOption, - mockScaleLabel, - mockValidation, - mockTransaction, - mockWebhook, - ) - - type expect struct { - statusCode int - } - type test struct { - description string - invalidRequest bool - request PostAndEditQuestionRequest - ExecutesCreation bool - ExecutesCheckQuestionNum bool - questionID int - questionnaireID string - validator string - questionNumExists bool - InsertQuestionError error - InsertOptionError error - InsertValidationError error - InsertScaleLabelError error - CheckNumberValid error - CheckQuestionNumError error - expect - } - testCases := []test{ - { - description: "一般的なリクエストなので201", - invalidRequest: false, - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "questionIDが0でも201", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 0, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "questionnaireIDがstringでも201", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - questionnaireID: "1", - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "QuestionTypeがMultipleChoiceでも201", - request: PostAndEditQuestionRequest{ - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "QuestionTypeがLinearScaleでも201", - request: PostAndEditQuestionRequest{ - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "QuestionTypeがNumberでも201", - request: PostAndEditQuestionRequest{ - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusCreated, - }, - }, - { - description: "QuestionTypeが存在しないものは400", - request: PostAndEditQuestionRequest{ - QuestionType: "aaa", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - InsertQuestionError: errors.New("InsertQuestionError"), - ExecutesCreation: false, - ExecutesCheckQuestionNum: false, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "InsertValidationがエラーで500", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - InsertValidationError: errors.New("InsertValidationError"), - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "CheckNumberValidがエラーで500", - request: PostAndEditQuestionRequest{ - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - CheckNumberValid: errors.New("CheckNumberValidError"), - ExecutesCreation: false, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "InsertQuestionがエラーで500", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - InsertQuestionError: errors.New("InsertQuestionError"), - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "InsertScaleLabelErrorがエラーで500", - request: PostAndEditQuestionRequest{ - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka", "mazrean"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - InsertScaleLabelError: errors.New("InsertScaleLabelError"), - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "InsertOptionErrorがエラーで500", - request: PostAndEditQuestionRequest{ - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - }, - InsertOptionError: errors.New("InsertOptionError"), - ExecutesCreation: true, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "questionnaireIDが数値ではないので400", - request: PostAndEditQuestionRequest{}, - questionnaireID: "arupaka", - ExecutesCreation: false, - ExecutesCheckQuestionNum: false, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "validatorが\"validator\"ではないので500", - request: PostAndEditQuestionRequest{}, - validator: "arupaka", - questionnaireID: "1", - ExecutesCreation: false, - ExecutesCheckQuestionNum: false, - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "正規表現が間違っているので400", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"arupaka"}, - ScaleLabelRight: "arupaka", - ScaleLabelLeft: "xxarupakaxx", - ScaleMin: 1, - ScaleMax: 2, - RegexPattern: "[[", - MinBound: "0", - MaxBound: "10", - }, - InsertQuestionError: errors.New("正規表現が間違っています"), - ExecutesCreation: false, - ExecutesCheckQuestionNum: false, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "リクエストの形式が異なっているので400", - invalidRequest: true, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "validation(妥当性確認)で落ちるので400", - request: PostAndEditQuestionRequest{}, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "CheckQuestionNumがエラーで500", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - MinBound: "0", - MaxBound: "10", - }, - ExecutesCheckQuestionNum: true, - CheckQuestionNumError: errors.New("CheckQuestionNumError"), - questionID: 1, - questionnaireID: "1", - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "questionNumは重複できないので400", - request: PostAndEditQuestionRequest{ - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - MinBound: "0", - MaxBound: "10", - }, - ExecutesCheckQuestionNum: true, - questionID: 1, - questionnaireID: "1", - questionNumExists: true, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - } - - for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { - var request io.Reader - if test.invalidRequest { - request = strings.NewReader("test") - } else { - buf := bytes.NewBuffer(nil) - err := json.NewEncoder(buf).Encode(test.request) - if err != nil { - t.Errorf("failed to encode request: %v", err) - } - - request = buf - } - - e := echo.New() - var req *http.Request - intQuestionnaireID, err := strconv.Atoi(test.questionnaireID) - if err != nil { - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/questionnaires/%s/questions", test.questionnaireID), request) - } else { - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/questionnaires/%d/questions", intQuestionnaireID), request) - } - - rec := httptest.NewRecorder() - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - c := e.NewContext(req, rec) - c.SetParamNames("questionnaireID") - - c.SetParamValues(test.questionnaireID) - - c.Set(questionnaireIDKey, test.request.QuestionnaireID) - if test.validator != "" { - c.Set(test.validator, validator.New()) - } else { - c.Set(validatorKey, validator.New()) - } - - if test.ExecutesCheckQuestionNum { - mockQuestion. - EXPECT(). - CheckQuestionNum(c.Request().Context(), intQuestionnaireID, test.request.QuestionNum). - Return(test.questionNumExists, test.CheckQuestionNumError) - } - if test.ExecutesCreation { - mockQuestion. - EXPECT(). - InsertQuestion(c.Request().Context(), intQuestionnaireID, test.request.PageNum, test.request.QuestionNum, test.request.QuestionType, test.request.Body, test.request.IsRequired). - Return(test.questionID, test.InsertQuestionError) - } - if test.InsertQuestionError == nil && test.request.QuestionType == "LinearScale" { - mockScaleLabel. - EXPECT(). - InsertScaleLabel(c.Request().Context(), test.questionID, model.ScaleLabels{ - ScaleLabelRight: test.request.ScaleLabelRight, - ScaleLabelLeft: test.request.ScaleLabelLeft, - ScaleMin: test.request.ScaleMin, - ScaleMax: test.request.ScaleMax, - }). - Return(test.InsertScaleLabelError) - } - if test.InsertQuestionError == nil && (test.request.QuestionType == "MultipleChoice" || test.request.QuestionType == "Checkbox" || test.request.QuestionType == "Dropdown") { - for i, option := range test.request.Options { - mockOption. - EXPECT(). - InsertOption(c.Request().Context(), test.questionID, i+1, option). - Return(test.InsertOptionError) - } - } - if test.request.QuestionType == "Number" { - mockValidation. - EXPECT(). - CheckNumberValid(test.request.MinBound, test.request.MaxBound). - Return(test.CheckNumberValid) - } - if test.ExecutesCreation && test.InsertQuestionError == nil && test.CheckNumberValid == nil && (test.request.QuestionType == "Text" || test.request.QuestionType == "Number") { - mockValidation. - EXPECT(). - InsertValidation(c.Request().Context(), test.questionID, model.Validations{ - RegexPattern: test.request.RegexPattern, - MinBound: test.request.MinBound, - MaxBound: test.request.MaxBound, - }). - Return(test.InsertValidationError) - } - - e.HTTPErrorHandler(questionnaire.PostQuestionByQuestionnaireID(c), c) - - assert.Equal(t, test.expect.statusCode, rec.Code, "status code") - - if test.expect.statusCode == http.StatusCreated { - var question map[string]interface{} - err := json.NewDecoder(rec.Body).Decode(&question) - if err != nil { - t.Errorf("failed to decode response body: %v", err) - } - - assert.Equal(t, float64(test.questionID), question["questionID"], "questionID") - assert.Equal(t, test.request.QuestionType, question["question_type"], "question_type") - assert.Equal(t, float64(test.request.QuestionNum), question["question_num"], "question_num") - assert.Equal(t, float64(test.request.PageNum), question["page_num"], "page_num") - assert.Equal(t, test.request.Body, question["body"], "body") - assert.Equal(t, test.request.IsRequired, question["is_required"], "is_required") - assert.ElementsMatch(t, test.request.Options, question["options"], "options") - assert.Equal(t, test.request.ScaleLabelRight, question["scale_label_right"], "scale_label_right") - assert.Equal(t, test.request.ScaleLabelLeft, question["scale_label_left"], "scale_label_left") - assert.Equal(t, float64(test.request.ScaleMax), question["scale_max"], "scale_max") - assert.Equal(t, float64(test.request.ScaleMin), question["scale_min"], "scale_min") - assert.Equal(t, test.request.RegexPattern, question["regex_pattern"], "regex_pattern") - assert.Equal(t, test.request.MinBound, question["min_bound"], "min_bound") - assert.Equal(t, test.request.MaxBound, question["max_bound"], "max_bound") - - } - }) - } -} - -func TestEditQuestionnaire(t *testing.T) { - t.Parallel() - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - mockOption := mock_model.NewMockIOption(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockTransaction := &model.MockTransaction{} - mockWebhook := mock_traq.NewMockIWebhook(ctrl) - - questionnaire := NewQuestionnaire( - mockQuestionnaire, - mockTarget, - mockAdministrator, - mockQuestion, - mockOption, - mockScaleLabel, - mockValidation, - mockTransaction, - mockWebhook, - ) - - type expect struct { - statusCode int - } - type test struct { - description string - invalidRequest bool - request PostAndEditQuestionnaireRequest - ExecutesCreation bool - questionnaireID int - InsertQuestionnaireError error - DeleteTargetsError error - InsertTargetsError error - DeleteAdministratorsError error - InsertAdministratorsError error - PostMessageError error - expect - } - - testCases := []test{ - { - description: "リクエストの形式が誤っているので400", - invalidRequest: true, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "validationで落ちるので400", - request: PostAndEditQuestionnaireRequest{}, - expect: expect{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "InsertQuestionnaireがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - InsertQuestionnaireError: errors.New("InsertQuestionnaireError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "DeleteTargetsがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - DeleteTargetsError: errors.New("DeleteTargetsError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "InsertTargetsがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - InsertTargetsError: errors.New("InsertTargetsError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "DeleteAdministratorsがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - DeleteAdministratorsError: errors.New("DeleteAdministratorsError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "InsertAdministratorsがエラーなので500", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - InsertAdministratorsError: errors.New("InsertAdministratorsError"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "一般的なリクエストなので200", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Time{}, false), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - { - description: "resTimeLimitが現在時刻より前でも200", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Now().Add(-24*time.Hour), true), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - { - description: "回答期限が設定されていてもでも200", - request: PostAndEditQuestionnaireRequest{ - Title: "第1回集会らん☆ぷろ募集アンケート", - Description: "第1回集会らん☆ぷろ参加者募集", - ResTimeLimit: null.NewTime(time.Now().Add(24*time.Hour), true), - ResSharedTo: "public", - Targets: []string{}, - Administrators: []string{"mazrean"}, - }, - ExecutesCreation: true, - questionnaireID: 1, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.description, func(t *testing.T) { - var request io.Reader - if testCase.invalidRequest { - request = strings.NewReader("test") - } else { - buf := bytes.NewBuffer(nil) - err := json.NewEncoder(buf).Encode(testCase.request) - if err != nil { - t.Errorf("failed to encode request: %v", err) - } - - request = buf - } - - e := echo.New() - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/questionnaires/%d", testCase.questionnaireID), request) - rec := httptest.NewRecorder() - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - c := e.NewContext(req, rec) - c.SetParamNames("questionnaireID") - c.SetParamValues(strconv.Itoa(testCase.questionnaireID)) - - c.Set(questionnaireIDKey, testCase.questionnaireID) - c.Set(validatorKey, validator.New()) - - if testCase.ExecutesCreation { - // 時刻は完全一致しないためその対応 - var mockTimeLimit interface{} - if testCase.request.ResTimeLimit.Valid { - mockTimeLimit = gomock.Any() - } else { - mockTimeLimit = testCase.request.ResTimeLimit - } - - mockQuestionnaire. - EXPECT(). - UpdateQuestionnaire( - c.Request().Context(), - testCase.request.Title, - testCase.request.Description, - mockTimeLimit, - testCase.request.ResSharedTo, - testCase.questionnaireID, - ). - Return(testCase.InsertQuestionnaireError) - - if testCase.InsertQuestionnaireError == nil { - mockTarget. - EXPECT(). - DeleteTargets( - c.Request().Context(), - testCase.questionnaireID, - ). - Return(testCase.DeleteTargetsError) - - if testCase.DeleteTargetsError == nil { - mockTarget. - EXPECT(). - InsertTargets( - c.Request().Context(), - testCase.questionnaireID, - testCase.request.Targets, - ). - Return(testCase.InsertTargetsError) - - if testCase.InsertTargetsError == nil { - mockAdministrator. - EXPECT(). - DeleteAdministrators( - c.Request().Context(), - testCase.questionnaireID, - ). - Return(testCase.DeleteAdministratorsError) - - if testCase.DeleteAdministratorsError == nil { - mockAdministrator. - EXPECT(). - InsertAdministrators( - c.Request().Context(), - testCase.questionnaireID, - testCase.request.Administrators, - ). - Return(testCase.InsertAdministratorsError) - } - } - } - } - } - - e.HTTPErrorHandler(questionnaire.EditQuestionnaire(c), c) - - assert.Equal(t, testCase.expect.statusCode, rec.Code, "status code") - }) - } -} - -func TestDeleteQuestionnaire(t *testing.T) { - t.Parallel() - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - mockOption := mock_model.NewMockIOption(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockTransaction := &model.MockTransaction{} - mockWebhook := mock_traq.NewMockIWebhook(ctrl) - - questionnaire := NewQuestionnaire( - mockQuestionnaire, - mockTarget, - mockAdministrator, - mockQuestion, - mockOption, - mockScaleLabel, - mockValidation, - mockTransaction, - mockWebhook, - ) - - type expect struct { - statusCode int - } - type test struct { - description string - questionnaireID int - DeleteQuestionnaireError error - DeleteTargetsError error - DeleteAdministratorsError error - expect - } - - testCases := []test{ - { - description: "エラーなしなので200", - questionnaireID: 1, - DeleteQuestionnaireError: nil, - DeleteTargetsError: nil, - DeleteAdministratorsError: nil, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - { - description: "questionnaireIDが0でも200", - questionnaireID: 0, - DeleteQuestionnaireError: nil, - DeleteTargetsError: nil, - DeleteAdministratorsError: nil, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - { - description: "DeleteQuestionnaireがエラーなので500", - questionnaireID: 1, - DeleteQuestionnaireError: errors.New("error"), - DeleteTargetsError: nil, - DeleteAdministratorsError: nil, - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "DeleteTargetsがエラーなので500", - questionnaireID: 1, - DeleteQuestionnaireError: nil, - DeleteTargetsError: errors.New("error"), - DeleteAdministratorsError: nil, - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "DeleteAdministratorsがエラーなので500", - questionnaireID: 1, - DeleteQuestionnaireError: nil, - DeleteTargetsError: nil, - DeleteAdministratorsError: errors.New("error"), - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.description, func(t *testing.T) { - e := echo.New() - req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/questionnaire/%d", testCase.questionnaireID), nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - c.SetPath("/questionnaires/:questionnaire_id") - c.SetParamNames("questionnaire_id") - c.SetParamValues(strconv.Itoa(testCase.questionnaireID)) - - c.Set(questionnaireIDKey, testCase.questionnaireID) - - mockQuestionnaire. - EXPECT(). - DeleteQuestionnaire( - c.Request().Context(), - testCase.questionnaireID, - ). - Return(testCase.DeleteQuestionnaireError) - - if testCase.DeleteQuestionnaireError == nil { - mockTarget. - EXPECT(). - DeleteTargets( - c.Request().Context(), - testCase.questionnaireID, - ). - Return(testCase.DeleteTargetsError) - - if testCase.DeleteTargetsError == nil { - mockAdministrator. - EXPECT(). - DeleteAdministrators( - c.Request().Context(), - testCase.questionnaireID, - ). - Return(testCase.DeleteAdministratorsError) - } - } - - e.HTTPErrorHandler(questionnaire.DeleteQuestionnaire(c), c) - - assert.Equal(t, testCase.expect.statusCode, rec.Code, "status code") - }) - } -} - -func TestCreateQuestionnaireMessage(t *testing.T) { - t.Parallel() - - type args struct { - questionnaireID int - title string - description string - administrators []string - resTimeLimit null.Time - targets []string - } - type expect struct { - message string - } - type test struct { - description string - args - expect - } - - tm, err := time.ParseInLocation("2006/01/02 15:04", "2021/10/01 09:06", time.Local) - if err != nil { - t.Errorf("failed to parse time: %v", err) - } - - testCases := []test{ - { - description: "通常の引数なので問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "description", - administrators: []string{"administrator1"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1 -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - description: "questionnaireIDが0でも問題なし", - args: args{ - questionnaireID: 0, - title: "title", - description: "description", - administrators: []string{"administrator1"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/0)』が作成されました -#### 管理者 -administrator1 -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/0`, - }, - }, - { - // 実際には発生しないけど念の為 - description: "titleが空文字でも問題なし", - args: args{ - questionnaireID: 1, - title: "", - description: "description", - administrators: []string{"administrator1"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1 -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - description: "説明が空文字でも問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "", - administrators: []string{"administrator1"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1 -#### 説明 - -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - description: "administrator複数人でも問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "description", - administrators: []string{"administrator1", "administrator2"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1,administrator2 -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - // 実際には発生しないけど念の為 - description: "administratorがいなくても問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "description", - administrators: []string{}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 - -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - description: "回答期限なしでも問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "description", - administrators: []string{"administrator1"}, - resTimeLimit: null.NewTime(time.Time{}, false), - targets: []string{"target1"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1 -#### 説明 -description -#### 回答期限 -なし -#### 対象者 -@target1 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - description: "対象者が複数人でも問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "description", - administrators: []string{"administrator1"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{"target1", "target2"}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1 -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -@target1 @target2 -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - { - description: "対象者がいなくても問題なし", - args: args{ - questionnaireID: 1, - title: "title", - description: "description", - administrators: []string{"administrator1"}, - resTimeLimit: null.TimeFrom(tm), - targets: []string{}, - }, - expect: expect{ - message: `### アンケート『[title](https://anke-to.trap.jp/questionnaires/1)』が作成されました -#### 管理者 -administrator1 -#### 説明 -description -#### 回答期限 -2021/10/01 09:06 -#### 対象者 -なし -#### 回答リンク -https://anke-to.trap.jp/responses/new/1`, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.description, func(t *testing.T) { - message := createQuestionnaireMessage( - testCase.args.questionnaireID, - testCase.args.title, - testCase.args.description, - testCase.args.administrators, - testCase.args.resTimeLimit, - testCase.args.targets, - ) - - assert.Equal(t, testCase.expect.message, message) - }) - } -} diff --git a/router/questions.go b/router/questions.go deleted file mode 100644 index 1b446686..00000000 --- a/router/questions.go +++ /dev/null @@ -1,158 +0,0 @@ -package router - -import ( - "errors" - "fmt" - "net/http" - "regexp" - - "github.com/labstack/echo/v4" - - "github.com/traPtitech/anke-to/model" -) - -// Question Questionの構造体 -type Question struct { - model.IValidation - model.IQuestion - model.IOption - model.IScaleLabel -} - -// NewQuestion Questionのコンストラクタ -func NewQuestion(validation model.IValidation, question model.IQuestion, option model.IOption, scaleLabel model.IScaleLabel) *Question { - return &Question{ - IValidation: validation, - IQuestion: question, - IOption: option, - IScaleLabel: scaleLabel, - } -} - -type PostAndEditQuestionRequest struct { - QuestionnaireID int `json:"questionnaireID" validate:"min=0"` - QuestionType string `json:"question_type" validate:"required,oneof=Text TextArea Number MultipleChoice Checkbox LinearScale"` - QuestionNum int `json:"question_num" validate:"min=0"` - 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=1000"` - ScaleLabelRight string `json:"scale_label_right" validate:"max=50"` - ScaleLabelLeft string `json:"scale_label_left" validate:"max=50"` - ScaleMin int `json:"scale_min"` - ScaleMax int `json:"scale_max" validate:"gtecsfield=ScaleMin"` - RegexPattern string `json:"regex_pattern"` - MinBound string `json:"min_bound" validate:"omitempty,number"` - MaxBound string `json:"max_bound" validate:"omitempty,number"` -} - -// EditQuestion PATCH /questions/:id -func (q *Question) EditQuestion(c echo.Context) error { - questionID, err := getQuestionID(c) - if err != nil { - c.Logger().Errorf("failed to get question id: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionID: %w", err)) - } - - req := PostAndEditQuestionRequest{} - - if err := c.Bind(&req); err != nil { - c.Logger().Infof("failed to bind PostAndEditQuestionRequest: %+v", err) - 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": - //正規表現のチェック - if _, err := regexp.Compile(req.RegexPattern); err != nil { - c.Logger().Infof("invalid regex pattern: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest) - } - case "Number": - //数字か,min<=maxになってるか - if err := q.CheckNumberValid(req.MinBound, req.MaxBound); err != nil { - c.Logger().Info("invalid number: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - } - - err = q.UpdateQuestion(c.Request().Context(), req.QuestionnaireID, req.PageNum, req.QuestionNum, req.QuestionType, req.Body, req.IsRequired, questionID) - if err != nil { - c.Logger().Errorf("failed to update question: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - switch req.QuestionType { - case "MultipleChoice", "Checkbox", "Dropdown": - if err := q.UpdateOptions(c.Request().Context(), req.Options, questionID); err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { - c.Logger().Errorf("failed to update options: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - case "LinearScale": - if err := q.UpdateScaleLabel(c.Request().Context(), questionID, - model.ScaleLabels{ - ScaleLabelLeft: req.ScaleLabelLeft, - ScaleLabelRight: req.ScaleLabelRight, - ScaleMax: req.ScaleMax, - ScaleMin: req.ScaleMin, - }); err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { - c.Logger().Errorf("failed to update scale label: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - case "Text", "Number": - if err := q.UpdateValidation(c.Request().Context(), questionID, - model.Validations{ - RegexPattern: req.RegexPattern, - MinBound: req.MinBound, - MaxBound: req.MaxBound, - }); err != nil && !errors.Is(err, model.ErrNoRecordUpdated) { - c.Logger().Errorf("failed to update validation: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - } - - return c.NoContent(http.StatusOK) -} - -// DeleteQuestion DELETE /questions/:id -func (q *Question) DeleteQuestion(c echo.Context) error { - questionID, err := getQuestionID(c) - if err != nil { - c.Logger().Errorf("failed to get question id: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionID: %w", err)) - } - - if err := q.IQuestion.DeleteQuestion(c.Request().Context(), questionID); err != nil { - c.Logger().Errorf("failed to delete question: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - if err := q.DeleteOptions(c.Request().Context(), questionID); err != nil { - c.Logger().Errorf("failed to delete options: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - if err := q.DeleteScaleLabel(c.Request().Context(), questionID); err != nil { - c.Logger().Errorf("failed to delete scale label: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - if err := q.DeleteValidation(c.Request().Context(), questionID); err != nil { - c.Logger().Errorf("failed to delete validation: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.NoContent(http.StatusOK) -} diff --git a/router/questions_test.go b/router/questions_test.go deleted file mode 100644 index 009f695d..00000000 --- a/router/questions_test.go +++ /dev/null @@ -1,1433 +0,0 @@ -package router - -import ( - "strings" - "testing" - - "github.com/go-playground/validator/v10" - "github.com/stretchr/testify/assert" -) - -func TestPostQuestionValidate(t *testing.T) { - t.Parallel() - - tests := []struct { - description string - request *PostAndEditQuestionRequest - isErr bool - }{ - { - description: "旧クライアントの一般的なTextタイプの質問なのでエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "questionnaireIDが0でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 0, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "questionNumが0でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 0, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "questionNumが負なのでエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: -1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "pageNumが0でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 0, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "pageNumが負なのでエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: -1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "質問文が空なのでエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "isRequiredがfalseでもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: false, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "Textタイプでoptionがnullでもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: nil, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "Textタイプでoptionが1000文字以上でもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"0" + strings.Repeat("1234567890", 100)}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "TextタイプでscaleLabelRightがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "TextタイプでscaleLabelLeftがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "TextタイプでscaleLabelRightが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "012345678901234567890123456789012345678901234567890", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "TextタイプでscaleLabelLeftが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "012345678901234567890123456789012345678901234567890", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "TextタイプでscaleMinが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: 0, - }, - }, - { - description: "TextタイプでscaleMaxが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: -1, - }, - }, - { - description: "TextタイプでscaleMaxが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 1, - }, - }, - { - description: "TextタイプでscaleMinが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 1, - }, - }, - { - description: "TextタイプでscaleMinがscaleMaxより大きいときエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "regexPatternが指定されていてもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - RegexPattern: ".*", - }, - }, - { - description: "MinBoundが設定されていてもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - MinBound: "0", - }, - }, - { - description: "MinBoundが数字でない時エラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - MinBound: "a", - }, - isErr: true, - }, - { - description: "MaxBoundが設定されていてもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - MaxBound: "0", - }, - }, - { - description: "MaxBoundが数字でない時エラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - MaxBound: "a", - }, - isErr: true, - }, - { - description: "旧クライアントの一般的なTextAreaタイプの質問なのでエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "TextAreaタイプでoptionがnullでもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: nil, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "TextAreaタイプでoptionが1000文字以上でもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"0" + strings.Repeat("1234567890", 100)}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "TextAreaタイプでscaleLabelRightがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "TextAreaタイプでscaleLabelLeftがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "TextAreaタイプでscaleLabelRightが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "012345678901234567890123456789012345678901234567890", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "TextAreaタイプでscaleLabelLeftが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "012345678901234567890123456789012345678901234567890", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "TextAreaタイプでscaleMinが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: 0, - }, - }, - { - description: "TextAreaタイプでscaleMaxが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: -1, - }, - }, - { - description: "TextAreaタイプでscaleMaxが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 1, - }, - }, - { - description: "TextAreaタイプでscaleMinが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 1, - }, - }, - { - description: "TextAreaタイプでscaleMinがscaleMaxより大きいときエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "TextArea", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "旧クライアントの一般的なNumberタイプの質問なのでエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "Numberタイプでoptionがnullでもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: nil, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "Numberタイプでoptionが1000文字以上でもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"0" + strings.Repeat("1234567890", 100)}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "NumberタイプでscaleLabelRightがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "NumberタイプでscaleLabelLeftがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "NumberタイプでscaleLabelRightが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "012345678901234567890123456789012345678901234567890", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "NumberタイプでscaleLabelLeftが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "012345678901234567890123456789012345678901234567890", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "NumberタイプでscaleMinが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: 0, - }, - }, - { - description: "NumberタイプでscaleMaxが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: -1, - }, - }, - { - description: "NumberタイプでscaleMaxが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 1, - }, - }, - { - description: "NumberタイプでscaleMinが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 1, - }, - }, - { - description: "NumberタイプでscaleMinがscaleMaxより大きいときエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Number", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "旧クライアントの一般的なCheckboxタイプの質問なのでエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"a", "b"}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "Checkboxタイプでoptionがnullでエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: nil, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "Checkboxタイプでoptionが1000文字以上でエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"0" + strings.Repeat("1234567890", 100)}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "CheckboxタイプでscaleLabelRightがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "CheckboxタイプでscaleLabelLeftがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "CheckboxタイプでscaleLabelRightが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "012345678901234567890123456789012345678901234567890", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "CheckboxタイプでscaleLabelLeftが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "012345678901234567890123456789012345678901234567890", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "CheckboxタイプでscaleMinが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: 0, - }, - }, - { - description: "CheckboxタイプでscaleMaxが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: -1, - }, - }, - { - description: "CheckboxタイプでscaleMaxが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 1, - }, - }, - { - description: "CheckboxタイプでscaleMinが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 1, - }, - }, - { - description: "CheckboxタイプでscaleMinがscaleMaxより大きいときエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Checkbox", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "旧クライアントの一般的なMultipleChoiceタイプの質問なのでエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"a", "b"}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "MultipleChoiceタイプでoptionがnullでエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: nil, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "MultipleChoiceタイプでoptionが1000文字以上でエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"0" + strings.Repeat("1234567890", 100)}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "MultipleChoiceタイプでscaleLabelRightがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "MultipleChoiceタイプでscaleLabelLeftがあってもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "MultipleChoiceタイプでscaleLabelRightが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "012345678901234567890123456789012345678901234567890", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "MultipleChoiceタイプでscaleLabelLeftが50字を超えてもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "012345678901234567890123456789012345678901234567890", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "MultipleChoiceタイプでscaleMinが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: 0, - }, - }, - { - description: "MultipleChoiceタイプでscaleMaxが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: -1, - ScaleMax: -1, - }, - }, - { - description: "MultipleChoiceタイプでscaleMaxが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 1, - }, - }, - { - description: "MultipleChoiceタイプでscaleMinが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 1, - }, - }, - { - description: "MultipleChoiceタイプでscaleMinがscaleMaxより大きいときエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "MultipleChoice", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 1, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "旧クライアントの一般的なLinearScaleタイプの質問なのでエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: 1, - ScaleMax: 5, - }, - }, - { - description: "LinearScaleタイプでoptionがnullでもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: nil, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - }, - { - description: "LinearScaleタイプでoptionが1000文字以上でもエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{"0" + strings.Repeat("1234567890", 100)}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "LinearScaleタイプでscaleLabelRightがなくてもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: false, - }, - { - description: "LinearScaleタイプでscaleLabelLeftがなくてもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: false, - }, - { - description: "LinearScaleタイプでscaleLabelLeft&Rightがなくてもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "", - ScaleLabelLeft: "", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: false, - }, - { - description: "LinearScaleタイプでscaleLabelRightが50字を超えていたらエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "012345678901234567890123456789012345678901234567890", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "LinearScaleタイプでscaleLabelLeftが50字を超えていたらエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "LinearScale", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "012345678901234567890123456789012345678901234567890", - ScaleMin: 0, - ScaleMax: 0, - }, - isErr: true, - }, - { - description: "LinearScaleタイプでscaleMinが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: -1, - ScaleMax: 0, - }, - }, - { - description: "LinearScaleタイプでscaleMaxが負でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: -1, - ScaleMax: -1, - }, - }, - { - description: "LinearScaleタイプでscaleMaxが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: 0, - ScaleMax: 1, - }, - }, - { - description: "LinearScaleタイプでscaleMinが正でもエラーなし", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: 1, - ScaleMax: 1, - }, - }, - { - description: "LinearScaleタイプでscaleMinがscaleMaxより大きいときエラー", - request: &PostAndEditQuestionRequest{ - QuestionnaireID: 1, - QuestionType: "Text", - QuestionNum: 1, - PageNum: 1, - Body: "発表タイトル", - IsRequired: true, - Options: []string{}, - ScaleLabelRight: "右", - ScaleLabelLeft: "左", - ScaleMin: 1, - ScaleMax: 0, - }, - isErr: true, - }, - } - - for _, test := range tests { - validate := validator.New() - - t.Run(test.description, func(t *testing.T) { - err := validate.Struct(test.request) - - if test.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/router/responses.go b/router/responses.go deleted file mode 100644 index fde97cfd..00000000 --- a/router/responses.go +++ /dev/null @@ -1,426 +0,0 @@ -package router - -import ( - "errors" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/labstack/echo/v4" - - "gopkg.in/guregu/null.v4" - - "github.com/traPtitech/anke-to/model" -) - -// Response Responseの構造体 -type Response struct { - model.IQuestionnaire - model.IValidation - model.IScaleLabel - model.IRespondent - model.IResponse -} - -// NewResponse Responseのコンストラクタ -func NewResponse(questionnaire model.IQuestionnaire, validation model.IValidation, scaleLabel model.IScaleLabel, respondent model.IRespondent, response model.IResponse) *Response { - return &Response{ - IQuestionnaire: questionnaire, - IValidation: validation, - IScaleLabel: scaleLabel, - IRespondent: respondent, - IResponse: response, - } -} - -// Responses 質問に対する回答一覧の構造体 -type Responses struct { - ID int `json:"questionnaireID" validate:"min=0"` - Temporarily bool `json:"temporarily"` - Body []model.ResponseBody `json:"body" validate:"required,dive"` -} - -// PostResponse POST /responses -func (r *Response) PostResponse(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - req := Responses{} - - if err := c.Bind(&req); err != nil { - c.Logger().Infof("failed to bind Responses: %+v", err) - 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 = validate.StructCtx(c.Request().Context(), req) - if err != nil { - c.Logger().Infof("failed to validate: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - limit, err := r.GetQuestionnaireLimit(c.Request().Context(), req.ID) - if err != nil { - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Info("questionnaire not found") - return echo.NewHTTPError(http.StatusNotFound, err) - } - c.Logger().Errorf("failed to get questionnaire limit: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - // 回答期限を過ぎた回答は許可しない - if limit.Valid && limit.Time.Before(time.Now()) { - c.Logger().Info("expired questionnaire") - return echo.NewHTTPError(http.StatusMethodNotAllowed) - } - - // validationsのパターンマッチ - questionIDs := make([]int, 0, len(req.Body)) - QuestionTypes := make(map[int]model.ResponseBody, len(req.Body)) - - for _, body := range req.Body { - questionIDs = append(questionIDs, body.QuestionID) - QuestionTypes[body.QuestionID] = body - } - - validations, err := r.GetValidations(c.Request().Context(), questionIDs) - if err != nil { - c.Logger().Errorf("failed to get validations: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - // パターンマッチしてエラーなら返す - for _, validation := range validations { - body := QuestionTypes[validation.QuestionID] - switch body.QuestionType { - case "Number": - if err := r.CheckNumberValidation(validation, body.Body.ValueOrZero()); err != nil { - if errors.Is(err, model.ErrInvalidNumber) { - c.Logger().Errorf("invalid number: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - c.Logger().Infof("invalid number: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - case "Text": - if err := r.CheckTextValidation(validation, body.Body.ValueOrZero()); err != nil { - if errors.Is(err, model.ErrTextMatching) { - c.Logger().Infof("invalid text: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - c.Logger().Errorf("invalid text: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - } - } - - scaleLabelIDs := []int{} - for _, body := range req.Body { - switch body.QuestionType { - case "LinearScale": - scaleLabelIDs = append(scaleLabelIDs, body.QuestionID) - } - } - - scaleLabels, err := r.GetScaleLabels(c.Request().Context(), scaleLabelIDs) - if err != nil { - c.Logger().Errorf("failed to get scale labels: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - scaleLabelMap := make(map[int]model.ScaleLabels, len(scaleLabels)) - for _, label := range scaleLabels { - scaleLabelMap[label.QuestionID] = label - } - - // LinearScaleのパターンマッチ - for _, body := range req.Body { - switch body.QuestionType { - case "LinearScale": - label, ok := scaleLabelMap[body.QuestionID] - if !ok { - label = model.ScaleLabels{} - } - if err := r.CheckScaleLabel(label, body.Body.ValueOrZero()); err != nil { - c.Logger().Infof("failed to check scale label: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - } - } - - var submittedAt time.Time - //一時保存のときはnull - if req.Temporarily { - submittedAt = time.Time{} - } else { - submittedAt = time.Now() - } - - responseID, err := r.InsertRespondent(c.Request().Context(), userID, req.ID, null.NewTime(submittedAt, !req.Temporarily)) - if err != nil { - c.Logger().Errorf("failed to insert respondent: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - responseMetas := make([]*model.ResponseMeta, 0, len(req.Body)) - for _, body := range req.Body { - switch body.QuestionType { - case "MultipleChoice", "Checkbox", "Dropdown": - for _, option := range body.OptionResponse { - responseMetas = append(responseMetas, &model.ResponseMeta{ - QuestionID: body.QuestionID, - Data: option, - }) - } - default: - responseMetas = append(responseMetas, &model.ResponseMeta{ - QuestionID: body.QuestionID, - Data: body.Body.ValueOrZero(), - }) - } - } - - if len(responseMetas) > 0 { - err = r.InsertResponses(c.Request().Context(), responseID, responseMetas) - if err != nil { - c.Logger().Errorf("failed to insert responses: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to insert responses: %w", err)) - } - } - - return c.JSON(http.StatusCreated, map[string]interface{}{ - "responseID": responseID, - "questionnaireID": req.ID, - "temporarily": req.Temporarily, - "submitted_at": submittedAt, - "body": req.Body, - }) -} - -// GetResponse GET /responses/:responseID -func (r *Response) GetResponse(c echo.Context) error { - strResponseID := c.Param("responseID") - responseID, err := strconv.Atoi(strResponseID) - if err != nil { - c.Logger().Infof("failed to convert responseID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to parse responseID(%s) to integer: %w", strResponseID, err)) - } - - respondentDetail, err := r.GetRespondentDetail(c.Request().Context(), responseID) - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("response not found: %+v", err) - return echo.NewHTTPError(http.StatusNotFound, "response not found") - } - if err != nil { - c.Logger().Errorf("failed to get respondent detail: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, respondentDetail) -} - -// EditResponse PATCH /responses/:responseID -func (r *Response) EditResponse(c echo.Context) error { - responseID, err := getResponseID(c) - if err != nil { - c.Logger().Errorf("failed to get responseID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get responseID: %w", err)) - } - - req := Responses{} - if err := c.Bind(&req); err != nil { - c.Logger().Infof("failed to bind Responses: %+v", err) - 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) { - c.Logger().Infof("questionnaire not found: %+v", err) - return echo.NewHTTPError(http.StatusNotFound, err) - } - c.Logger().Errorf("failed to get questionnaire limit: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - // 回答期限を過ぎた回答は許可しない - if limit.Valid && limit.Time.Before(time.Now()) { - c.Logger().Info("expired questionnaire") - return echo.NewHTTPError(http.StatusMethodNotAllowed) - } - - // validationsのパターンマッチ - questionIDs := make([]int, 0, len(req.Body)) - QuestionTypes := make(map[int]model.ResponseBody, len(req.Body)) - - for _, body := range req.Body { - questionIDs = append(questionIDs, body.QuestionID) - QuestionTypes[body.QuestionID] = body - } - - validations, err := r.GetValidations(c.Request().Context(), questionIDs) - if err != nil { - c.Logger().Errorf("failed to get validations: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - // パターンマッチしてエラーなら返す - for _, validation := range validations { - body := QuestionTypes[validation.QuestionID] - switch body.QuestionType { - case "Number": - if err := r.CheckNumberValidation(validation, body.Body.ValueOrZero()); err != nil { - if errors.Is(err, model.ErrInvalidNumber) { - c.Logger().Errorf("invalid number: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - c.Logger().Infof("invalid number: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - case "Text": - if err := r.CheckTextValidation(validation, body.Body.ValueOrZero()); err != nil { - if errors.Is(err, model.ErrTextMatching) { - c.Logger().Infof("invalid text: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - c.Logger().Errorf("invalid text: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - } - } - - scaleLabelIDs := []int{} - for _, body := range req.Body { - switch body.QuestionType { - case "LinearScale": - scaleLabelIDs = append(scaleLabelIDs, body.QuestionID) - } - } - - scaleLabels, err := r.GetScaleLabels(c.Request().Context(), scaleLabelIDs) - if err != nil { - c.Logger().Errorf("failed to get scale labels: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - scaleLabelMap := make(map[int]model.ScaleLabels, len(scaleLabels)) - for _, label := range scaleLabels { - scaleLabelMap[label.QuestionID] = label - } - - // LinearScaleのパターンマッチ - for _, body := range req.Body { - switch body.QuestionType { - case "LinearScale": - label, ok := scaleLabelMap[body.QuestionID] - if !ok { - label = model.ScaleLabels{} - } - if err := r.CheckScaleLabel(label, body.Body.ValueOrZero()); err != nil { - c.Logger().Infof("invalid scale label: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err) - } - } - } - - if !req.Temporarily { - err := r.UpdateSubmittedAt(c.Request().Context(), responseID) - if err != nil { - c.Logger().Errorf("failed to update submitted at: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to update sbmitted_at: %w", err)) - } - } - - //全消し&追加(レコード数爆発しそう) - if err := r.IResponse.DeleteResponse(c.Request().Context(), responseID); err != nil && !errors.Is(err, model.ErrNoRecordDeleted) { - c.Logger().Errorf("failed to delete response: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - responseMetas := make([]*model.ResponseMeta, 0, len(req.Body)) - for _, body := range req.Body { - switch body.QuestionType { - case "MultipleChoice", "Checkbox", "Dropdown": - for _, option := range body.OptionResponse { - responseMetas = append(responseMetas, &model.ResponseMeta{ - QuestionID: body.QuestionID, - Data: option, - }) - } - default: - responseMetas = append(responseMetas, &model.ResponseMeta{ - QuestionID: body.QuestionID, - Data: body.Body.ValueOrZero(), - }) - } - } - - if len(responseMetas) > 0 { - err = r.InsertResponses(c.Request().Context(), responseID, responseMetas) - if err != nil { - c.Logger().Errorf("failed to insert responses: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to insert responses: %w", err)) - } - } - - return c.NoContent(http.StatusOK) -} - -// DeleteResponse DELETE /responses/:responseID -func (r *Response) DeleteResponse(c echo.Context) error { - responseID, err := getResponseID(c) - if err != nil { - c.Logger().Errorf("failed to get response id: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get responseID: %w", err)) - } - - limit, err := r.GetQuestionnaireLimitByResponseID(c.Request().Context(), responseID) - if err != nil { - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Infof("response not found: %+v", err) - return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("failed to find limit of responseID:%d(error: %w)", responseID, err)) - } - c.Logger().Errorf("failed to get questionnaire limit: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get limit of responseID:%d(error: %w)", responseID, err)) - } - - // 回答期限を過ぎた回答の削除は許可しない - if limit.Valid && limit.Time.Before(time.Now()) { - c.Logger().Info("expired response") - return echo.NewHTTPError(http.StatusMethodNotAllowed) - } - - err = r.DeleteRespondent(c.Request().Context(), responseID) - if err != nil { - c.Logger().Errorf("failed to delete respondent: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - err = r.IResponse.DeleteResponse(c.Request().Context(), responseID) - if err != nil && !errors.Is(err, model.ErrNoRecordDeleted) { - c.Logger().Errorf("failed to delete response: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.NoContent(http.StatusOK) -} diff --git a/router/responses_test.go b/router/responses_test.go deleted file mode 100644 index 2754bfbb..00000000 --- a/router/responses_test.go +++ /dev/null @@ -1,1879 +0,0 @@ -package router - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - - "github.com/go-playground/validator/v10" - - "net/http" - "net/http/httptest" - "strconv" - "testing" - "time" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/golang/mock/gomock" - "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/model/mock_model" - "gopkg.in/guregu/null.v4" -) - -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=1000"` -} - -func TestPostResponseValidate(t *testing.T) { - t.Parallel() - - tests := []struct { - description string - request *Responses - isErr bool - }{ - { - description: "一般的なリクエストなのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - }, - { - description: "IDが0でもエラーなし", - request: &Responses{ - ID: 0, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - }, - { - description: "BodyのQuestionIDが0でもエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 0, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - }, - { - description: "ResponsesのIDが負なのでエラー", - request: &Responses{ - ID: -1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - isErr: true, - }, - { - description: "Temporarilyがtrueでもエラーなし", - request: &Responses{ - ID: 1, - Temporarily: true, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - isErr: false, - }, - { - description: "Bodyがnilなのでエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: nil, - }, - isErr: true, - }, - { - description: "BodyのQuestionIDが負なのでエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: -1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - isErr: true, - }, - { - description: "TextタイプでoptionResponseが1000文字以上でエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, - }, - }, - }, - isErr: true, - }, - { - description: "TextタイプでoptionResponseが1000文字ピッタリはエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.String{}, - OptionResponse: []string{strings.Repeat("1234567890", 100)}, - }, - }, - }, - }, - { - description: "一般的なTextAreaタイプの回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "TextArea", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - }, - { - description: "TextAreaタイプでoptionResponseが1000文字以上でもエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "TextArea", - Body: null.String{}, - OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, - }, - }, - }, - isErr: true, - }, - { - description: "TextAreaタイプでoptionResponseが1000文字ピッタリはエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "TextArea", - Body: null.String{}, - OptionResponse: []string{strings.Repeat("1234567890", 100)}, - }, - }, - }, - }, - { - description: "一般的なNumberタイプの回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Number", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - }, - { - description: "NumberタイプでoptionResponseが1000文字以上でもエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Number", - Body: null.String{}, - OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, - }, - }, - }, - isErr: true, - }, - { - description: "NumberタイプでoptionResponseが1000文字ピッタリでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Number", - Body: null.String{}, - OptionResponse: []string{strings.Repeat("1234567890", 100)}, - }, - }, - }, - }, - { - description: "Checkboxタイプで一般的な回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Checkbox", - Body: null.String{}, - OptionResponse: []string{"a", "b"}, - }, - }, - }, - }, - { - description: "Checkboxタイプで選択しなくてもエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Checkbox", - Body: null.String{}, - OptionResponse: []string{}, - }, - }, - }, - }, - { - description: "CheckboxタイプでOptionResponseがnilな回答なのでエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Checkbox", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - isErr: true, - }, - { - description: "CheckboxタイプでOptionResponseが1000文字以上な回答なのでエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Checkbox", - Body: null.String{}, - OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, - }, - }, - }, - isErr: true, - }, - { - description: "CheckboxタイプでOptionResponseが1000文字ピッタリな回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Checkbox", - Body: null.String{}, - OptionResponse: []string{strings.Repeat("1234567890", 100)}, - }, - }, - }, - }, - { - description: "MultipleChoiceタイプで一般的な回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "MultipleChoice", - Body: null.String{}, - OptionResponse: []string{"a", "b"}, - }, - }, - }, - }, - { - description: "MultipleChoiceタイプでOptionResponseがnilな回答なのでエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "MultipleChoice", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - isErr: true, - }, - { - description: "MultipleChoiceタイプでOptionResponseが1000文字以上な回答なのでエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "MultipleChoice", - Body: null.String{}, - OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, - }, - }, - }, - isErr: true, - }, - { - description: "MultipleChoiceタイプでOptionResponseが1000文字ピッタリな回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "MultipleChoice", - Body: null.String{}, - OptionResponse: []string{strings.Repeat("1234567890", 100)}, - }, - }, - }, - }, - { - description: "一般的なLinearScaleタイプの回答なのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "LinearScale", - Body: null.String{}, - OptionResponse: nil, - }, - }, - }, - }, - { - description: "LinearScaleタイプでoptionResponseが1000文字以上でもエラー", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "LinearScale", - Body: null.String{}, - OptionResponse: []string{"0" + strings.Repeat("1234567890", 100)}, - }, - }, - }, - isErr: true, - }, - { - description: "LinearScaleタイプでoptionResponseが1000文字ピッタリなのでエラーなし", - request: &Responses{ - ID: 1, - Temporarily: false, - Body: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "LinearScale", - Body: null.String{}, - OptionResponse: []string{strings.Repeat("1234567890", 100)}, - }, - }, - }, - }, - } - - for _, test := range tests { - validate := validator.New() - t.Run(test.description, func(t *testing.T) { - err := validate.Struct(test.request) - - if test.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestPostResponse(t *testing.T) { - type responseRequestBody struct { - QuestionnaireID int `json:"questionnaireID" validate:"min=0"` - Temporarily bool `json:"temporarily"` - Body []responseBody `json:"body" validate:"required"` - Submitted_at time.Time `json:"submitted_at"` - } - type responseResponseBody struct { - Body []responseBody `json:"body" validate:"required"` - QuestionnaireID int `json:"questionnaireID" validate:"min=0"` - ResponseID int `json:"responseID" validate:"min=0"` - Temporarily bool `json:"temporarily"` - Submitted_at time.Time `json:"submitted_at"` - } - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - questionnaireIDSuccess := 1 - questionIDSuccess := 1 - responseIDSuccess := 1 - - questionnaireIDFailure := 0 - questionIDFailure := 0 - responseIDFailure := 0 - - questionnaireIDLimit := 2 - - validation := - model.Validations{ - QuestionID: questionIDSuccess, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - } - scalelabel := - model.ScaleLabels{ - QuestionID: questionIDSuccess, - ScaleLabelRight: "そう思わない", - ScaleLabelLeft: "そう思う", - ScaleMin: 1, - ScaleMax: 5, - } - // questionnaireIDNotFound := -1 - // questionIDNotFound := -1 - // responseIDNotFound := -1 - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockResponse := mock_model.NewMockIResponse(ctrl) - - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - r := NewResponse( - mockQuestionnaire, - mockValidation, - mockScaleLabel, - mockRespondent, - mockResponse, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - // Questionnaire - // GetQuestionnaireLimit - // success - mockQuestionnaire.EXPECT(). - GetQuestionnaireLimit(gomock.Any(), questionnaireIDSuccess). - Return(null.TimeFrom(nowTime.Add(time.Minute)), nil).AnyTimes() - // failure - mockQuestionnaire.EXPECT(). - GetQuestionnaireLimit(gomock.Any(), questionnaireIDFailure). - Return(null.NewTime(time.Time{}, false), model.ErrRecordNotFound).AnyTimes() - // limit - mockQuestionnaire.EXPECT(). - GetQuestionnaireLimit(gomock.Any(), questionnaireIDLimit). - Return(null.TimeFrom(nowTime.Add(-time.Minute)), nil).AnyTimes() - - // Validation - // GetValidations - // success - mockValidation.EXPECT(). - GetValidations(gomock.Any(), []int{questionIDSuccess}). - Return([]model.Validations{validation}, nil).AnyTimes() - // failure - mockValidation.EXPECT(). - GetValidations(gomock.Any(), []int{questionIDFailure}). - Return([]model.Validations{}, nil).AnyTimes() - // nothing - mockValidation.EXPECT(). - GetValidations(gomock.Any(), []int{}). - Return([]model.Validations{}, nil).AnyTimes() - // CheckNumberValidation - // success - mockValidation.EXPECT(). - CheckNumberValidation(validation, "success case"). - Return(nil).AnyTimes() - // ErrInvalidNumber - mockValidation.EXPECT(). - CheckNumberValidation(validation, "ErrInvalidNumber"). - Return(model.ErrInvalidNumber).AnyTimes() - // BadRequest - mockValidation.EXPECT(). - CheckNumberValidation(validation, "BadRequest"). - Return(errMock).AnyTimes() - - // CheckTextValidation - // success - mockValidation.EXPECT(). - CheckTextValidation(validation, "success case"). - Return(nil).AnyTimes() - // ErrTextMatching - mockValidation.EXPECT(). - CheckTextValidation(validation, "ErrTextMatching"). - Return(model.ErrTextMatching).AnyTimes() - // InternalServerError - mockValidation.EXPECT(). - CheckTextValidation(validation, "InternalServerError"). - Return(errMock).AnyTimes() - - // ScaleLabel - // GetScaleLabels - // success - mockScaleLabel.EXPECT(). - GetScaleLabels(gomock.Any(), []int{questionIDSuccess}). - Return([]model.ScaleLabels{scalelabel}, nil).AnyTimes() - // failure - mockScaleLabel.EXPECT(). - GetScaleLabels(gomock.Any(), []int{questionIDFailure}). - Return([]model.ScaleLabels{}, nil).AnyTimes() - // nothing - mockScaleLabel.EXPECT(). - GetScaleLabels(gomock.Any(), []int{}). - Return([]model.ScaleLabels{}, nil).AnyTimes() - - // CheckScaleLabel - // success - mockScaleLabel.EXPECT(). - CheckScaleLabel(scalelabel, "success case"). - Return(nil).AnyTimes() - // BadRequest - mockScaleLabel.EXPECT(). - CheckScaleLabel(scalelabel, "BadRequest"). - Return(errMock).AnyTimes() - - // Respondent - // InsertRespondent - // success - mockRespondent.EXPECT(). - InsertRespondent(gomock.Any(), string(userOne), questionnaireIDSuccess, gomock.Any()). - Return(responseIDSuccess, nil).AnyTimes() - // failure - mockRespondent.EXPECT(). - InsertRespondent(gomock.Any(), string(userOne), questionnaireIDFailure, gomock.Any()). - Return(responseIDFailure, nil).AnyTimes() - - // Response - // InsertResponses - // success - mockResponse.EXPECT(). - InsertResponses(gomock.Any(), responseIDSuccess, gomock.Any()). - Return(nil).AnyTimes() - // failure - mockResponse.EXPECT(). - InsertResponses(gomock.Any(), responseIDFailure, gomock.Any()). - Return(errMock).AnyTimes() - - // responseID, err := mockRespondent. - // InsertRespondent(string(userOne), 1, null.NewTime(nowTime, true)) - // assertion.Equal(1, responseID) - // assertion.NoError(err) - - type request struct { - user users - isBadRequestBody bool - requestBody responseRequestBody - } - type expect struct { - isErr bool - code int - responseID int - } - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "true Temporarily", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: true, - Submitted_at: time.Time{}, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "null submittedat", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "empty body", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{}, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "questionnaire does not exist", - request: request{ - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDFailure, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "bad request body", - request: request{ - isBadRequestBody: true, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "limit exceeded", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDLimit, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{}, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusMethodNotAllowed, - }, - }, - { - description: "valid number", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Number", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "invalid number", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Number", - Body: null.StringFrom("ErrInvalidNumber"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - { - description: "BadRequest number", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Number", - Body: null.StringFrom("BadRequest"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "valid text", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "text does not match", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("ErrTextMatching"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "invalid text", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("InternalServerError"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - { - description: "valid LinearScale", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Submitted_at: time.Now(), - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "LinearScale", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusCreated, - responseID: responseIDSuccess, - }, - }, - { - description: "invalid LinearScale", - request: request{ - user: userOne, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Submitted_at: time.Now(), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "LinearScale", - Body: null.StringFrom("BadRequest"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - } - - e := echo.New() - e.POST("/api/responses", r.PostResponse, m.SetUserIDMiddleware, m.SetValidatorMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - requestByte, jsonErr := json.Marshal(testCase.request.requestBody) - require.NoError(t, jsonErr) - requestStr := string(requestByte) + "\n" - - if testCase.request.isBadRequestBody { - requestStr = "badRequestBody" - } - rec := createRecorder(e, testCase.request.user, methodPost, makePath("/responses"), typeJSON, requestStr) - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - response := responseResponseBody{ - ResponseID: testCase.expect.responseID, - QuestionnaireID: testCase.request.requestBody.QuestionnaireID, - Temporarily: testCase.request.requestBody.Temporarily, - Body: testCase.request.requestBody.Body, - Submitted_at: testCase.request.requestBody.Submitted_at, - } - var resActual responseResponseBody - - err := json.NewDecoder(rec.Body).Decode(&resActual) - if err != nil { - t.Errorf("failed to decode response body: %v", err) - } - assertion.Equal(response.ResponseID, resActual.ResponseID, "ResponseID") - assertion.Equal(response.QuestionnaireID, resActual.QuestionnaireID, "QuestionnaireID") - assertion.Equal(response.Temporarily, response.Temporarily, "Temporarily") - assertion.Equal(response.Body, resActual.Body, "Body") - assertion.WithinDuration(response.Submitted_at, resActual.Submitted_at, time.Second*2, "submitted_at") - } -} - -func TestGetResponse(t *testing.T) { - - type responseResponseBody struct { - QuestionnaireID int `json:"questionnaireID"` - SubmittedAt null.Time `json:"submitted_at"` - ModifiedAt null.Time `json:"modified_at"` - Body []responseBody `json:"body"` - } - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - responseIDSuccess := 1 - responseIDFailure := 0 - responseIDNotFound := -1 - - questionnaireIDSuccess := 1 - questionIDSuccess := 1 - respondentDetail := model.RespondentDetail{ - QuestionnaireID: questionnaireIDSuccess, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - Responses: []model.ResponseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("回答"), - OptionResponse: []string{}, - }, - }, - } - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockResponse := mock_model.NewMockIResponse(ctrl) - - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - r := NewResponse( - mockQuestionnaire, - mockValidation, - mockScaleLabel, - mockRespondent, - mockResponse, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - - // Respondent - // InsertRespondent - // success - mockRespondent.EXPECT(). - GetRespondentDetail(gomock.Any(), responseIDSuccess). - Return(respondentDetail, nil).AnyTimes() - // failure - mockRespondent.EXPECT(). - GetRespondentDetail(gomock.Any(), responseIDFailure). - Return(model.RespondentDetail{}, errMock).AnyTimes() - // NotFound - mockRespondent.EXPECT(). - GetRespondentDetail(gomock.Any(), responseIDNotFound). - Return(model.RespondentDetail{}, model.ErrRecordNotFound).AnyTimes() - - type request struct { - user users - responseID int - } - type expect struct { - isErr bool - code int - response responseResponseBody - } - - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - responseID: responseIDSuccess, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: responseResponseBody{ - QuestionnaireID: questionnaireIDSuccess, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: null.TimeFrom(nowTime), - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("回答"), - OptionResponse: []string{}, - }, - }, - }, - }, - }, - { - description: "failure", - request: request{ - responseID: responseIDFailure, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - { - description: "NotFound", - request: request{ - responseID: responseIDNotFound, - }, - expect: expect{ - isErr: true, - code: http.StatusNotFound, - }, - }, - } - - e := echo.New() - e.GET("/api/responses/:responseID", r.GetResponse, m.SetUserIDMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - - rec := createRecorder(e, testCase.request.user, methodGet, fmt.Sprint(rootPath, "/responses/", testCase.request.responseID), typeNone, "") - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - responseByte, jsonErr := json.Marshal(testCase.expect.response) - require.NoError(t, jsonErr) - responseStr := string(responseByte) + "\n" - assertion.Equal(responseStr, rec.Body.String(), testCase.description, "responseBody") - } -} - -func TestEditResponse(t *testing.T) { - type responseRequestBody struct { - QuestionnaireID int `json:"questionnaireID"` - Temporarily bool `json:"temporarily"` - Body []responseBody `json:"body"` - } - type responseResponseBody struct { - Body []responseBody `json:"body"` - QuestionnaireID int `json:"questionnaireID"` - ResponseID int `json:"responseID"` - Temporarily bool `json:"temporarily"` - } - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - questionnaireIDSuccess := 1 - questionIDSuccess := 1 - responseIDSuccess := 1 - - questionnaireIDFailure := 0 - questionIDFailure := 0 - responseIDFailure := 0 - - questionnaireIDLimit := 2 - - validation := - model.Validations{ - QuestionID: questionIDSuccess, - RegexPattern: "^\\d*\\.\\d*$", - MinBound: "0", - MaxBound: "10", - } - scalelabel := - model.ScaleLabels{ - QuestionID: questionIDSuccess, - ScaleLabelRight: "そう思わない", - ScaleLabelLeft: "そう思う", - ScaleMin: 1, - ScaleMax: 5, - } - // questionnaireIDNotFound := -1 - // questionIDNotFound := -1 - // responseIDNotFound := -1 - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockResponse := mock_model.NewMockIResponse(ctrl) - - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - r := NewResponse( - mockQuestionnaire, - mockValidation, - mockScaleLabel, - mockRespondent, - mockResponse, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - // Questionnaire - // GetQuestionnaireLimit - // success - mockQuestionnaire.EXPECT(). - GetQuestionnaireLimit(gomock.Any(), questionnaireIDSuccess). - Return(null.TimeFrom(nowTime.Add(time.Minute)), nil).AnyTimes() - // failure - mockQuestionnaire.EXPECT(). - GetQuestionnaireLimit(gomock.Any(), questionnaireIDFailure). - Return(null.NewTime(time.Time{}, false), errMock).AnyTimes() - // limit - mockQuestionnaire.EXPECT(). - GetQuestionnaireLimit(gomock.Any(), questionnaireIDLimit). - Return(null.TimeFrom(nowTime.Add(-time.Minute)), nil).AnyTimes() - - // Validation - // GetValidations - // success - mockValidation.EXPECT(). - GetValidations(gomock.Any(), []int{questionIDSuccess}). - Return([]model.Validations{validation}, nil).AnyTimes() - // failure - mockValidation.EXPECT(). - GetValidations(gomock.Any(), []int{questionIDFailure}). - Return([]model.Validations{}, nil).AnyTimes() - // nothing - mockValidation.EXPECT(). - GetValidations(gomock.Any(), []int{}). - Return([]model.Validations{}, nil).AnyTimes() - // CheckNumberValidation - // success - mockValidation.EXPECT(). - CheckNumberValidation(validation, "success case"). - Return(nil).AnyTimes() - // ErrInvalidNumber - mockValidation.EXPECT(). - CheckNumberValidation(validation, "ErrInvalidNumber"). - Return(model.ErrInvalidNumber).AnyTimes() - // BadRequest - mockValidation.EXPECT(). - CheckNumberValidation(validation, "BadRequest"). - Return(errMock).AnyTimes() - - // CheckTextValidation - // success - mockValidation.EXPECT(). - CheckTextValidation(validation, "success case"). - Return(nil).AnyTimes() - // ErrTextMatching - mockValidation.EXPECT(). - CheckTextValidation(validation, "ErrTextMatching"). - Return(model.ErrTextMatching).AnyTimes() - // InternalServerError - mockValidation.EXPECT(). - CheckTextValidation(validation, "InternalServerError"). - Return(errMock).AnyTimes() - - // ScaleLabel - // GetScaleLabels - // success - mockScaleLabel.EXPECT(). - GetScaleLabels(gomock.Any(), []int{questionIDSuccess}). - Return([]model.ScaleLabels{scalelabel}, nil).AnyTimes() - // failure - mockScaleLabel.EXPECT(). - GetScaleLabels(gomock.Any(), []int{questionIDFailure}). - Return([]model.ScaleLabels{}, nil).AnyTimes() - // nothing - mockScaleLabel.EXPECT(). - GetScaleLabels(gomock.Any(), []int{}). - Return([]model.ScaleLabels{}, nil).AnyTimes() - - // CheckScaleLabel - // success - mockScaleLabel.EXPECT(). - CheckScaleLabel(scalelabel, "success case"). - Return(nil).AnyTimes() - // BadRequest - mockScaleLabel.EXPECT(). - CheckScaleLabel(scalelabel, "BadRequest"). - Return(errMock).AnyTimes() - - // Respondent - // InsertRespondent - // success - mockRespondent.EXPECT(). - InsertRespondent(gomock.Any(), string(userOne), questionnaireIDSuccess, gomock.Any()). - Return(responseIDSuccess, nil).AnyTimes() - // failure - mockRespondent.EXPECT(). - InsertRespondent(gomock.Any(), string(userOne), questionnaireIDFailure, gomock.Any()). - Return(responseIDFailure, nil).AnyTimes() - // UpdateSubmittedAt - // success - mockRespondent.EXPECT(). - UpdateSubmittedAt(gomock.Any(), gomock.Any()). - Return(nil).AnyTimes() - - // Response - // InsertResponses - // success - mockResponse.EXPECT(). - InsertResponses(gomock.Any(), responseIDSuccess, gomock.Any()). - Return(nil).AnyTimes() - // failure - mockResponse.EXPECT(). - InsertResponses(gomock.Any(), responseIDFailure, gomock.Any()). - Return(errMock).AnyTimes() - // DeleteResponse - // success - mockResponse.EXPECT(). - DeleteResponse(gomock.Any(), responseIDSuccess). - Return(nil).AnyTimes() - // failure - mockResponse.EXPECT(). - DeleteResponse(gomock.Any(), responseIDFailure). - Return(model.ErrNoRecordDeleted).AnyTimes() - - // responseID, err := mockRespondent. - // InsertRespondent(string(userOne), 1, null.NewTime(nowTime, true)) - // assertion.Equal(1, responseID) - // assertion.NoError(err) - - type request struct { - user users - responseID int - isBadRequestBody bool - requestBody responseRequestBody - } - type expect struct { - isErr bool - code int - } - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - }, - }, - { - description: "true Temporarily", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: true, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - }, - }, - { - description: "empty body", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{}, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - }, - }, - { - description: "bad request body", - request: request{ - isBadRequestBody: true, - responseID: responseIDSuccess, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "limit exceeded", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDLimit, - Temporarily: false, - Body: []responseBody{}, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusMethodNotAllowed, - }, - }, - { - description: "valid number", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Number", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - }, - }, - { - description: "invalid number", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Number", - Body: null.StringFrom("ErrInvalidNumber"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - { - description: "BadRequest number", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Number", - Body: null.StringFrom("BadRequest"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "valid text", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - }, - }, - { - description: "text does not match", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("ErrTextMatching"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "invalid text", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("InternalServerError"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - { - description: "valid LinearScale", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "LinearScale", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - }, - }, - { - description: "invalid LinearScale", - request: request{ - user: userOne, - responseID: responseIDSuccess, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "LinearScale", - Body: null.StringFrom("BadRequest"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - { - description: "response does not exist", - request: request{ - user: userOne, - responseID: responseIDFailure, - requestBody: responseRequestBody{ - QuestionnaireID: questionnaireIDSuccess, - Temporarily: false, - Body: []responseBody{ - { - QuestionID: questionIDSuccess, - QuestionType: "Text", - Body: null.StringFrom("success case"), - OptionResponse: []string{}, - }, - }, - }, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, //middlewareで弾くので500で良い - }, - }, - } - - e := echo.New() - 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 { - return c.JSON(http.StatusBadRequest, "responseID is not number") - } - - c.Set(responseIDKey, responseID) - return next(c) - } - }) - - for _, testCase := range testCases { - requestByte, jsonErr := json.Marshal(testCase.request.requestBody) - require.NoError(t, jsonErr) - requestStr := string(requestByte) + "\n" - - if testCase.request.isBadRequestBody { - requestStr = "badRequestBody" - } - rec := createRecorder(e, testCase.request.user, methodPatch, makePath(fmt.Sprint("/responses/", testCase.request.responseID)), typeJSON, requestStr) - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - } -} - -func TestDeleteResponse(t *testing.T) { - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockValidation := mock_model.NewMockIValidation(ctrl) - mockScaleLabel := mock_model.NewMockIScaleLabel(ctrl) - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockResponse := mock_model.NewMockIResponse(ctrl) - - r := NewResponse( - mockQuestionnaire, - mockValidation, - mockScaleLabel, - mockRespondent, - mockResponse, - ) - - type request struct { - QuestionnaireLimit null.Time - GetQuestionnaireLimitError error - ExecutesDeletion bool - DeleteRespondentError error - DeleteResponseError error - } - type expect struct { - statusCode int - } - type test struct { - description string - request - expect - } - - testCases := []test{ - { - description: "期限が設定されていない、かつDeleteRespondentがエラーなしなので200", - request: request{ - QuestionnaireLimit: null.NewTime(time.Time{}, false), - GetQuestionnaireLimitError: nil, - ExecutesDeletion: true, - DeleteRespondentError: nil, - DeleteResponseError: nil, - }, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - { - description: "期限前、かつDeleteRespondentがエラーなしなので200", - request: request{ - QuestionnaireLimit: null.NewTime(time.Now().AddDate(0, 0, 1), true), - GetQuestionnaireLimitError: nil, - ExecutesDeletion: true, - DeleteRespondentError: nil, - DeleteResponseError: nil, - }, - expect: expect{ - statusCode: http.StatusOK, - }, - }, - { - description: "期限後なので405", - request: request{ - QuestionnaireLimit: null.NewTime(time.Now().AddDate(0, 0, -1), true), - GetQuestionnaireLimitError: nil, - ExecutesDeletion: false, - DeleteRespondentError: nil, - DeleteResponseError: nil, - }, - expect: expect{ - statusCode: http.StatusMethodNotAllowed, - }, - }, - { - description: "GetQuestionnaireLimitByResponseIDがエラーRecordNotFoundを吐くので404", - request: request{ - QuestionnaireLimit: null.NewTime(time.Time{}, false), - GetQuestionnaireLimitError: model.ErrRecordNotFound, - ExecutesDeletion: false, - DeleteRespondentError: nil, - DeleteResponseError: nil, - }, - expect: expect{ - statusCode: http.StatusNotFound, - }, - }, - { - description: "GetQuestionnaireLimitByResponseIDがエラーを吐くので500", - request: request{ - QuestionnaireLimit: null.NewTime(time.Time{}, false), - GetQuestionnaireLimitError: errors.New("error"), - ExecutesDeletion: false, - DeleteRespondentError: nil, - DeleteResponseError: nil, - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "DeleteRespondentがエラーを吐くので500", - request: request{ - QuestionnaireLimit: null.NewTime(time.Time{}, false), - GetQuestionnaireLimitError: nil, - ExecutesDeletion: true, - DeleteRespondentError: errors.New("error"), - DeleteResponseError: nil, - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "DeleteResponseがエラーを吐くので500", - request: request{ - QuestionnaireLimit: null.NewTime(time.Time{}, false), - GetQuestionnaireLimitError: nil, - ExecutesDeletion: true, - DeleteRespondentError: nil, - DeleteResponseError: errors.New("error"), - }, - expect: expect{ - statusCode: http.StatusInternalServerError, - }, - }, - } - - for _, testCase := range testCases { - userID := "userID1" - responseID := 1 - - e := echo.New() - req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/responses/%d", responseID), nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - c.SetPath("/responses/:responseID") - c.SetParamNames("responseID") - c.SetParamValues(strconv.Itoa(responseID)) - c.Set(userIDKey, userID) - c.Set(responseIDKey, responseID) - - mockQuestionnaire. - EXPECT(). - GetQuestionnaireLimitByResponseID(gomock.Any(), responseID). - Return(testCase.request.QuestionnaireLimit, testCase.request.GetQuestionnaireLimitError) - if testCase.request.ExecutesDeletion { - mockRespondent. - EXPECT(). - DeleteRespondent(gomock.Any(), responseID). - Return(testCase.request.DeleteRespondentError) - if testCase.request.DeleteRespondentError == nil { - mockResponse. - EXPECT(). - DeleteResponse(c.Request().Context(), responseID). - Return(testCase.request.DeleteResponseError) - } - } - - e.HTTPErrorHandler(r.DeleteResponse(c), c) - - assertion.Equal(testCase.expect.statusCode, rec.Code, testCase.description, "status code") - } -} diff --git a/router/results.go b/router/results.go deleted file mode 100644 index 829c20f0..00000000 --- a/router/results.go +++ /dev/null @@ -1,43 +0,0 @@ -package router - -import ( - "net/http" - "strconv" - - "github.com/labstack/echo/v4" - "github.com/traPtitech/anke-to/model" -) - -// Result Resultの構造体 -type Result struct { - model.IRespondent - model.IQuestionnaire - model.IAdministrator -} - -// NewResult Resultのコンストラクタ -func NewResult(respondent model.IRespondent, questionnaire model.IQuestionnaire, administrator model.IAdministrator) *Result { - return &Result{ - IRespondent: respondent, - IQuestionnaire: questionnaire, - IAdministrator: administrator, - } -} - -// GetResults GET /results/:questionnaireID -func (r *Result) GetResults(c echo.Context) error { - sort := c.QueryParam("sort") - questionnaireID, err := strconv.Atoi(c.Param("questionnaireID")) - if err != nil { - c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest) - } - - respondentDetails, err := r.GetRespondentDetails(c.Request().Context(), questionnaireID, sort) - if err != nil { - c.Logger().Errorf("failed to get respondent details: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, respondentDetails) -} diff --git a/router/results_test.go b/router/results_test.go deleted file mode 100644 index 5f77272e..00000000 --- a/router/results_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package router - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/model/mock_model" - "gopkg.in/guregu/null.v4" -) - -func TestGetResults(t *testing.T) { - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - - result := NewResult(mockRespondent, mockQuestionnaire, mockAdministrator) - - type request struct { - sortParam string - questionnaireIDParam string - questionnaireIDValid bool - questionnaireID int - respondentDetails []model.RespondentDetail - getRespondentDetailsError error - } - type response struct { - statusCode int - body string - } - type test struct { - description string - request - response - } - - textResponse := []model.RespondentDetail{ - { - ResponseID: 1, - TraqID: "mazrean", - QuestionnaireID: 1, - SubmittedAt: null.NewTime(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), true), - ModifiedAt: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), - Responses: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.NewString("テスト", true), - OptionResponse: nil, - }, - }, - }, - } - sb := strings.Builder{} - err := json.NewEncoder(&sb).Encode(textResponse) - if err != nil { - t.Errorf("failed to encode text response: %v", err) - return - } - - testCases := []test{ - { - description: "questionnaireIDが数字でないので400", - request: request{ - sortParam: "traqid", - questionnaireIDParam: "abc", - }, - response: response{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "questionnaireIDが空文字なので400", - request: request{ - sortParam: "traqid", - questionnaireIDParam: "", - }, - response: response{ - statusCode: http.StatusBadRequest, - }, - }, - { - description: "GetRespondentDetailsがエラーなので500", - request: request{ - sortParam: "traqid", - questionnaireIDValid: true, - questionnaireIDParam: "1", - questionnaireID: 1, - getRespondentDetailsError: fmt.Errorf("error"), - }, - response: response{ - statusCode: http.StatusInternalServerError, - }, - }, - { - description: "respondentDetailsがnilでも200", - request: request{ - sortParam: "traqid", - questionnaireIDValid: true, - questionnaireIDParam: "1", - questionnaireID: 1, - }, - response: response{ - statusCode: http.StatusOK, - body: "null\n", - }, - }, - { - description: "respondentDetailsがそのまま帰り200", - request: request{ - sortParam: "traqid", - questionnaireIDValid: true, - questionnaireIDParam: "1", - questionnaireID: 1, - respondentDetails: []model.RespondentDetail{ - { - ResponseID: 1, - TraqID: "mazrean", - QuestionnaireID: 1, - SubmittedAt: null.NewTime(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), true), - ModifiedAt: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), - Responses: []model.ResponseBody{ - { - QuestionID: 1, - QuestionType: "Text", - Body: null.NewString("テスト", true), - OptionResponse: nil, - }, - }, - }, - }, - }, - response: response{ - statusCode: http.StatusOK, - body: sb.String(), - }, - }, - } - - for _, testCase := range testCases { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/results/%s?sort=%s", testCase.request.questionnaireIDParam, testCase.request.sortParam), nil) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - c.SetPath("/results/:questionnaireID") - c.SetParamNames("questionnaireID", "sort") - c.SetParamValues(testCase.request.questionnaireIDParam, testCase.request.sortParam) - - if testCase.request.questionnaireIDValid { - mockRespondent. - EXPECT(). - GetRespondentDetails(c.Request().Context(), testCase.request.questionnaireID, testCase.request.sortParam). - Return(testCase.request.respondentDetails, testCase.request.getRespondentDetailsError) - } - - e.HTTPErrorHandler(result.GetResults(c), c) - assertion.Equalf(testCase.response.statusCode, rec.Code, testCase.description, "statusCode") - if testCase.response.statusCode == http.StatusOK { - assertion.Equalf(testCase.response.body, rec.Body.String(), testCase.description, "body") - } - } -} diff --git a/router/users.go b/router/users.go deleted file mode 100644 index 896d0276..00000000 --- a/router/users.go +++ /dev/null @@ -1,268 +0,0 @@ -package router - -import ( - "fmt" - "net/http" - "strconv" - "time" - - "github.com/labstack/echo/v4" - "gopkg.in/guregu/null.v4" - - "github.com/traPtitech/anke-to/model" -) - -// User Userの構造体 -type User struct { - model.IRespondent - model.IQuestionnaire - model.ITarget - model.IAdministrator -} - -type UserQueryparam struct { - Sort string `validate:"omitempty,oneof=created_at -created_at title -title modified_at -modified_at"` - Answered string `validate:"omitempty,oneof=answered unanswered"` -} - -// NewUser Userのコンストラクタ -func NewUser(respondent model.IRespondent, questionnaire model.IQuestionnaire, target model.ITarget, administrator model.IAdministrator) *User { - return &User{ - IRespondent: respondent, - IQuestionnaire: questionnaire, - ITarget: target, - IAdministrator: administrator, - } -} - -// GetUsersMe GET /users/me -func (*User) GetUsersMe(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "traqID": userID, - }) -} - -// GetMyResponses GET /users/me/responses -func (u *User) GetMyResponses(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - myResponses, err := u.GetRespondentInfos(c.Request().Context(), userID) - if err != nil { - c.Logger().Errorf("failed to get respondentInfos: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, myResponses) -} - -// GetMyResponsesByID GET /users/me/responses/:questionnaireID -func (u *User) GetMyResponsesByID(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - questionnaireID, err := strconv.Atoi(c.Param("questionnaireID")) - if err != nil { - c.Logger().Infof("failed to convert questionnaireID to int: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest) - } - - myresponses, err := u.GetRespondentInfos(c.Request().Context(), userID, questionnaireID) - if err != nil { - c.Logger().Errorf("failed to get respondentInfos: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, myresponses) -} - -// GetTargetedQuestionnaire GET /users/me/targeted -func (u *User) GetTargetedQuestionnaire(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - sort := c.QueryParam("sort") - ret, err := u.GetTargettedQuestionnaires(c.Request().Context(), userID, "", sort) - if err != nil { - c.Logger().Errorf("failed to get targetedQuestionnaires: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, ret) -} - -// GetMyQuestionnaire GET /users/me/administrates -func (u *User) GetMyQuestionnaire(c echo.Context) error { - userID, err := getUserID(c) - if err != nil { - c.Logger().Errorf("failed to get userID: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) - } - - // 自分が管理者になっているアンケート一覧 - questionnaires, err := u.GetAdminQuestionnaires(c.Request().Context(), userID) - if err != nil { - c.Logger().Errorf("failed to get adminQuestionnaires: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaires: %w", err)) - } - - questionnaireIDs := make([]int, 0, len(questionnaires)) - for _, questionnaire := range questionnaires { - questionnaireIDs = append(questionnaireIDs, questionnaire.ID) - } - - targets, err := u.GetTargets(c.Request().Context(), questionnaireIDs) - if err != nil { - c.Logger().Errorf("failed to get targets: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get targets: %w", err)) - } - targetMap := map[int][]string{} - for _, target := range targets { - tgts, ok := targetMap[target.QuestionnaireID] - if !ok { - targetMap[target.QuestionnaireID] = []string{target.UserTraqid} - } else { - targetMap[target.QuestionnaireID] = append(tgts, target.UserTraqid) - } - } - - respondents, err := u.GetRespondentsUserIDs(c.Request().Context(), questionnaireIDs) - if err != nil { - c.Logger().Errorf("failed to get respondentsUserIDs: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get respondents: %w", err)) - } - respondentMap := map[int][]string{} - for _, respondent := range respondents { - rspdts, ok := respondentMap[respondent.QuestionnaireID] - if !ok { - respondentMap[respondent.QuestionnaireID] = []string{respondent.UserTraqid} - } else { - respondentMap[respondent.QuestionnaireID] = append(rspdts, respondent.UserTraqid) - } - } - - administrators, err := u.GetAdministrators(c.Request().Context(), questionnaireIDs) - if err != nil { - c.Logger().Errorf("failed to get administrators: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get administrators: %w", err)) - } - administratorMap := map[int][]string{} - for _, administrator := range administrators { - admins, ok := administratorMap[administrator.QuestionnaireID] - if !ok { - administratorMap[administrator.QuestionnaireID] = []string{administrator.UserTraqid} - } else { - administratorMap[administrator.QuestionnaireID] = append(admins, administrator.UserTraqid) - } - } - - type QuestionnaireInfo struct { - ID int `json:"questionnaireID"` - Title string `json:"title"` - Description string `json:"description"` - ResTimeLimit null.Time `json:"res_time_limit"` - CreatedAt string `json:"created_at"` - ModifiedAt string `json:"modified_at"` - ResSharedTo string `json:"res_shared_to"` - AllResponded bool `json:"all_responded"` - Targets []string `json:"targets"` - Administrators []string `json:"administrators"` - Respondents []string `json:"respondents"` - } - ret := []QuestionnaireInfo{} - - for _, questionnaire := range questionnaires { - targets, ok := targetMap[questionnaire.ID] - if !ok { - targets = []string{} - } - - administrators, ok := administratorMap[questionnaire.ID] - if !ok { - administrators = []string{} - } - - respondents, ok := respondentMap[questionnaire.ID] - if !ok { - respondents = []string{} - } - - allresponded := true - for _, t := range targets { - found := false - for _, r := range respondents { - if t == r { - found = true - break - } - } - if !found { - allresponded = false - break - } - } - - ret = append(ret, QuestionnaireInfo{ - ID: questionnaire.ID, - Title: questionnaire.Title, - Description: questionnaire.Description, - ResTimeLimit: questionnaire.ResTimeLimit, - CreatedAt: questionnaire.CreatedAt.Format(time.RFC3339), - ModifiedAt: questionnaire.ModifiedAt.Format(time.RFC3339), - ResSharedTo: questionnaire.ResSharedTo, - AllResponded: allresponded, - Targets: targets, - Administrators: administrators, - Respondents: respondents, - }) - } - - return c.JSON(http.StatusOK, ret) -} - -// GetTargettedQuestionnairesBytraQID GET /users/:traQID/targeted -func (u *User) GetTargettedQuestionnairesBytraQID(c echo.Context) error { - traQID := c.Param("traQID") - sort := c.QueryParam("sort") - answered := c.QueryParam("answered") - - p := UserQueryparam{ - Sort: sort, - Answered: answered, - } - - validate, err := getValidator(c) - if err != nil { - c.Logger().Errorf("failed to get validator: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError) - } - - err = validate.StructCtx(c.Request().Context(), p) - if err != nil { - c.Logger().Infof("failed to validate: %+v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - ret, err := u.GetTargettedQuestionnaires(c.Request().Context(), traQID, answered, sort) - if err != nil { - c.Logger().Errorf("failed to get targetted questionnaires: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, err) - } - - return c.JSON(http.StatusOK, ret) -} diff --git a/router/users_test.go b/router/users_test.go deleted file mode 100644 index 4738232d..00000000 --- a/router/users_test.go +++ /dev/null @@ -1,951 +0,0 @@ -package router - -import ( - "encoding/json" - "fmt" - "github.com/go-playground/validator/v10" - "net/http" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/model/mock_model" - "gopkg.in/guregu/null.v4" - "gorm.io/gorm" -) - -type myResponse struct { - Title string `json:"questionnaire_title"` - Description string `json:"description"` - ResTimeLimit null.Time `json:"res_time_limit"` - ResponseID int `json:"responseID"` - QuestionnaireID int `json:"questionnaireID"` - ModifiedAt time.Time `json:"modified_at"` - SubmittedAt null.Time `json:"submitted_at"` - DeletedAt null.Time `json:"deleted_at"` -} - -type targettedQuestionnaire struct { - QuestionnaireID int `json:"questionnaireID"` - Title string `json:"title"` - Description string `json:"description"` - ResTimeLimit null.Time `json:"res_time_limit"` - DeletedAt null.Time `json:"deleted_at"` - ResSharedTo string `json:"res_shared_to"` - CreatedAt time.Time `json:"created_at"` - ModifiedAt time.Time `json:"modified_at"` - RespondedAt null.Time `json:"responded_at"` - HasResponse bool `json:"has_response"` -} - -func TestGetTargettedQuestionnairesBytraQIDValidate(t *testing.T) { - t.Parallel() - - tests := []struct { - description string - request *UserQueryparam - isErr bool - }{ - { - description: "一般的なQueryParameterなのでエラーなし", - request: &UserQueryparam{ - Sort: "created_at", - Answered: "answered", - }, - }, - { - description: "Sortが-created_atでもエラーなし", - request: &UserQueryparam{ - Sort: "-created_at", - Answered: "answered", - }, - }, - { - description: "Sortがtitleでもエラーなし", - request: &UserQueryparam{ - Sort: "title", - Answered: "answered", - }, - }, - { - description: "Sortが-titleでもエラーなし", - request: &UserQueryparam{ - Sort: "-title", - Answered: "answered", - }, - }, - { - description: "Sortがmodified_atでもエラーなし", - request: &UserQueryparam{ - Sort: "modified_at", - Answered: "answered", - }, - }, - { - description: "Sortが-modified_atでもエラーなし", - request: &UserQueryparam{ - Sort: "-modified_at", - Answered: "answered", - }, - }, - { - description: "Answeredがunansweredでもエラーなし", - request: &UserQueryparam{ - Sort: "created_at", - Answered: "unanswered", - }, - }, - { - description: "Sortが空文字でもエラーなし", - request: &UserQueryparam{ - Sort: "", - Answered: "answered", - }, - }, - { - description: "Answeredが空文字でもエラーなし", - request: &UserQueryparam{ - Sort: "created_at", - Answered: "", - }, - }, - { - description: "Sortが指定された文字列ではないためエラー", - request: &UserQueryparam{ - Sort: "sort", - Answered: "answered", - }, - isErr: true, - }, - { - description: "Answeredが指定された文字列ではないためエラー", - request: &UserQueryparam{ - Sort: "created_at", - Answered: "answer", - }, - isErr: true, - }, - } - - for _, test := range tests { - validate := validator.New() - - t.Run(test.description, func(t *testing.T) { - err := validate.Struct(test.request) - if test.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestGetUsersMe(t *testing.T) { - - type meResponseBody struct { - TraqID string `json:"traqID"` - } - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - u := NewUser( - mockRespondent, - mockQuestionnaire, - mockTarget, - mockAdministrator, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - - type request struct { - user users - } - type expect struct { - isErr bool - code int - response meResponseBody - } - - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: meResponseBody{ - string(userOne), - }, - }, - }, - } - - e := echo.New() - e.GET("api/users/me", u.GetUsersMe, m.SetUserIDMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - rec := createRecorder(e, testCase.request.user, methodGet, makePath("/users/me"), typeNone, "") - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - responseByte, jsonErr := json.Marshal(testCase.expect.response) - require.NoError(t, jsonErr) - responseStr := string(responseByte) + "\n" - assertion.Equal(responseStr, rec.Body.String(), testCase.description, "responseBody") - } -} - -func TestGetMyResponses(t *testing.T) { - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - responseID1 := 1 - questionnaireID1 := 1 - responseID2 := 2 - questionnaireID2 := 2 - responseID3 := 3 - questionnaireID3 := 3 - myResponses := []myResponse{ - { - ResponseID: responseID1, - QuestionnaireID: questionnaireID1, - Title: "質問1", - Description: "質問1 description", - ResTimeLimit: null.NewTime(nowTime, false), - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - { - ResponseID: responseID2, - QuestionnaireID: questionnaireID2, - Title: "質問2", - Description: "質問2 description", - ResTimeLimit: null.NewTime(nowTime, false), - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - { - ResponseID: responseID3, - QuestionnaireID: questionnaireID3, - Title: "質問3", - Description: "質問3 description", - ResTimeLimit: null.NewTime(nowTime, false), - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - } - respondentInfos := []model.RespondentInfo{ - { - Title: "質問1", - Description: "質問1 description", - ResTimeLimit: null.NewTime(nowTime, false), - Respondents: model.Respondents{ - ResponseID: responseID1, - QuestionnaireID: questionnaireID1, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - }, - { - Title: "質問2", - Description: "質問2 description", - ResTimeLimit: null.NewTime(nowTime, false), - Respondents: model.Respondents{ - ResponseID: responseID2, - QuestionnaireID: questionnaireID2, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - }, - { - Title: "質問3", - Description: "質問3 description", - ResTimeLimit: null.NewTime(nowTime, false), - Respondents: model.Respondents{ - ResponseID: responseID3, - QuestionnaireID: questionnaireID3, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - }, - } - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - u := NewUser( - mockRespondent, - mockQuestionnaire, - mockTarget, - mockAdministrator, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - - // Respondent - // GetRespondentInfos - // success - mockRespondent.EXPECT(). - GetRespondentInfos(gomock.Any(), string(userOne)). - Return(respondentInfos, nil).AnyTimes() - // empty - mockRespondent.EXPECT(). - GetRespondentInfos(gomock.Any(), "empty"). - Return([]model.RespondentInfo{}, nil).AnyTimes() - // failure - mockRespondent.EXPECT(). - GetRespondentInfos(gomock.Any(), "StatusInternalServerError"). - Return(nil, errMock).AnyTimes() - - type request struct { - user users - } - type expect struct { - isErr bool - code int - response []myResponse - } - - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: myResponses, - }, - }, - { - description: "empty", - request: request{ - user: "empty", - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: []myResponse{}, - }, - }, - { - description: "StatusInternalServerError", - request: request{ - user: "StatusInternalServerError", - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - } - - e := echo.New() - e.GET("api/users/me/responses", u.GetMyResponses, m.SetUserIDMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - rec := createRecorder(e, testCase.request.user, methodGet, makePath("/users/me/responses"), typeNone, "") - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - responseByte, jsonErr := json.Marshal(testCase.expect.response) - require.NoError(t, jsonErr) - responseStr := string(responseByte) + "\n" - assertion.Equal(responseStr, rec.Body.String(), testCase.description, "responseBody") - } -} - -func TestGetMyResponsesByID(t *testing.T) { - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - responseID1 := 1 - responseID2 := 2 - questionnaireIDSuccess := 1 - questionnaireIDNotFound := -1 - myResponses := []myResponse{ - { - ResponseID: responseID1, - QuestionnaireID: questionnaireIDSuccess, - Title: "質問1", - Description: "質問1 description", - ResTimeLimit: null.NewTime(nowTime, false), - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - { - ResponseID: responseID2, - QuestionnaireID: questionnaireIDSuccess, - Title: "質問2", - Description: "質問2 description", - ResTimeLimit: null.NewTime(nowTime, false), - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - } - respondentInfos := []model.RespondentInfo{ - { - Title: "質問1", - Description: "質問1 description", - ResTimeLimit: null.NewTime(nowTime, false), - Respondents: model.Respondents{ - ResponseID: responseID1, - QuestionnaireID: questionnaireIDSuccess, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - }, - { - Title: "質問2", - Description: "質問2 description", - ResTimeLimit: null.NewTime(nowTime, false), - Respondents: model.Respondents{ - ResponseID: responseID2, - QuestionnaireID: questionnaireIDSuccess, - SubmittedAt: null.TimeFrom(nowTime), - ModifiedAt: nowTime, - }, - }, - } - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - u := NewUser( - mockRespondent, - mockQuestionnaire, - mockTarget, - mockAdministrator, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - - // Respondent - // GetRespondentInfos - // success - mockRespondent.EXPECT(). - GetRespondentInfos(gomock.Any(), string(userOne), questionnaireIDSuccess). - Return(respondentInfos, nil).AnyTimes() - // questionnaireIDNotFound - mockRespondent.EXPECT(). - GetRespondentInfos(gomock.Any(), string(userOne), questionnaireIDNotFound). - Return([]model.RespondentInfo{}, nil).AnyTimes() - // failure - mockRespondent.EXPECT(). - GetRespondentInfos(gomock.Any(), "StatusInternalServerError", questionnaireIDSuccess). - Return(nil, errMock).AnyTimes() - - type request struct { - user users - questionnaireID int - isBadParam bool - } - type expect struct { - isErr bool - code int - response []myResponse - } - - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - questionnaireID: questionnaireIDSuccess, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: myResponses, - }, - }, - { - description: "questionnaireID does not exist", - request: request{ - user: userOne, - questionnaireID: questionnaireIDNotFound, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: []myResponse{}, - }, - }, - { - description: "StatusInternalServerError", - request: request{ - user: "StatusInternalServerError", - questionnaireID: questionnaireIDSuccess, - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - { - description: "badParam", - request: request{ - user: userOne, - questionnaireID: questionnaireIDSuccess, - isBadParam: true, - }, - expect: expect{ - isErr: true, - code: http.StatusBadRequest, - }, - }, - } - - e := echo.New() - e.GET("api/users/me/responses/:questionnaireID", u.GetMyResponsesByID, m.SetUserIDMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - reqPath := fmt.Sprint(rootPath, "/users/me/responses/", testCase.request.questionnaireID) - if testCase.request.isBadParam { - reqPath = fmt.Sprint(rootPath, "/users/me/responses/", "badParam") - } - rec := createRecorder(e, testCase.request.user, methodGet, reqPath, typeNone, "") - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - responseByte, jsonErr := json.Marshal(testCase.expect.response) - require.NoError(t, jsonErr) - responseStr := string(responseByte) + "\n" - assertion.Equal(responseStr, rec.Body.String(), testCase.description, "responseBody") - } -} - -func TestGetTargetedQuestionnaire(t *testing.T) { - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - questionnaireID1 := 1 - questionnaireID2 := 2 - targettedQuestionnaires := []model.TargettedQuestionnaire{ - { - Questionnaires: model.Questionnaires{ - ID: questionnaireID1, - Title: "questionnaireID1", - Description: "questionnaireID1", - ResTimeLimit: null.TimeFrom(nowTime), - DeletedAt: gorm.DeletedAt{ - Time: nowTime, - Valid: false, - }, - ResSharedTo: "public", - CreatedAt: nowTime, - ModifiedAt: nowTime, - }, - RespondedAt: null.NewTime(nowTime, false), - HasResponse: false, - }, - { - Questionnaires: model.Questionnaires{ - ID: questionnaireID2, - Title: "questionnaireID2", - Description: "questionnaireID2", - ResTimeLimit: null.TimeFrom(nowTime), - DeletedAt: gorm.DeletedAt{ - Time: nowTime, - Valid: false, - }, - ResSharedTo: "public", - CreatedAt: nowTime, - ModifiedAt: nowTime, - }, - RespondedAt: null.NewTime(nowTime, false), - HasResponse: false, - }, - } - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - u := NewUser( - mockRespondent, - mockQuestionnaire, - mockTarget, - mockAdministrator, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - - // Questionnaire - // GetTargettedQuestionnaires - // success - mockQuestionnaire.EXPECT(). - GetTargettedQuestionnaires(gomock.Any(), string(userOne), "", gomock.Any()). - Return(targettedQuestionnaires, nil).AnyTimes() - // empty - mockQuestionnaire.EXPECT(). - GetTargettedQuestionnaires(gomock.Any(), "empty", "", gomock.Any()). - Return([]model.TargettedQuestionnaire{}, nil).AnyTimes() - // failure - mockQuestionnaire.EXPECT(). - GetTargettedQuestionnaires(gomock.Any(), "StatusInternalServerError", "", gomock.Any()). - Return(nil, errMock).AnyTimes() - - type request struct { - user users - } - type expect struct { - isErr bool - code int - response []model.TargettedQuestionnaire - } - - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: targettedQuestionnaires, - }, - }, - { - description: "empty", - request: request{ - user: "empty", - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: []model.TargettedQuestionnaire{}, - }, - }, - { - description: "StatusInternalServerError", - request: request{ - user: "StatusInternalServerError", - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - } - - e := echo.New() - e.GET("api/users/me/targeted", u.GetTargetedQuestionnaire, m.SetUserIDMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - rec := createRecorder(e, testCase.request.user, methodGet, makePath("/users/me/targeted"), typeNone, "") - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - responseByte, jsonErr := json.Marshal(testCase.expect.response) - require.NoError(t, jsonErr) - responseStr := string(responseByte) + "\n" - assertion.Equal(responseStr, rec.Body.String(), testCase.description, "responseBody") - } -} - -func TestGetTargettedQuestionnairesBytraQID(t *testing.T) { - - t.Parallel() - assertion := assert.New(t) - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - nowTime := time.Now() - - questionnaireID1 := 1 - questionnaireID2 := 2 - targettedQuestionnaires := []model.TargettedQuestionnaire{ - { - Questionnaires: model.Questionnaires{ - ID: questionnaireID1, - Title: "questionnaireID1", - Description: "questionnaireID1", - ResTimeLimit: null.TimeFrom(nowTime), - DeletedAt: gorm.DeletedAt{ - Time: nowTime, - Valid: false, - }, - ResSharedTo: "public", - CreatedAt: nowTime, - ModifiedAt: nowTime, - }, - RespondedAt: null.NewTime(nowTime, false), - HasResponse: false, - }, - { - Questionnaires: model.Questionnaires{ - ID: questionnaireID2, - Title: "questionnaireID2", - Description: "questionnaireID2", - ResTimeLimit: null.TimeFrom(nowTime), - DeletedAt: gorm.DeletedAt{ - Time: nowTime, - Valid: false, - }, - ResSharedTo: "public", - CreatedAt: nowTime, - ModifiedAt: nowTime, - }, - RespondedAt: null.NewTime(nowTime, false), - HasResponse: false, - }, - } - - mockRespondent := mock_model.NewMockIRespondent(ctrl) - mockQuestionnaire := mock_model.NewMockIQuestionnaire(ctrl) - mockTarget := mock_model.NewMockITarget(ctrl) - mockAdministrator := mock_model.NewMockIAdministrator(ctrl) - - mockQuestion := mock_model.NewMockIQuestion(ctrl) - - u := NewUser( - mockRespondent, - mockQuestionnaire, - mockTarget, - mockAdministrator, - ) - m := NewMiddleware( - mockAdministrator, - mockRespondent, - mockQuestion, - mockQuestionnaire, - ) - - // Questionnaire - // GetTargettedQuestionnaires - // success - mockQuestionnaire.EXPECT(). - GetTargettedQuestionnaires(gomock.Any(), string(userOne), "", gomock.Any()). - Return(targettedQuestionnaires, nil).AnyTimes() - // empty - mockQuestionnaire.EXPECT(). - GetTargettedQuestionnaires(gomock.Any(), "empty", "", gomock.Any()). - Return([]model.TargettedQuestionnaire{}, nil).AnyTimes() - // failure - mockQuestionnaire.EXPECT(). - GetTargettedQuestionnaires(gomock.Any(), "StatusInternalServerError", "", gomock.Any()). - Return(nil, errMock).AnyTimes() - - type request struct { - user users - targetUser users - } - type expect struct { - isErr bool - code int - response []model.TargettedQuestionnaire - } - - type test struct { - description string - request - expect - } - testCases := []test{ - { - description: "success", - request: request{ - user: userOne, - targetUser: userOne, - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: targettedQuestionnaires, - }, - }, - { - description: "empty", - request: request{ - user: userOne, - targetUser: "empty", - }, - expect: expect{ - isErr: false, - code: http.StatusOK, - response: []model.TargettedQuestionnaire{}, - }, - }, - { - description: "StatusInternalServerError", - request: request{ - user: userOne, - targetUser: "StatusInternalServerError", - }, - expect: expect{ - isErr: true, - code: http.StatusInternalServerError, - }, - }, - } - - e := echo.New() - e.GET("api/users/:traQID/targeted", u.GetTargettedQuestionnairesBytraQID, m.SetUserIDMiddleware, m.SetValidatorMiddleware, m.TraPMemberAuthenticate) - - for _, testCase := range testCases { - rec := createRecorder(e, testCase.request.user, methodGet, fmt.Sprint(rootPath, "/users/", testCase.request.targetUser, "/targeted"), typeNone, "") - - assertion.Equal(testCase.expect.code, rec.Code, testCase.description, "status code") - if rec.Code < 200 || rec.Code >= 300 { - continue - } - - responseByte, jsonErr := json.Marshal(testCase.expect.response) - require.NoError(t, jsonErr) - responseStr := string(responseByte) + "\n" - assertion.Equal(responseStr, rec.Body.String(), testCase.description, "responseBody") - } -} - -// func TestGetUsersMe(t *testing.T) { -// testList := []struct { -// description string -// result meResponseBody -// expectCode int -// }{} -// fmt.Println(testList) -// } - -// func TestGetMyResponses(t *testing.T) { -// testList := []struct { -// description string -// result respondentInfos -// expectCode int -// }{} -// fmt.Println(testList) -// } - -// func TestGetMyResponsesByID(t *testing.T) { -// testList := []struct { -// description string -// questionnaireID int -// result respondentInfos -// expectCode int -// }{} -// fmt.Println(testList) -// } - -// func TestGetTargetedQuestionnaire(t *testing.T) { -// testList := []struct { -// description string -// result targettedQuestionnaire -// expectCode int -// }{} -// fmt.Println(testList) -// } - -// func TestGetMyQuestionnaire(t *testing.T) { -// testList := []struct { -// description string -// result targettedQuestionnaire -// expectCode int -// }{} -// fmt.Println(testList) -// } -// func TestGetTargettedQuestionnairesBytraQID(t *testing.T) { -// testList := []struct { -// description string -// result targettedQuestionnaire -// expectCode int -// }{} -// fmt.Println(testList) -// } From 55a0dc5910147895922fc909d1960c56825c6c08 Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Fri, 6 Dec 2024 15:00:50 +0900 Subject: [PATCH 80/83] wip introduce wire --- handler/middleware.go | 18 +++--- main.go | 55 +++++++++-------- router.go | 140 +++++++++++++++++++++--------------------- wire.go | 15 ++--- 4 files changed, 111 insertions(+), 117 deletions(-) diff --git a/handler/middleware.go b/handler/middleware.go index 02f8ee51..b7e0167f 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -41,7 +41,7 @@ const ( var adminUserIDs = []string{"ryoha", "xxarupakaxx", "kaitoyama", "cp20", "itzmeowww"} // SetUserIDMiddleware X-Showcase-UserからユーザーIDを取得しセットする -func SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { +func (*Middleware) SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { userID := c.Request().Header.Get("X-Showcase-User") if userID == "" { @@ -55,7 +55,7 @@ func SetUserIDMiddleware(next echo.HandlerFunc) echo.HandlerFunc { } // TraPMemberAuthenticate traP部員かの認証 -func TraPMemberAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { +func (*Middleware) TraPMemberAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { userID, err := getUserID(c) if err != nil { @@ -74,7 +74,7 @@ func TraPMemberAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { } // TrapRateLimitMiddlewareFunc traP IDベースのリクエスト制限 -func TrapRateLimitMiddlewareFunc() echo.MiddlewareFunc { +func (*Middleware) TrapRateLimitMiddlewareFunc() echo.MiddlewareFunc { config := middleware.RateLimiterConfig{ Store: middleware.NewRateLimiterMemoryStore(5), IdentifierExtractor: func(c echo.Context) (string, error) { @@ -92,9 +92,8 @@ func TrapRateLimitMiddlewareFunc() echo.MiddlewareFunc { } // QuestionnaireReadAuthenticate アンケートの閲覧権限があるかの認証 -func QuestionnaireReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { +func (m *Middleware) QuestionnaireReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - m := NewMiddleware() userID, err := getUserID(c) if err != nil { @@ -148,9 +147,8 @@ func QuestionnaireReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { } // QuestionnaireAdministratorAuthenticate アンケートの管理者かどうかの認証 -func QuestionnaireAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { +func (m *Middleware) QuestionnaireAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - m := NewMiddleware() userID, err := getUserID(c) if err != nil { @@ -188,9 +186,8 @@ func QuestionnaireAdministratorAuthenticate(next echo.HandlerFunc) echo.HandlerF } // ResponseReadAuthenticate 回答閲覧権限があるかの認証 -func ResponseReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { +func (m *Middleware) ResponseReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - m := NewMiddleware() userID, err := getUserID(c) if err != nil { @@ -255,9 +252,8 @@ func ResponseReadAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { } // RespondentAuthenticate 回答者かどうかの認証 -func RespondentAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { +func (m *Middleware) RespondentAuthenticate(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - m := NewMiddleware() userID, err := getUserID(c) if err != nil { diff --git a/main.go b/main.go index 3778b60b..8668f1ef 100644 --- a/main.go +++ b/main.go @@ -61,37 +61,38 @@ func main() { controller.Wg.Add(1) go func() { e := echo.New() - swagger, err := openapi.GetSwagger() - if err != nil { - panic(err) - } - e.Use(oapiMiddleware.OapiRequestValidator(swagger)) - e.Use(handler.SetUserIDMiddleware) - e.Use(middleware.Logger()) - e.Use(middleware.Recover()) - - mws := NewMiddlewareSwitcher() - mws.AddGroupConfig("", handler.TraPMemberAuthenticate) - - mws.AddRouteConfig("/questionnaires", http.MethodGet, handler.TrapRateLimitMiddlewareFunc()) - mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodGet, handler.QuestionnaireReadAuthenticate) - mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodPatch, handler.QuestionnaireAdministratorAuthenticate) - mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodDelete, handler.QuestionnaireAdministratorAuthenticate) - - mws.AddRouteConfig("/responses/:responseID", http.MethodGet, handler.ResponseReadAuthenticate) - mws.AddRouteConfig("/responses/:responseID", http.MethodPatch, handler.RespondentAuthenticate) - mws.AddRouteConfig("/responses/:responseID", http.MethodDelete, handler.RespondentAuthenticate) - - openapi.RegisterHandlers(e, handler.Handler{}) - - e.Use(mws.ApplyMiddlewares) - e.Logger.Fatal(e.Start(port)) - + swagger, err := openapi.GetSwagger() + if err != nil { + panic(err) + } + api := InjectAPIServer() + e.Use(oapiMiddleware.OapiRequestValidator(swagger)) + e.Use(api.SetUserIDMiddleware) + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + mws := NewMiddlewareSwitcher() + mws.AddGroupConfig("", api.TraPMemberAuthenticate) + + mws.AddRouteConfig("/questionnaires", http.MethodGet, api.TrapRateLimitMiddlewareFunc()) + mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodGet, api.QuestionnaireReadAuthenticate) + mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodPatch, api.QuestionnaireAdministratorAuthenticate) + mws.AddRouteConfig("/questionnaires/:questionnaireID", http.MethodDelete, api.QuestionnaireAdministratorAuthenticate) + + mws.AddRouteConfig("/responses/:responseID", http.MethodGet, api.ResponseReadAuthenticate) + mws.AddRouteConfig("/responses/:responseID", http.MethodPatch, api.RespondentAuthenticate) + mws.AddRouteConfig("/responses/:responseID", http.MethodDelete, api.RespondentAuthenticate) + + openapi.RegisterHandlers(e, handler.Handler{}) + + e.Use(mws.ApplyMiddlewares) + e.Logger.Fatal(e.Start(port)) + controller.Wg.Done() }() controller.Wg.Add(1) - go func () { + go func() { controller.ReminderInit() controller.Wg.Done() }() diff --git a/router.go b/router.go index 389a4d19..ab4860d6 100644 --- a/router.go +++ b/router.go @@ -1,83 +1,83 @@ package main -import ( - "github.com/labstack/echo-contrib/prometheus" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" -) +// import ( +// "github.com/labstack/echo-contrib/prometheus" +// "github.com/labstack/echo/v4" +// "github.com/labstack/echo/v4/middleware" +// ) -// SetRouting ルーティングの設定 -func SetRouting(port string) { - e := echo.New() +// // SetRouting ルーティングの設定 +// func SetRouting(port string) { +// e := echo.New() - // Middleware - e.Use(middleware.Recover()) - e.Use(middleware.Logger()) - p := prometheus.NewPrometheus("echo", nil) - p.Use(e) +// // Middleware +// e.Use(middleware.Recover()) +// e.Use(middleware.Logger()) +// p := prometheus.NewPrometheus("echo", nil) +// p.Use(e) - api := InjectAPIServer() +// api := InjectAPIServer() - // Static Files - e.Static("/", "client/dist") - e.Static("/js", "client/dist/js") - e.Static("/img", "client/dist/img") - e.Static("/fonts", "client/dist/fonts") - e.Static("/css", "client/dist/css") +// // Static Files +// e.Static("/", "client/dist") +// e.Static("/js", "client/dist/js") +// e.Static("/img", "client/dist/img") +// e.Static("/fonts", "client/dist/fonts") +// e.Static("/css", "client/dist/css") - e.File("/app.js", "client/dist/app.js") - e.File("/favicon.ico", "client/dist/favicon.ico") - e.File("*", "client/dist/index.html") +// e.File("/app.js", "client/dist/app.js") +// e.File("/favicon.ico", "client/dist/favicon.ico") +// e.File("*", "client/dist/index.html") - echoAPI := e.Group("/api", api.SetValidatorMiddleware, api.SetUserIDMiddleware, api.TraPMemberAuthenticate) - { - apiQuestionnnaires := echoAPI.Group("/questionnaires") - { - apiQuestionnnaires.GET("", api.GetQuestionnaires, api.TrapRateLimitMiddlewareFunc()) - apiQuestionnnaires.POST("", api.PostQuestionnaire) - apiQuestionnnaires.GET("/:questionnaireID", api.GetQuestionnaire) - apiQuestionnnaires.PATCH("/:questionnaireID", api.EditQuestionnaire, api.QuestionnaireAdministratorAuthenticate) - apiQuestionnnaires.DELETE("/:questionnaireID", api.DeleteQuestionnaire, api.QuestionnaireAdministratorAuthenticate) - apiQuestionnnaires.GET("/:questionnaireID/questions", api.GetQuestions) - apiQuestionnnaires.POST("/:questionnaireID/questions", api.PostQuestionByQuestionnaireID) - } +// echoAPI := e.Group("/api", api.SetValidatorMiddleware, api.SetUserIDMiddleware, api.TraPMemberAuthenticate) +// { +// apiQuestionnnaires := echoAPI.Group("/questionnaires") +// { +// apiQuestionnnaires.GET("", api.GetQuestionnaires, api.TrapRateLimitMiddlewareFunc()) +// apiQuestionnnaires.POST("", api.PostQuestionnaire) +// apiQuestionnnaires.GET("/:questionnaireID", api.GetQuestionnaire) +// apiQuestionnnaires.PATCH("/:questionnaireID", api.EditQuestionnaire, api.QuestionnaireAdministratorAuthenticate) +// apiQuestionnnaires.DELETE("/:questionnaireID", api.DeleteQuestionnaire, api.QuestionnaireAdministratorAuthenticate) +// apiQuestionnnaires.GET("/:questionnaireID/questions", api.GetQuestions) +// apiQuestionnnaires.POST("/:questionnaireID/questions", api.PostQuestionByQuestionnaireID) +// } - apiQuestions := echoAPI.Group("/questions") - { - apiQuestions.PATCH("/:questionID", api.EditQuestion, api.QuestionAdministratorAuthenticate) - apiQuestions.DELETE("/:questionID", api.DeleteQuestion, api.QuestionAdministratorAuthenticate) - } +// apiQuestions := echoAPI.Group("/questions") +// { +// apiQuestions.PATCH("/:questionID", api.EditQuestion, api.QuestionAdministratorAuthenticate) +// apiQuestions.DELETE("/:questionID", api.DeleteQuestion, api.QuestionAdministratorAuthenticate) +// } - apiResponses := echoAPI.Group("/responses") - { - apiResponses.POST("", api.PostResponse) - apiResponses.GET("/:responseID", api.GetResponse, api.ResponseReadAuthenticate) - apiResponses.PATCH("/:responseID", api.EditResponse, api.RespondentAuthenticate) - apiResponses.DELETE("/:responseID", api.DeleteResponse, api.RespondentAuthenticate) - } +// apiResponses := echoAPI.Group("/responses") +// { +// apiResponses.POST("", api.PostResponse) +// apiResponses.GET("/:responseID", api.GetResponse, api.ResponseReadAuthenticate) +// apiResponses.PATCH("/:responseID", api.EditResponse, api.RespondentAuthenticate) +// apiResponses.DELETE("/:responseID", api.DeleteResponse, api.RespondentAuthenticate) +// } - apiUsers := echoAPI.Group("/users") - { - /* - TODO - apiUsers.GET("") - */ - apiUsersMe := apiUsers.Group("/me") - { - apiUsersMe.GET("", api.GetUsersMe) - apiUsersMe.GET("/responses", api.GetMyResponses) - apiUsersMe.GET("/responses/:questionnaireID", api.GetMyResponsesByID) - apiUsersMe.GET("/targeted", api.GetTargetedQuestionnaire) - apiUsersMe.GET("/administrates", api.GetMyQuestionnaire) - } - apiUsers.GET("/:traQID/targeted", api.GetTargettedQuestionnairesBytraQID) - } +// apiUsers := echoAPI.Group("/users") +// { +// /* +// TODO +// apiUsers.GET("") +// */ +// apiUsersMe := apiUsers.Group("/me") +// { +// apiUsersMe.GET("", api.GetUsersMe) +// apiUsersMe.GET("/responses", api.GetMyResponses) +// apiUsersMe.GET("/responses/:questionnaireID", api.GetMyResponsesByID) +// apiUsersMe.GET("/targeted", api.GetTargetedQuestionnaire) +// apiUsersMe.GET("/administrates", api.GetMyQuestionnaire) +// } +// apiUsers.GET("/:traQID/targeted", api.GetTargettedQuestionnairesBytraQID) +// } - apiResults := echoAPI.Group("/results") - { - apiResults.GET("/:questionnaireID", api.GetResults, api.ResultAuthenticate) - } - } +// apiResults := echoAPI.Group("/results") +// { +// apiResults.GET("/:questionnaireID", api.GetResults, api.ResultAuthenticate) +// } +// } - e.Logger.Fatal(e.Start(port)) -} +// e.Logger.Fatal(e.Start(port)) +// } diff --git a/wire.go b/wire.go index c32e951f..9a35d84a 100644 --- a/wire.go +++ b/wire.go @@ -5,8 +5,9 @@ package main import ( "github.com/google/wire" + "github.com/traPtitech/anke-to/controller" + "github.com/traPtitech/anke-to/handler" "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/router" "github.com/traPtitech/anke-to/traq" ) @@ -25,15 +26,11 @@ var ( webhookBind = wire.Bind(new(traq.IWebhook), new(*traq.Webhook)) ) -func InjectAPIServer() *router.API { +func InjectAPIServer() *handler.Middleware { wire.Build( - router.NewAPI, - router.NewMiddleware, - router.NewQuestionnaire, - router.NewQuestion, - router.NewResponse, - router.NewResult, - router.NewUser, + handler.NewMiddleware, + controller.NewResponse, + controller.NewQuestionnaire, model.NewAdministrator, model.NewOption, model.NewQuestionnaire, From f5708e18663102115171ca3cb1a040d0492630e8 Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Mon, 9 Dec 2024 15:45:55 +0900 Subject: [PATCH 81/83] fix GetQuestionnaireInfo --- controller/adapter.go | 11 ++++++----- model/questionnaires.go | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/controller/adapter.go b/controller/adapter.go index 051a5a71..b385a31a 100644 --- a/controller/adapter.go +++ b/controller/adapter.go @@ -3,6 +3,7 @@ package controller import ( "strconv" + "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" @@ -64,10 +65,10 @@ func convertResSharedTo(resSharedTo string) openapi.ResShareType { } -func createUsersAndGroups(users []string, groups []string) openapi.UsersAndGroups { +func createUsersAndGroups(users []string, groups uuid.UUIDs) openapi.UsersAndGroups { res := openapi.UsersAndGroups{ Users: users, - Groups: groups, + Groups: groups.Strings(), } return res } @@ -147,14 +148,14 @@ func convertRespondents(respondents []model.Respondents) []string { return res } -func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, adminUsers []string, adminGroups []string, targetUsers []string, targetGroups []string, respondents []string) openapi.QuestionnaireDetail { +func questionnaire2QuestionnaireDetail(questionnaires model.Questionnaires, adminUsers []string, adminGroups []uuid.UUID, targetUsers []string, targetGroups []uuid.UUID, respondents []string) openapi.QuestionnaireDetail { res := openapi.QuestionnaireDetail{ Admins: createUsersAndGroups(adminUsers, adminGroups), CreatedAt: questionnaires.CreatedAt, Description: questionnaires.Description, // IsAllowingMultipleResponses: questionnaires.IsAllowingMultipleResponses, - IsAnonymous: questionnaires.IsAnonymous, - IsPublished: questionnaires.IsPublished, + IsAnonymous: questionnaires.IsAnonymous, + IsPublished: questionnaires.IsPublished, ModifiedAt: questionnaires.ModifiedAt, QuestionnaireId: questionnaires.ID, Questions: convertQuestions(questionnaires.Questions), diff --git a/model/questionnaires.go b/model/questionnaires.go index e6ac50c8..0ed2780b 100644 --- a/model/questionnaires.go +++ b/model/questionnaires.go @@ -5,6 +5,7 @@ package model import ( "context" + "github.com/google/uuid" "gopkg.in/guregu/null.v4" ) @@ -15,7 +16,7 @@ type IQuestionnaire interface { DeleteQuestionnaire(ctx context.Context, questionnaireID int) error GetQuestionnaires(ctx context.Context, userID string, sort string, search string, pageNum int, onlyTargetingMe bool, onlyAdministratedByMe bool) ([]QuestionnaireInfo, int, error) GetAdminQuestionnaires(ctx context.Context, userID string) ([]Questionnaires, error) - GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []string, []string, []string, []string, error) + GetQuestionnaireInfo(ctx context.Context, questionnaireID int) (*Questionnaires, []string, []uuid.UUID, []string, []uuid.UUID, []string, error) GetTargettedQuestionnaires(ctx context.Context, userID string, answered string, sort string) ([]TargettedQuestionnaire, error) GetQuestionnaireLimit(ctx context.Context, questionnaireID int) (null.Time, error) GetQuestionnaireLimitByResponseID(ctx context.Context, responseID int) (null.Time, error) From a183cb3106ffba9cebad6af39745ae5f90d080f8 Mon Sep 17 00:00:00 2001 From: Eraxyso <130852025+Eraxyso@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:44:25 +0000 Subject: [PATCH 82/83] fix: add soring parameter for implementation and test of GetMyResponseIDs --- model/respondents_impl.go | 15 ++++++++++----- model/respondents_test.go | 6 +++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/model/respondents_impl.go b/model/respondents_impl.go index 70c500e9..233dbcfc 100755 --- a/model/respondents_impl.go +++ b/model/respondents_impl.go @@ -396,18 +396,23 @@ func (*Respondent) GetRespondentsUserIDs(ctx context.Context, questionnaireIDs [ } // GetMyResponses 自分のすべての回答を取得 -func (*Respondent) GetMyResponseIDs(ctx context.Context, userID string) ([]int, error) { +func (*Respondent) GetMyResponseIDs(ctx context.Context, sort string, userID string) ([]int, error) { db, err := getTx(ctx) if err != nil { return nil, fmt.Errorf("failed to get transaction: %w", err) } responsesID := []int{} - err = db. - Model(&Respondents{}). + query := db.Model(&Respondents{}). Where("user_traqid = ?", userID). - Select("response_id"). - Find(&responsesID).Error + Select("response_id") + + query, _, err = setRespondentsOrder(query, sort) + if err != nil { + return nil, fmt.Errorf("failed to set respondents order: %w", err) + } + + err = query.Find(&responsesID).Error if err != nil { return nil, fmt.Errorf("failed to get responsesID: %w", err) } diff --git a/model/respondents_test.go b/model/respondents_test.go index 6dde78ca..41078f11 100644 --- a/model/respondents_test.go +++ b/model/respondents_test.go @@ -1025,6 +1025,7 @@ func TestGetMyResponseIDs(t *testing.T) { } type args struct { + sort string userID string } type expect struct { @@ -1042,6 +1043,7 @@ func TestGetMyResponseIDs(t *testing.T) { { description: "valid user with one resonse", args: args{ + sort: "submitted_at", userID: userOne, }, expect: expect{ @@ -1051,6 +1053,7 @@ func TestGetMyResponseIDs(t *testing.T) { { description: "valid user with multiple responses", args: args{ + sort: "submitted_at", userID: userTwo, }, expect: expect{ @@ -1060,6 +1063,7 @@ func TestGetMyResponseIDs(t *testing.T) { { description: "valid user with no response", args: args{ + sort: "submitted_at", userID: userThree, }, expect: expect{ @@ -1069,7 +1073,7 @@ func TestGetMyResponseIDs(t *testing.T) { } for _, testCase := range testCases { - MyResponseIDs, err := respondentImpl.GetMyResponseIDs(ctx, testCase.args.userID) + MyResponseIDs, err := respondentImpl.GetMyResponseIDs(ctx, testCase.args.sort, testCase.args.userID) if !testCase.expect.isErr { assertion.NoError(err, testCase.description, "no error") From f16d7a3d9e71015dae45d7531cf0ea7cd5cef8c4 Mon Sep 17 00:00:00 2001 From: kaitoyama Date: Tue, 17 Dec 2024 18:23:32 +0900 Subject: [PATCH 83/83] fix for wire --- controller/questionnaire.go | 24 ++++++++++++++++++-- controller/response.go | 20 +++++++++++++++-- handler/handler.go | 16 ++++++++++++- handler/questionnaire.go | 28 ++++++++--------------- handler/response.go | 15 +++++-------- main.go | 4 ++-- model/administratorGroups_impl.go | 2 +- wire.go | 37 +++++++++++++++++++++++++++++-- wire_gen.go | 30 +++++++++++++------------ 9 files changed, 123 insertions(+), 53 deletions(-) diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 88a34bc1..84c216c0 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -32,8 +32,28 @@ type Questionnaire struct { Response } -func NewQuestionnaire() *Questionnaire { - return &Questionnaire{} +func NewQuestionnaire( + questionnaire model.IQuestionnaire, + target model.ITarget, + administrator model.IAdministrator, + question model.IQuestion, + option model.IOption, + scaleLabel model.IScaleLabel, + validation model.IValidation, + transaction model.ITransaction, + webhook traq.IWebhook, +) *Questionnaire { + return &Questionnaire{ + IQuestionnaire: questionnaire, + ITarget: target, + IAdministrator: administrator, + IQuestion: question, + IOption: option, + IScaleLabel: scaleLabel, + IValidation: validation, + ITransaction: transaction, + IWebhook: webhook, + } } const MaxTitleLength = 50 diff --git a/controller/response.go b/controller/response.go index 425b874c..933db74c 100644 --- a/controller/response.go +++ b/controller/response.go @@ -22,8 +22,24 @@ type Response struct { model.IScaleLabel } -func NewResponse() *Response { - return &Response{} +func NewResponse( + questionnaire model.IQuestionnaire, + respondent model.IRespondent, + response model.IResponse, + target model.ITarget, + question model.IQuestion, + validation model.IValidation, + scaleLabel model.IScaleLabel, +) *Response { + return &Response{ + IQuestionnaire: questionnaire, + IRespondent: respondent, + IResponse: response, + ITarget: target, + IQuestion: question, + IValidation: validation, + IScaleLabel: scaleLabel, + } } func (r Response) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesParams, userID string) (openapi.ResponsesWithQuestionnaireInfo, error) { diff --git a/handler/handler.go b/handler/handler.go index f620ece3..aa6ddd82 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -1,3 +1,17 @@ package handler -type Handler struct{} +import "github.com/traPtitech/anke-to/controller" + +type Handler struct { + Questionnaire *controller.Questionnaire + Response *controller.Response +} + +func NewHandler(questionnaire *controller.Questionnaire, + response *controller.Response, +) *Handler { + return &Handler{ + Questionnaire: questionnaire, + Response: response, + } +} diff --git a/handler/questionnaire.go b/handler/questionnaire.go index 954f50b9..1719d207 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/labstack/echo/v4" - "github.com/traPtitech/anke-to/controller" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" ) @@ -14,14 +13,13 @@ import ( // (GET /questionnaires) func (h Handler) GetQuestionnaires(ctx echo.Context, params openapi.GetQuestionnairesParams) error { res := openapi.QuestionnaireList{} - q := controller.NewQuestionnaire() userID, err := getUserID(ctx) if err != nil { ctx.Logger().Errorf("failed to get userID: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) } - res, err = q.GetQuestionnaires(ctx, userID, params) + res, err = h.Questionnaire.GetQuestionnaires(ctx, userID, params) if err != nil { ctx.Logger().Errorf("failed to get questionnaires: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get questionnaires: %w", err)) @@ -50,14 +48,13 @@ func (h Handler) PostQuestionnaire(ctx echo.Context) error { } res := openapi.QuestionnaireDetail{} - q := controller.NewQuestionnaire() userID, err := getUserID(ctx) if err != nil { ctx.Logger().Errorf("failed to get userID: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) } - res, err = q.PostQuestionnaire(ctx, userID, params) + res, err = h.Questionnaire.PostQuestionnaire(ctx, userID, params) if err != nil { ctx.Logger().Errorf("failed to post questionnaire: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to post questionnaire: %w", err)) @@ -69,8 +66,7 @@ func (h Handler) PostQuestionnaire(ctx echo.Context) error { // (GET /questionnaires/{questionnaireID}) func (h Handler) GetQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.QuestionnaireDetail{} - q := controller.NewQuestionnaire() - res, err := q.GetQuestionnaire(ctx, questionnaireID) + res, err := h.Questionnaire.GetQuestionnaire(ctx, questionnaireID) if err != nil { if errors.Is(err, model.ErrRecordNotFound) { return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("questionnaire not found: %w", err)) @@ -89,8 +85,7 @@ func (h Handler) EditQuestionnaire(ctx echo.Context, questionnaireID openapi.Que return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) } - q := controller.NewQuestionnaire() - err := q.EditQuestionnaire(ctx, questionnaireID, params) + err := h.Questionnaire.EditQuestionnaire(ctx, questionnaireID, params) if err != nil { ctx.Logger().Errorf("failed to edit questionnaire: %+v", err) return err @@ -101,8 +96,7 @@ func (h Handler) EditQuestionnaire(ctx echo.Context, questionnaireID openapi.Que // (DELETE /questionnaires/{questionnaireID}) func (h Handler) DeleteQuestionnaire(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { - q := controller.NewQuestionnaire() - err := q.DeleteQuestionnaire(ctx, questionnaireID) + err := h.Questionnaire.DeleteQuestionnaire(ctx, questionnaireID) if err != nil { ctx.Logger().Errorf("failed to delete questionnaire: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to delete questionnaire: %w", err)) @@ -114,8 +108,7 @@ func (h Handler) DeleteQuestionnaire(ctx echo.Context, questionnaireID openapi.Q // (GET /questionnaires/{questionnaireID}/myRemindStatus) func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { res := openapi.QuestionnaireIsRemindEnabled{} - q := controller.NewQuestionnaire() - status, err := q.GetQuestionnaireMyRemindStatus(ctx, questionnaireID) + status, err := h.Questionnaire.GetQuestionnaireMyRemindStatus(ctx, questionnaireID) if err != nil { ctx.Logger().Errorf("failed to get questionnaire my remind status: %+v", err) return err @@ -133,8 +126,7 @@ func (h Handler) EditQuestionnaireMyRemindStatus(ctx echo.Context, questionnaire return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) } - q := controller.NewQuestionnaire() - err := q.EditQuestionnaireMyRemindStatus(ctx, questionnaireID, params.IsRemindEnabled) + err := h.Questionnaire.EditQuestionnaireMyRemindStatus(ctx, questionnaireID, params.IsRemindEnabled) if err != nil { ctx.Logger().Errorf("failed to edit questionnaire my remind status: %+v", err) return err @@ -149,8 +141,7 @@ func (h Handler) GetQuestionnaireResponses(ctx echo.Context, questionnaireID ope ctx.Logger().Errorf("failed to get userID: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) } - q := controller.NewQuestionnaire() - res, err := q.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) + res, err := h.Questionnaire.GetQuestionnaireResponses(ctx, questionnaireID, params, userID) if err != nil { ctx.Logger().Errorf("failed to get questionnaire responses: %+v", err) return err @@ -185,8 +176,7 @@ func (h Handler) PostQuestionnaireResponse(ctx echo.Context, questionnaireID ope return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to validate request body: %w", err)) } - q := controller.NewQuestionnaire() - res, err = q.PostQuestionnaireResponse(ctx, questionnaireID, params, userID) + res, err = h.Questionnaire.PostQuestionnaireResponse(ctx, questionnaireID, params, userID) if err != nil { ctx.Logger().Errorf("failed to post questionnaire response: %+v", err) return err diff --git a/handler/response.go b/handler/response.go index 7db80a56..5bf97c57 100644 --- a/handler/response.go +++ b/handler/response.go @@ -5,7 +5,6 @@ import ( "net/http" "github.com/labstack/echo/v4" - "github.com/traPtitech/anke-to/controller" "github.com/traPtitech/anke-to/openapi" ) @@ -18,8 +17,7 @@ func (h Handler) GetMyResponses(ctx echo.Context, params openapi.GetMyResponsesP return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) } - r := controller.NewResponse() - res, err = r.GetMyResponses(ctx, params, userID) + res, err = h.Response.GetMyResponses(ctx, params, userID) if err != nil { ctx.Logger().Errorf("failed to get my responses: %+v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get my responses: %w", err)) @@ -35,8 +33,7 @@ func (h Handler) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDI return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) } - r := controller.NewResponse() - err = r.DeleteResponse(ctx, responseID, userID) + err = h.Response.DeleteResponse(ctx, responseID, userID) if err != nil { ctx.Logger().Errorf("failed to delete response: %+v", err) return err @@ -49,8 +46,7 @@ func (h Handler) DeleteResponse(ctx echo.Context, responseID openapi.ResponseIDI func (h Handler) GetResponse(ctx echo.Context, responseID openapi.ResponseIDInPath) error { res := openapi.Response{} - r := controller.NewResponse() - res, err := r.GetResponse(ctx, responseID) + res, err := h.Response.GetResponse(ctx, responseID) if err != nil { ctx.Logger().Errorf("failed to get response: %+v", err) return err @@ -78,11 +74,10 @@ func (h Handler) EditResponse(ctx echo.Context, responseID openapi.ResponseIDInP return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to validate request body: %w", err)) } - r := controller.NewResponse() - err = r.EditResponse(ctx, responseID, req) + err = h.Response.EditResponse(ctx, responseID, req) if err != nil { ctx.Logger().Errorf("failed to edit response: %+v", err) - return err + return err } return ctx.NoContent(200) diff --git a/main.go b/main.go index 8668f1ef..2aad8ef1 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( "github.com/labstack/echo/v4/middleware" oapiMiddleware "github.com/oapi-codegen/echo-middleware" "github.com/traPtitech/anke-to/controller" - "github.com/traPtitech/anke-to/handler" "github.com/traPtitech/anke-to/model" "github.com/traPtitech/anke-to/openapi" @@ -83,7 +82,8 @@ func main() { mws.AddRouteConfig("/responses/:responseID", http.MethodPatch, api.RespondentAuthenticate) mws.AddRouteConfig("/responses/:responseID", http.MethodDelete, api.RespondentAuthenticate) - openapi.RegisterHandlers(e, handler.Handler{}) + handlerApi := InjectHandler() + openapi.RegisterHandlers(e, handlerApi) e.Use(mws.ApplyMiddlewares) e.Logger.Fatal(e.Start(port)) diff --git a/model/administratorGroups_impl.go b/model/administratorGroups_impl.go index a4953d43..ccf97e6b 100644 --- a/model/administratorGroups_impl.go +++ b/model/administratorGroups_impl.go @@ -12,7 +12,7 @@ type AdministratorGroup struct{} // NewAdministratorGroup AdministratorGroupRepositoryのコンストラクタ func NewAdministratorGroup() *AdministratorGroup { - return &AdministratorGroup{} + return new(AdministratorGroup) } type AdministratorGroups struct { diff --git a/wire.go b/wire.go index 9a35d84a..c3e060c6 100644 --- a/wire.go +++ b/wire.go @@ -26,9 +26,9 @@ var ( webhookBind = wire.Bind(new(traq.IWebhook), new(*traq.Webhook)) ) -func InjectAPIServer() *handler.Middleware { +func InjectHandler() *handler.Handler { wire.Build( - handler.NewMiddleware, + handler.NewHandler, controller.NewResponse, controller.NewQuestionnaire, model.NewAdministrator, @@ -57,3 +57,36 @@ func InjectAPIServer() *handler.Middleware { return nil } + +func InjectAPIServer() *handler.Middleware { + wire.Build( + // handler.NewHandler, + handler.NewMiddleware, + // controller.NewResponse, + // controller.NewQuestionnaire, + // model.NewAdministrator, + // model.NewOption, + // model.NewQuestionnaire, + // model.NewQuestion, + // model.NewRespondent, + // model.NewResponse, + // model.NewScaleLabel, + // model.NewTarget, + // model.NewValidation, + // model.NewTransaction, + // traq.NewWebhook, + // administratorBind, + // optionBind, + // questionnaireBind, + // questionBind, + // respondentBind, + // responseBind, + // scaleLabelBind, + // targetBind, + // validationBind, + // transactionBind, + // webhookBind, + ) + + return nil +} diff --git a/wire_gen.go b/wire_gen.go index bede2c48..b25ae41a 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run github.com/google/wire/cmd/wire +//go:generate go run -mod=mod github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -8,8 +8,9 @@ package main import ( "github.com/google/wire" + "github.com/traPtitech/anke-to/controller" + "github.com/traPtitech/anke-to/handler" "github.com/traPtitech/anke-to/model" - "github.com/traPtitech/anke-to/router" "github.com/traPtitech/anke-to/traq" ) @@ -19,26 +20,27 @@ import ( // Injectors from wire.go: -func InjectAPIServer() *router.API { - administrator := model.NewAdministrator() - respondent := model.NewRespondent() - question := model.NewQuestion() +func InjectHandler() *handler.Handler { questionnaire := model.NewQuestionnaire() - middleware := router.NewMiddleware(administrator, respondent, question, questionnaire) target := model.NewTarget() + administrator := model.NewAdministrator() + question := model.NewQuestion() option := model.NewOption() scaleLabel := model.NewScaleLabel() validation := model.NewValidation() transaction := model.NewTransaction() webhook := traq.NewWebhook() - routerQuestionnaire := router.NewQuestionnaire(questionnaire, target, administrator, question, option, scaleLabel, validation, transaction, webhook) - routerQuestion := router.NewQuestion(validation, question, option, scaleLabel) + controllerQuestionnaire := controller.NewQuestionnaire(questionnaire, target, administrator, question, option, scaleLabel, validation, transaction, webhook) + respondent := model.NewRespondent() response := model.NewResponse() - routerResponse := router.NewResponse(questionnaire, validation, scaleLabel, respondent, response) - result := router.NewResult(respondent, questionnaire, administrator) - user := router.NewUser(respondent, questionnaire, target, administrator) - api := router.NewAPI(middleware, routerQuestionnaire, routerQuestion, routerResponse, result, user) - return api + controllerResponse := controller.NewResponse(questionnaire, respondent, response, target, question, validation, scaleLabel) + handlerHandler := handler.NewHandler(controllerQuestionnaire, controllerResponse) + return handlerHandler +} + +func InjectAPIServer() *handler.Middleware { + middleware := handler.NewMiddleware() + return middleware } // wire.go: