2024-05-09 23:53:33 +04:00
|
|
|
package statistics
|
|
|
|
|
2024-05-15 23:51:10 +04:00
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"dwelling-radio/internal/radio"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
2024-05-09 23:53:33 +04:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2024-05-15 23:51:10 +04:00
|
|
|
|
|
|
|
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()
|
|
|
|
}
|