From 12ddb567e30e1d8dda7445374f44bc4200967d12 Mon Sep 17 00:00:00 2001 From: William Perron Date: Tue, 14 Nov 2023 21:51:02 -0500 Subject: [PATCH] Add /schedule command Adds the Discord slash command to get the schedule for the next few weeks. Also updates the required Go version to 1.21 to benefit from the new `time.DateOnly` format that's gonna be used in the absences table. --- Dockerfile | 2 +- absences.go | 36 ++++++++++++++++++++----------- absences_test.go | 12 +++++++++-- cmd/themis-server/main.go | 45 ++++++++++++++++++++++++++++++++++++++- fly.toml | 2 +- go.mod | 2 +- go.sum | 1 + 7 files changed, 82 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1a2490c..0762816 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19-buster as builder +FROM golang:1.21-bullseye as builder WORKDIR /app COPY . . RUN mkdir ./bin; go build -buildvcs=false -o ./bin ./cmd/... diff --git a/absences.go b/absences.go index 9a54ad1..d5e4164 100644 --- a/absences.go +++ b/absences.go @@ -22,7 +22,7 @@ func (s *Store) AddAbsence(ctx context.Context, session time.Time, userId string return fmt.Errorf("failed to prepare absence query: %w", err) } - _, err = stmt.ExecContext(ctx, session.Format("2006-01-02"), userId) + _, err = stmt.ExecContext(ctx, session.Format(time.DateOnly), userId) if err != nil { return fmt.Errorf("failed to insert absence: %w", err) } @@ -42,7 +42,7 @@ func (s *Store) GetAbsentees(ctx context.Context, session time.Time) ([]string, return nil, fmt.Errorf("failed to prepare query: %w", err) } - rows, err := stmt.QueryContext(ctx, session.Format("2006-01-02")) + rows, err := stmt.QueryContext(ctx, session.Format(time.DateOnly)) if err != nil { return nil, fmt.Errorf("failed to execute query: %w", err) } @@ -61,12 +61,13 @@ func (s *Store) GetAbsentees(ctx context.Context, session time.Time) ([]string, return absentees, nil } -type Foo struct { - session_date string - userid string -} +// map session_date -> list of absentees +type Schedule map[string][]string + +func (s *Store) GetSchedule(ctx context.Context, from, to time.Time) (Schedule, error) { + schedule := make(Schedule) + initSchedule(schedule, from, to) -func (s *Store) GetSchedule(ctx context.Context, from, to time.Time) ([]Foo, error) { tx, err := s.db.Begin() if err != nil { return nil, fmt.Errorf("failed to begin transaction: %w", err) @@ -78,21 +79,32 @@ func (s *Store) GetSchedule(ctx context.Context, from, to time.Time) ([]Foo, err return nil, fmt.Errorf("failed to prepare query: %w", err) } - rows, err := stmt.QueryContext(ctx, from.Format("2006-01-02"), to.Format("2006-01-02")) + rows, err := stmt.QueryContext(ctx, from.Format(time.DateOnly), to.Format(time.DateOnly)) if err != nil { return nil, fmt.Errorf("failed to execute query: %w", err) } - schedule := make([]Foo, 0) for rows.Next() { - var foo Foo - err = rows.Scan(&foo.session_date, &foo.userid) + var date string + var user string + err = rows.Scan(&date, &user) if err != nil { return nil, fmt.Errorf("failed to scan row: %w", err) } - schedule = append(schedule, foo) + if _, ok := schedule[date]; ok { + schedule[date] = append(schedule[date], user) + } else { + schedule[date] = []string{user} + } } return schedule, nil } + +func initSchedule(schedule Schedule, from, to time.Time) { + for from.Before(to) || from.Equal(to) { + schedule[from.Format(time.DateOnly)] = []string{} + from = from.AddDate(0, 0, 7) + } +} diff --git a/absences_test.go b/absences_test.go index c6f8403..710e4fe 100644 --- a/absences_test.go +++ b/absences_test.go @@ -35,7 +35,15 @@ func TestGetSchedule(t *testing.T) { _ = store.AddAbsence(context.TODO(), now.Add(7*24*time.Hour), "foobar") - schedule, err := store.GetSchedule(context.TODO(), now, now.Add(2*7*24*time.Hour)) + schedule, err := store.GetSchedule(context.TODO(), now, now.AddDate(0, 0, 14)) assert.NoError(t, err) - assert.Equal(t, 1, len(schedule)) + // reason being, the schedule should initialize to the desired time range + assert.Equal(t, 3, len(schedule)) + for d, a := range schedule { + if d == now.Add(7*24*time.Hour).Format(time.DateOnly) { + assert.Equal(t, 1, len(a)) + } else { + assert.Equal(t, 0, len(a)) + } + } } diff --git a/cmd/themis-server/main.go b/cmd/themis-server/main.go index 3c903d1..292efae 100644 --- a/cmd/themis-server/main.go +++ b/cmd/themis-server/main.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "os/signal" + "sort" "strconv" "strings" "syscall" @@ -152,6 +153,11 @@ func main() { }, }, }, + { + Name: "schedule", + Description: "Get the schedule for the following weeks.", + Type: discordgo.ChatApplicationCommand, + }, } handlers := map[string]Handler{ "info": func(s *discordgo.Session, i *discordgo.InteractionCreate) { @@ -438,7 +444,44 @@ func main() { } }, "schedule": func(s *discordgo.Session, i *discordgo.InteractionCreate) { - store.GetSchedule(ctx, time.Now(), time.Now().Add(4*7*24*time.Hour)) + // get schedule from now to 4 mondays into the future + sched, err := store.GetSchedule(ctx, themis.NextMonday(), themis.NextMonday().Add(4*7*24*time.Hour)) + if err != nil { + log.Error().Err(err).Msg("failed to get schedule") + if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "failed to get schedule, check logs for more info.", + }, + }); err != nil { + log.Error().Err(err).Msg("failed to respond to interaction") + } + } + + sb := strings.Builder{} + keys := make([]string, 0, len(sched)) + for k := range sched { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, d := range keys { + sb.WriteString(d + ": ") + if len(sched[d]) == 0 { + sb.WriteString("Everyone is available!\n") + } else { + sb.WriteString(strings.Join(sched[d], ", ") + " won't be able to make it") + } + } + + if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: sb.String(), + }, + }); err != nil { + log.Error().Err(err).Msg("failed to respond to interaction") + } }, } diff --git a/fly.toml b/fly.toml index cc2961b..0421d03 100644 --- a/fly.toml +++ b/fly.toml @@ -2,7 +2,7 @@ app = "themis" kill_signal = "SIGINT" -kill_timeout = 5 +kill_timeout = 30 processes = [] [env] diff --git a/go.mod b/go.mod index 7c0f157..8e5e82f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.wperron.io/themis -go 1.19 +go 1.21 require ( github.com/bwmarrin/discordgo v0.26.1 diff --git a/go.sum b/go.sum index 2de4c7e..9eb4eca 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,7 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=