Adds next birthdays endpoint
This commit is contained in:
parent
0bd05b6efe
commit
1cc687395c
7 changed files with 137 additions and 4 deletions
|
@ -70,10 +70,12 @@ $ make run
|
||||||
- [X] Reduce logger verbosity (through levels)
|
- [X] Reduce logger verbosity (through levels)
|
||||||
- [X] Add pictures to birthday notifications
|
- [X] Add pictures to birthday notifications
|
||||||
- [X] Create a configurable template to fill with each notification
|
- [X] Create a configurable template to fill with each notification
|
||||||
- [ ] Add some endpoints
|
- [X] Add some endpoints
|
||||||
- [ ] Health endpoint
|
- [X] Health endpoint
|
||||||
- [ ] Next birthday endpoint
|
- [X] Next birthdays endpoint
|
||||||
- [ ] Birthday list endpoint
|
- [ ] Birthday list endpoint
|
||||||
|
- [ ] Allow to use a random port in web tests
|
||||||
|
- [ ] Web server should be optional
|
||||||
- [ ] Create different message systems to use with the bot
|
- [ ] Create different message systems to use with the bot
|
||||||
- [X] Telegram
|
- [X] Telegram
|
||||||
- [ ] Email
|
- [ ] Email
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
John Doe,john@doe.com,12345,17/04/2192
|
John Doe,john@doe.com,12345,17/04/2192
|
||||||
|
John Doe The Second,john@doesecond.com,12543,17/04/2192
|
||||||
Jane Doe,jane@doe.com,54321,10/11/2020
|
Jane Doe,jane@doe.com,54321,10/11/2020
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Birthday struct {
|
type Birthday struct {
|
||||||
|
@ -19,6 +20,10 @@ func (b *Birthday) Filename() string {
|
||||||
return fmt.Sprintf("%d_%d_%d_%s.png", b.YearOfBirth, b.MonthOfBirth, b.DayOfBirth, b.Phone)
|
return fmt.Sprintf("%d_%d_%d_%s.png", b.YearOfBirth, b.MonthOfBirth, b.DayOfBirth, b.Phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Birthday) Time() time.Time {
|
||||||
|
return time.Date(b.YearOfBirth, time.Month(b.MonthOfBirth), b.DayOfBirth, 0, 0, 0, 0, time.Now().Location())
|
||||||
|
}
|
||||||
|
|
||||||
func NewBirthdayFromRecord(record []string) (*Birthday, error) {
|
func NewBirthdayFromRecord(record []string) (*Birthday, error) {
|
||||||
if len(record) != 4 {
|
if len(record) != 4 {
|
||||||
return nil, fmt.Errorf("invalid length %d for record", len(record))
|
return nil, fmt.Errorf("invalid length %d for record", len(record))
|
||||||
|
@ -97,3 +102,36 @@ func FilterByDate(birthdays []*Birthday, day, month, year int) []*Birthday {
|
||||||
}
|
}
|
||||||
return filteredBirthdays
|
return filteredBirthdays
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NextBirthdayDate(birthdays []*Birthday, now time.Time) (int, int, int) {
|
||||||
|
nowRounded := now.Round(24 * time.Hour)
|
||||||
|
|
||||||
|
var nextBirthday *Birthday
|
||||||
|
for _, birthday := range birthdays {
|
||||||
|
if nextBirthday == nil {
|
||||||
|
nextBirthday = birthday
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
birthdayTime := birthday.Time()
|
||||||
|
nextBirthdayTime := nextBirthday.Time()
|
||||||
|
|
||||||
|
if nextBirthdayTime.Before(nowRounded) && birthdayTime.After(nowRounded) {
|
||||||
|
nextBirthday = birthday
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if birthdayTime.Before(nextBirthdayTime) {
|
||||||
|
if birthdayTime.After(nowRounded) || nextBirthdayTime.Before(nowRounded) {
|
||||||
|
nextBirthday = birthday
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextBirthday.DayOfBirth, nextBirthday.MonthOfBirth, nextBirthday.YearOfBirth
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextBirthdays(birthdays []*Birthday, now time.Time) []*Birthday {
|
||||||
|
day, month, year := NextBirthdayDate(birthdays, now)
|
||||||
|
return FilterByDate(birthdays, day, month, year)
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -55,3 +56,68 @@ func TestFilename(t *testing.T) {
|
||||||
|
|
||||||
require.Equal(t, "2022_4_6_123456789.png", birthday.Filename())
|
require.Equal(t, "2022_4_6_123456789.png", birthday.Filename())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNextBirthdayDate(t *testing.T) {
|
||||||
|
firstBirthday := &Birthday{
|
||||||
|
YearOfBirth: 1900,
|
||||||
|
MonthOfBirth: 2,
|
||||||
|
DayOfBirth: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
secondBirthday := &Birthday{
|
||||||
|
YearOfBirth: 1900,
|
||||||
|
MonthOfBirth: 8,
|
||||||
|
DayOfBirth: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
birthdays := []*Birthday{firstBirthday, secondBirthday}
|
||||||
|
birthdaysReversed := []*Birthday{secondBirthday, firstBirthday}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Name string
|
||||||
|
Now time.Time
|
||||||
|
Birthdays []*Birthday
|
||||||
|
ExpectedDay int
|
||||||
|
ExpectedMonth int
|
||||||
|
ExpectedYear int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "should find first birthday",
|
||||||
|
Now: time.Date(1900, time.Month(1), 1, 0, 0, 0, 0, time.Now().Location()),
|
||||||
|
ExpectedDay: 1,
|
||||||
|
ExpectedMonth: 2,
|
||||||
|
ExpectedYear: 1900,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "should find second birthday",
|
||||||
|
Now: time.Date(1900, time.Month(4), 1, 0, 0, 0, 0, time.Now().Location()),
|
||||||
|
ExpectedDay: 1,
|
||||||
|
ExpectedMonth: 8,
|
||||||
|
ExpectedYear: 1900,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "should find first birthday for next year",
|
||||||
|
Now: time.Date(1900, time.Month(10), 1, 0, 0, 0, 0, time.Now().Location()),
|
||||||
|
ExpectedDay: 1,
|
||||||
|
ExpectedMonth: 2,
|
||||||
|
ExpectedYear: 1900,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Run("with birthdays sorted", func(t *testing.T) {
|
||||||
|
day, month, year := NextBirthdayDate(birthdays, tc.Now)
|
||||||
|
require.Equal(t, tc.ExpectedDay, day)
|
||||||
|
require.Equal(t, tc.ExpectedMonth, month)
|
||||||
|
require.Equal(t, tc.ExpectedYear, year)
|
||||||
|
})
|
||||||
|
t.Run("with birthdays reversed", func(t *testing.T) {
|
||||||
|
day, month, year := NextBirthdayDate(birthdaysReversed, tc.Now)
|
||||||
|
require.Equal(t, tc.ExpectedDay, day)
|
||||||
|
require.Equal(t, tc.ExpectedMonth, month)
|
||||||
|
require.Equal(t, tc.ExpectedYear, year)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,11 @@ func testConfig(t *testing.T) *model.Config {
|
||||||
require.NoError(t, f.Close())
|
require.NoError(t, f.Close())
|
||||||
require.NoError(t, os.Remove(f.Name()))
|
require.NoError(t, os.Remove(f.Name()))
|
||||||
|
|
||||||
return &model.Config{Birthdays: &model.BirthdaysConfig{File: f.Name()}}
|
// ToDo: allow for a random port to be used
|
||||||
|
return &model.Config{
|
||||||
|
Web: &model.WebConfig{Port: 9090},
|
||||||
|
Birthdays: &model.BirthdaysConfig{File: f.Name()},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupTestHelper(t *testing.T, opts ...Option) *TestHelper {
|
func SetupTestHelper(t *testing.T, opts ...Option) *TestHelper {
|
||||||
|
|
|
@ -167,3 +167,7 @@ func (s *Server) Notify(birthday *model.Birthday) error {
|
||||||
func (s *Server) Birthdays() []*model.Birthday {
|
func (s *Server) Birthdays() []*model.Birthday {
|
||||||
return s.birthdays
|
return s.birthdays
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) NextBirthdays() []*model.Birthday {
|
||||||
|
return model.NextBirthdays(s.birthdays, time.Now())
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -25,6 +26,7 @@ func NewWebServer(server *Server) *WebServer {
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/health", ws.healthHandler)
|
mux.HandleFunc("/health", ws.healthHandler)
|
||||||
|
mux.HandleFunc("/next_birthdays", ws.nextBirthdayHandler)
|
||||||
|
|
||||||
ws.httpServer.Handler = mux
|
ws.httpServer.Handler = mux
|
||||||
|
|
||||||
|
@ -56,3 +58,19 @@ func (ws *WebServer) Stop() error {
|
||||||
func (ws *WebServer) healthHandler(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebServer) healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, "OK")
|
fmt.Fprint(w, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) nextBirthdayHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws.JSON(w, http.StatusOK, ws.server.NextBirthdays())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) JSON(w http.ResponseWriter, statusCode int, data any) {
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
ws.logger.Error("cannot marshal data", "error", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue