package statistics import ( "database/sql" "dwelling-radio/internal/radio" "fmt" "time" "github.com/pkg/errors" ) const MostListenedDateFormat string = "02 January 2006 at 15:04:05 MST" type Statistics interface { Add(*radio.Song) error LastNSongs(n int64) ([]radio.Song, error) MostNPopularSongs(n int64) ([]radio.Song, error) MostSimultaneousListeners() (radio.Song, error) Close() error } type ErrPrepareStmt struct { Name string } func (e ErrPrepareStmt) Error() string { return fmt.Sprintf("failed to prepare an SQL statement '%s'", e.Name) } var ErrNoSong = errors.New("no song was passed (a struct is nil or empty)") var ErrSongNotAdded = errors.New("song was not added") type BaseStatistics struct { Db *sql.DB DbDateFormat string StmtHistoryAdd *sql.Stmt StmtSongAdd *sql.Stmt StmtLastNSongs *sql.Stmt StmtMostPopularSongs *sql.Stmt StmtMostSimultaneousListeners *sql.Stmt } func (s *BaseStatistics) Add(song *radio.Song) error { if song == nil || song.Artist == "" || song.Title == "" { return ErrNoSong } tx, err := s.Db.Begin() if err != nil { return err } defer tx.Rollback() row := tx.Stmt(s.StmtSongAdd).QueryRow(song.Artist, song.Title) if row.Err() != nil { return row.Err() } var songID int64 if err := row.Scan(&songID); err != nil { return err } _, err = tx.Stmt(s.StmtHistoryAdd).Exec(song.StartAt.UTC().Format(s.DbDateFormat), songID, song.Listeners, song.PeakListeners) if err != nil { return errors.Wrap(err, ErrSongNotAdded.Error()) } tx.Commit() return nil } func (s *BaseStatistics) LastNSongs(n int64) ([]radio.Song, error) { if n == 0 { return nil, nil } tx, err := s.Db.Begin() if err != nil { return nil, err } defer tx.Rollback() rows, err := tx.Stmt(s.StmtLastNSongs).Query(n) if err != nil { return nil, err } songs := make([]radio.Song, n) i := 0 for rows.Next() { var startAt string if err := rows.Scan(&startAt, &songs[i].Artist, &songs[i].Title, &songs[i].Listeners, &songs[i].PeakListeners); err != nil { return nil, err } songs[i].StartAt, err = time.Parse(s.DbDateFormat, startAt) if err != nil { return nil, err } i++ } tx.Commit() if i == 0 { return nil, nil } lst := make([]radio.Song, i) copy(lst, songs[:]) return lst, nil } func (s *BaseStatistics) MostNPopularSongs(n int64) ([]radio.Song, error) { if n == 0 { return nil, nil } return nil, nil } func (s *BaseStatistics) MostSimultaneousListeners() (radio.Song, error) { return radio.Song{}, nil } func (s *BaseStatistics) Close() error { return s.Db.Close() }