Skip to content

Commit

Permalink
Merge branch 'fix/openapi' into fix/openapi-anonymous-response
Browse files Browse the repository at this point in the history
  • Loading branch information
kaitoyama committed Dec 4, 2024
2 parents 326fdee + 98d8c16 commit 15150c7
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 35 deletions.
96 changes: 92 additions & 4 deletions controller/questionnaire.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -369,6 +375,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 {
Expand Down Expand Up @@ -494,6 +511,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 {
Expand All @@ -509,12 +532,48 @@ 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 {
// todo: edit remind status
func (q Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int, isRemindEnabled bool) error {
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
}

Expand Down Expand Up @@ -719,3 +778,32 @@ https://anke-to.trap.jp/responses/new/%d`,
questionnaireID,
)
}

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,
)
}
158 changes: 158 additions & 0 deletions controller/reminder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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 (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{}
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)
}
}
}
1 change: 1 addition & 0 deletions docs/db_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,4 @@
| ---------------- | -------- | ---- | --- | ------- | ----- | -------- |
| questionnaire_id | int(11) | NO | PRI | _NULL_ |
| user_traqid | char(32) | NO | PRI | _NULL_ |
| is_canceled | boolean | NO | | false | | アンケートの対象者がキャンセルしたかどうか |
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
12 changes: 9 additions & 3 deletions handler/questionnaire.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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(&params); 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)
}
Expand Down
Loading

0 comments on commit 15150c7

Please sign in to comment.