From a3fe0998a5dff5e18f71a80406bf2c831593aa8b Mon Sep 17 00:00:00 2001 From: kavos113 Date: Tue, 5 Nov 2024 08:54:55 +0900 Subject: [PATCH 01/28] 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 02/28] 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 03/28] 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 04/28] 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 05/28] 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 06/28] 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 07/28] 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 08/28] 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 09/28] 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 f39b8aef97a30157ad791f3d027a88a24c9cec54 Mon Sep 17 00:00:00 2001 From: kavos113 Date: Sat, 9 Nov 2024 09:07:29 +0900 Subject: [PATCH 10/28] 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 11/28] 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 12/28] 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 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 13/28] 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 14/28] 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 15/28] 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 16/28] 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 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 17/28] 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 18/28] 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", + "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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 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 19/28] 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 20/28] 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 21/28] 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 22/28] 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 23/28] 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 24/28] 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 25/28] 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 26/28] 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 27/28] 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 28/28] 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: