mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 12:01:52 -07:00
wip: endpoint working
This commit is contained in:
parent
3b585f748a
commit
6b73f83484
19 changed files with 510 additions and 243 deletions
8
Makefile
8
Makefile
|
|
@ -27,10 +27,10 @@ postgres.remove:
|
|||
postgres.remove-scratch:
|
||||
docker stop koito-scratch && docker rm koito-scratch
|
||||
|
||||
api.debug:
|
||||
api.debug: postgres.start
|
||||
KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5432?sslmode=disable go run cmd/api/main.go
|
||||
|
||||
api.scratch:
|
||||
api.scratch: postgres.run-scratch
|
||||
KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir/scratch KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5433?sslmode=disable go run cmd/api/main.go
|
||||
|
||||
api.test:
|
||||
|
|
@ -45,7 +45,7 @@ client.dev:
|
|||
docs.dev:
|
||||
cd docs && yarn dev
|
||||
|
||||
client.deps:
|
||||
client.deps:
|
||||
cd client && yarn install
|
||||
|
||||
client.build: client.deps
|
||||
|
|
@ -53,4 +53,4 @@ client.build: client.deps
|
|||
|
||||
test: api.test
|
||||
|
||||
build: api.build client.build
|
||||
build: api.build client.build
|
||||
|
|
|
|||
28
engine/handlers/get_summary.go
Normal file
28
engine/handlers/get_summary.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/gabehf/koito/internal/logger"
|
||||
"github.com/gabehf/koito/internal/summary"
|
||||
"github.com/gabehf/koito/internal/utils"
|
||||
)
|
||||
|
||||
func SummaryHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
l.Debug().Msg("GetTopAlbumsHandler: Received request to retrieve top albums")
|
||||
timeframe := TimeframeFromRequest(r)
|
||||
|
||||
summary, err := summary.GenerateSummary(ctx, store, 1, timeframe, "")
|
||||
if err != nil {
|
||||
l.Err(err).Int("userid", 1).Any("timeframe", timeframe).Msgf("SummaryHandler: Failed to generate summary")
|
||||
utils.WriteError(w, "failed to generate summary", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, summary)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/gabehf/koito/internal/logger"
|
||||
|
|
@ -81,10 +82,93 @@ func OptsFromRequest(r *http.Request) db.GetItemsOpts {
|
|||
Week: week,
|
||||
Month: month,
|
||||
Year: year,
|
||||
From: from,
|
||||
To: to,
|
||||
From: int64(from),
|
||||
To: int64(to),
|
||||
ArtistID: artistId,
|
||||
AlbumID: albumId,
|
||||
TrackID: trackId,
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a request and returns a db.Timeframe representing the week, month, year, period, or unix
|
||||
// time range specified by the request parameters
|
||||
func TimeframeFromRequest(r *http.Request) db.Timeframe {
|
||||
opts := OptsFromRequest(r)
|
||||
now := time.Now()
|
||||
loc := now.Location()
|
||||
|
||||
// if 'from' is set, but 'to' is not set, assume 'to' should be now
|
||||
if opts.From != 0 && opts.To == 0 {
|
||||
opts.To = now.Unix()
|
||||
}
|
||||
|
||||
// YEAR
|
||||
if opts.Year != 0 && opts.Month == 0 && opts.Week == 0 {
|
||||
start := time.Date(opts.Year, 1, 1, 0, 0, 0, 0, loc)
|
||||
end := time.Date(opts.Year+1, 1, 1, 0, 0, 0, 0, loc).Add(-time.Second)
|
||||
|
||||
opts.From = start.Unix()
|
||||
opts.To = end.Unix()
|
||||
}
|
||||
|
||||
// MONTH (+ optional year)
|
||||
if opts.Month != 0 {
|
||||
year := opts.Year
|
||||
if year == 0 {
|
||||
year = now.Year()
|
||||
if int(now.Month()) < opts.Month {
|
||||
year--
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Date(year, time.Month(opts.Month), 1, 0, 0, 0, 0, loc)
|
||||
end := endOfMonth(year, time.Month(opts.Month), loc)
|
||||
|
||||
opts.From = start.Unix()
|
||||
opts.To = end.Unix()
|
||||
}
|
||||
|
||||
// WEEK (+ optional year)
|
||||
if opts.Week != 0 {
|
||||
year := opts.Year
|
||||
if year == 0 {
|
||||
year = now.Year()
|
||||
|
||||
_, currentWeek := now.ISOWeek()
|
||||
if currentWeek < opts.Week {
|
||||
year--
|
||||
}
|
||||
}
|
||||
|
||||
// ISO week 1 is defined as the week with Jan 4 in it
|
||||
jan4 := time.Date(year, 1, 4, 0, 0, 0, 0, loc)
|
||||
week1Start := startOfWeek(jan4)
|
||||
|
||||
start := week1Start.AddDate(0, 0, (opts.Week-1)*7)
|
||||
end := endOfWeek(start)
|
||||
|
||||
opts.From = start.Unix()
|
||||
opts.To = end.Unix()
|
||||
}
|
||||
|
||||
return db.Timeframe{
|
||||
Period: opts.Period,
|
||||
T1u: opts.From,
|
||||
T2u: opts.To,
|
||||
}
|
||||
}
|
||||
func startOfWeek(t time.Time) time.Time {
|
||||
// ISO week: Monday = 1
|
||||
weekday := int(t.Weekday())
|
||||
if weekday == 0 { // Sunday
|
||||
weekday = 7
|
||||
}
|
||||
return time.Date(t.Year(), t.Month(), t.Day()-weekday+1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
func endOfWeek(t time.Time) time.Time {
|
||||
return startOfWeek(t).AddDate(0, 0, 7).Add(-time.Second)
|
||||
}
|
||||
func endOfMonth(year int, month time.Month, loc *time.Location) time.Time {
|
||||
startNextMonth := time.Date(year, month+1, 1, 0, 0, 0, 0, loc)
|
||||
return startNextMonth.Add(-time.Second)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ func bindRoutes(
|
|||
r.Get("/stats", handlers.StatsHandler(db))
|
||||
r.Get("/search", handlers.SearchHandler(db))
|
||||
r.Get("/aliases", handlers.GetAliasesHandler(db))
|
||||
r.Get("/summary", handlers.SummaryHandler(db))
|
||||
})
|
||||
r.Post("/logout", handlers.LogoutHandler(db))
|
||||
if !cfg.RateLimitDisabled() {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
type DB interface {
|
||||
// Get
|
||||
|
||||
GetArtist(ctx context.Context, opts GetArtistOpts) (*models.Artist, error)
|
||||
GetAlbum(ctx context.Context, opts GetAlbumOpts) (*models.Album, error)
|
||||
GetTrack(ctx context.Context, opts GetTrackOpts) (*models.Track, error)
|
||||
|
|
@ -28,7 +29,9 @@ type DB interface {
|
|||
GetUserBySession(ctx context.Context, sessionId uuid.UUID) (*models.User, error)
|
||||
GetUserByUsername(ctx context.Context, username string) (*models.User, error)
|
||||
GetUserByApiKey(ctx context.Context, key string) (*models.User, error)
|
||||
|
||||
// Save
|
||||
|
||||
SaveArtist(ctx context.Context, opts SaveArtistOpts) (*models.Artist, error)
|
||||
SaveArtistAliases(ctx context.Context, id int32, aliases []string, source string) error
|
||||
SaveAlbum(ctx context.Context, opts SaveAlbumOpts) (*models.Album, error)
|
||||
|
|
@ -39,7 +42,9 @@ type DB interface {
|
|||
SaveUser(ctx context.Context, opts SaveUserOpts) (*models.User, error)
|
||||
SaveApiKey(ctx context.Context, opts SaveApiKeyOpts) (*models.ApiKey, error)
|
||||
SaveSession(ctx context.Context, userId int32, expiresAt time.Time, persistent bool) (*models.Session, error)
|
||||
|
||||
// Update
|
||||
|
||||
UpdateArtist(ctx context.Context, opts UpdateArtistOpts) error
|
||||
UpdateTrack(ctx context.Context, opts UpdateTrackOpts) error
|
||||
UpdateAlbum(ctx context.Context, opts UpdateAlbumOpts) error
|
||||
|
|
@ -52,7 +57,9 @@ type DB interface {
|
|||
SetPrimaryTrackAlias(ctx context.Context, id int32, alias string) error
|
||||
SetPrimaryAlbumArtist(ctx context.Context, id int32, artistId int32, value bool) error
|
||||
SetPrimaryTrackArtist(ctx context.Context, id int32, artistId int32, value bool) error
|
||||
|
||||
// Delete
|
||||
|
||||
DeleteArtist(ctx context.Context, id int32) error
|
||||
DeleteAlbum(ctx context.Context, id int32) error
|
||||
DeleteTrack(ctx context.Context, id int32) error
|
||||
|
|
@ -62,26 +69,36 @@ type DB interface {
|
|||
DeleteTrackAlias(ctx context.Context, id int32, alias string) error
|
||||
DeleteSession(ctx context.Context, sessionId uuid.UUID) error
|
||||
DeleteApiKey(ctx context.Context, id int32) error
|
||||
|
||||
// Count
|
||||
|
||||
CountListens(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountListensToItem(ctx context.Context, opts TimeListenedOpts) (int64, error)
|
||||
CountTracks(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountAlbums(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountArtists(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountNewTracks(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountNewAlbums(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountNewArtists(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
// in seconds
|
||||
CountTimeListened(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
// in seconds
|
||||
CountTimeListenedToItem(ctx context.Context, opts TimeListenedOpts) (int64, error)
|
||||
CountUsers(ctx context.Context) (int64, error)
|
||||
// Search
|
||||
|
||||
SearchArtists(ctx context.Context, q string) ([]*models.Artist, error)
|
||||
SearchAlbums(ctx context.Context, q string) ([]*models.Album, error)
|
||||
SearchTracks(ctx context.Context, q string) ([]*models.Track, error)
|
||||
|
||||
// Merge
|
||||
|
||||
MergeTracks(ctx context.Context, fromId, toId int32) error
|
||||
MergeAlbums(ctx context.Context, fromId, toId int32, replaceImage bool) error
|
||||
MergeArtists(ctx context.Context, fromId, toId int32, replaceImage bool) error
|
||||
|
||||
// Etc
|
||||
|
||||
ImageHasAssociation(ctx context.Context, image uuid.UUID) (bool, error)
|
||||
GetImageSource(ctx context.Context, image uuid.UUID) (string, error)
|
||||
AlbumsWithoutImages(ctx context.Context, from int32) ([]*models.Album, error)
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@ type GetItemsOpts struct {
|
|||
Week int // 1-52
|
||||
Month int // 1-12
|
||||
Year int
|
||||
From int // unix timestamp
|
||||
To int // unix timestamp
|
||||
From int64 // unix timestamp
|
||||
To int64 // unix timestamp
|
||||
|
||||
// Used only for getting top tracks
|
||||
ArtistID int
|
||||
|
|
@ -144,10 +144,10 @@ type ListenActivityOpts struct {
|
|||
}
|
||||
|
||||
type TimeListenedOpts struct {
|
||||
Period Period
|
||||
AlbumID int32
|
||||
ArtistID int32
|
||||
TrackID int32
|
||||
Timeframe Timeframe
|
||||
AlbumID int32
|
||||
ArtistID int32
|
||||
TrackID int32
|
||||
}
|
||||
|
||||
type GetExportPageOpts struct {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@ type Timeframe struct {
|
|||
T2u int64
|
||||
}
|
||||
|
||||
func TimeframeToTimeRange(timeframe Timeframe) (t1, t2 time.Time) {
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Period string
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
|
|||
}
|
||||
|
||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||
Period: db.PeriodAllTime,
|
||||
AlbumID: ret.ID,
|
||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||
AlbumID: ret.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetAlbum: CountTimeListenedToItem: %w", err)
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
|
|||
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
||||
}
|
||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||
Period: db.PeriodAllTime,
|
||||
ArtistID: row.ID,
|
||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||
ArtistID: row.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
||||
|
|
@ -70,8 +70,8 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
|
|||
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
||||
}
|
||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||
Period: db.PeriodAllTime,
|
||||
ArtistID: row.ID,
|
||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||
ArtistID: row.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
||||
|
|
@ -105,8 +105,8 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
|
|||
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
||||
}
|
||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||
Period: db.PeriodAllTime,
|
||||
ArtistID: row.ID,
|
||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||
ArtistID: row.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
||||
|
|
|
|||
|
|
@ -4,21 +4,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/gabehf/koito/internal/repository"
|
||||
)
|
||||
|
||||
func (p *Psql) CountListens(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountListens(ctx, repository.CountListensParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -30,14 +22,7 @@ func (p *Psql) CountListens(ctx context.Context, timeframe db.Timeframe) (int64,
|
|||
}
|
||||
|
||||
func (p *Psql) CountTracks(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountTopTracks(ctx, repository.CountTopTracksParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -49,14 +34,7 @@ func (p *Psql) CountTracks(ctx context.Context, timeframe db.Timeframe) (int64,
|
|||
}
|
||||
|
||||
func (p *Psql) CountAlbums(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountTopReleases(ctx, repository.CountTopReleasesParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -68,14 +46,7 @@ func (p *Psql) CountAlbums(ctx context.Context, timeframe db.Timeframe) (int64,
|
|||
}
|
||||
|
||||
func (p *Psql) CountArtists(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountTopArtists(ctx, repository.CountTopArtistsParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -86,15 +57,9 @@ func (p *Psql) CountArtists(ctx context.Context, timeframe db.Timeframe) (int64,
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// in seconds
|
||||
func (p *Psql) CountTimeListened(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountTimeListened(ctx, repository.CountTimeListenedParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -105,9 +70,9 @@ func (p *Psql) CountTimeListened(ctx context.Context, timeframe db.Timeframe) (i
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// in seconds
|
||||
func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListenedOpts) (int64, error) {
|
||||
t2 := time.Now()
|
||||
t1 := db.StartTimeFromPeriod(opts.Period)
|
||||
t1, t2 := db.TimeframeToTimeRange(opts.Timeframe)
|
||||
|
||||
if opts.ArtistID > 0 {
|
||||
count, err := p.q.CountTimeListenedToArtist(ctx, repository.CountTimeListenedToArtistParams{
|
||||
|
|
@ -143,15 +108,45 @@ func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListened
|
|||
return 0, errors.New("CountTimeListenedToItem: an id must be provided")
|
||||
}
|
||||
|
||||
func (p *Psql) CountNewTracks(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
func (p *Psql) CountListensToItem(ctx context.Context, opts db.TimeListenedOpts) (int64, error) {
|
||||
t1, t2 := db.TimeframeToTimeRange(opts.Timeframe)
|
||||
|
||||
if opts.ArtistID > 0 {
|
||||
count, err := p.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
ArtistID: opts.ArtistID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CountListensToItem (Artist): %w", err)
|
||||
}
|
||||
return count, nil
|
||||
} else if opts.AlbumID > 0 {
|
||||
count, err := p.q.CountListensFromRelease(ctx, repository.CountListensFromReleaseParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
ReleaseID: opts.AlbumID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CountListensToItem (Album): %w", err)
|
||||
}
|
||||
return count, nil
|
||||
} else if opts.TrackID > 0 {
|
||||
count, err := p.q.CountListensFromTrack(ctx, repository.CountListensFromTrackParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
TrackID: opts.TrackID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CountListensToItem (Track): %w", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
return 0, errors.New("CountListensToItem: an id must be provided")
|
||||
}
|
||||
|
||||
func (p *Psql) CountNewTracks(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountNewTracks(ctx, repository.CountNewTracksParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -163,14 +158,7 @@ func (p *Psql) CountNewTracks(ctx context.Context, timeframe db.Timeframe) (int6
|
|||
}
|
||||
|
||||
func (p *Psql) CountNewAlbums(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountNewReleases(ctx, repository.CountNewReleasesParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
@ -182,14 +170,7 @@ func (p *Psql) CountNewAlbums(ctx context.Context, timeframe db.Timeframe) (int6
|
|||
}
|
||||
|
||||
func (p *Psql) CountNewArtists(ctx context.Context, timeframe db.Timeframe) (int64, error) {
|
||||
var t1, t2 time.Time
|
||||
if timeframe.T1u == 0 && timeframe.T2u == 0 {
|
||||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(timeframe.Period)
|
||||
} else {
|
||||
t1 = time.Unix(timeframe.T1u, 0)
|
||||
t2 = time.Unix(timeframe.T2u, 0)
|
||||
}
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
count, err := p.q.CountNewArtists(ctx, repository.CountNewArtistsParams{
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ func TestCountTimeListenedToArtist(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
period := db.PeriodAllTime
|
||||
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Period: period, ArtistID: 1})
|
||||
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, ArtistID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 400, count)
|
||||
truncateTestData(t)
|
||||
|
|
@ -141,7 +141,7 @@ func TestCountTimeListenedToAlbum(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
period := db.PeriodAllTime
|
||||
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Period: period, AlbumID: 2})
|
||||
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, AlbumID: 2})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 300, count)
|
||||
truncateTestData(t)
|
||||
|
|
@ -151,8 +151,38 @@ func TestCountTimeListenedToTrack(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
period := db.PeriodAllTime
|
||||
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Period: period, TrackID: 3})
|
||||
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, TrackID: 3})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 200, count)
|
||||
truncateTestData(t)
|
||||
}
|
||||
|
||||
func TestListensToArtist(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
period := db.PeriodAllTime
|
||||
count, err := store.CountListensToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, ArtistID: 1})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 4, count)
|
||||
truncateTestData(t)
|
||||
}
|
||||
|
||||
func TestListensToAlbum(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
period := db.PeriodAllTime
|
||||
count, err := store.CountListensToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, AlbumID: 2})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 3, count)
|
||||
truncateTestData(t)
|
||||
}
|
||||
|
||||
func TestListensToTrack(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
period := db.PeriodAllTime
|
||||
count, err := store.CountListensToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, TrackID: 3})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 2, count)
|
||||
truncateTestData(t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
|
|||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(opts.Period)
|
||||
}
|
||||
if opts.From != 0 || opts.To != 0 {
|
||||
t1 = time.Unix(opts.From, 0)
|
||||
t2 = time.Unix(opts.To, 0)
|
||||
}
|
||||
if opts.Limit == 0 {
|
||||
opts.Limit = DefaultItemsPerPage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ func (d *Psql) GetTopArtistsPaginated(ctx context.Context, opts db.GetItemsOpts)
|
|||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(opts.Period)
|
||||
}
|
||||
if opts.From != 0 || opts.To != 0 {
|
||||
t1 = time.Unix(opts.From, 0)
|
||||
t2 = time.Unix(opts.To, 0)
|
||||
}
|
||||
if opts.Limit == 0 {
|
||||
opts.Limit = DefaultItemsPerPage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
|
|||
t2 = time.Now()
|
||||
t1 = db.StartTimeFromPeriod(opts.Period)
|
||||
}
|
||||
if opts.From != 0 || opts.To != 0 {
|
||||
t1 = time.Unix(opts.From, 0)
|
||||
t2 = time.Unix(opts.To, 0)
|
||||
}
|
||||
if opts.Limit == 0 {
|
||||
opts.Limit = DefaultItemsPerPage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
|
|||
}
|
||||
|
||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||
Period: db.PeriodAllTime,
|
||||
TrackID: track.ID,
|
||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||
TrackID: track.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTrack: CountTimeListenedToItem: %w", err)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -34,15 +33,16 @@ func ImportListenBrainzExport(ctx context.Context, store db.DB, mbzc mbz.MusicBr
|
|||
for _, f := range r.File {
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
l.Debug().Msgf("File %s is dir, skipping...", f.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(f.Name, "listens/") && strings.HasSuffix(f.Name, ".jsonl") {
|
||||
fmt.Println("Found:", f.Name)
|
||||
l.Info().Msgf("Found: %s\n", f.Name)
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
log.Printf("Failed to open %s: %v\n", f.Name, err)
|
||||
l.Err(err).Msgf("Failed to open %s\n", f.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ func ImportListenBrainzFile(ctx context.Context, store db.DB, mbzc mbz.MusicBrai
|
|||
payload := new(handlers.LbzSubmitListenPayload)
|
||||
err := json.Unmarshal(line, payload)
|
||||
if err != nil {
|
||||
fmt.Println("Error unmarshaling JSON:", err)
|
||||
l.Err(err).Msg("Error unmarshaling JSON")
|
||||
continue
|
||||
}
|
||||
ts := time.Unix(payload.ListenedAt, 0)
|
||||
|
|
|
|||
|
|
@ -1,54 +1,19 @@
|
|||
package summary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/gabehf/koito/internal/cfg"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Title string
|
||||
TopArtistImage string
|
||||
TopArtists []struct {
|
||||
Name string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}
|
||||
TopAlbumImage string
|
||||
TopAlbums []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}
|
||||
TopTrackImage string
|
||||
TopTracks []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}
|
||||
MinutesListened int
|
||||
Plays int
|
||||
AvgPlaysPerDay float32
|
||||
UniqueTracks int32
|
||||
UniqueAlbums int32
|
||||
UniqueArtists int32
|
||||
NewTracks int32
|
||||
NewAlbums int32
|
||||
NewArtists int32
|
||||
}
|
||||
|
||||
var (
|
||||
assetPath = path.Join("..", "..", "assets")
|
||||
titleFontPath = path.Join(assetPath, "LeagueSpartan-Medium.ttf")
|
||||
|
|
@ -63,69 +28,69 @@ var (
|
|||
)
|
||||
|
||||
// lots of code borrowed from https://medium.com/@daniel.ruizcamacho/how-to-create-an-image-in-golang-step-by-step-4416affe088f
|
||||
func GenerateImage(summary *Summary) error {
|
||||
base := image.NewRGBA(image.Rect(0, 0, 750, 1100))
|
||||
draw.Draw(base, base.Bounds(), image.NewUniform(color.Black), image.Pt(0, 0), draw.Over)
|
||||
// func GenerateImage(summary *Summary) error {
|
||||
// base := image.NewRGBA(image.Rect(0, 0, 750, 1100))
|
||||
// draw.Draw(base, base.Bounds(), image.NewUniform(color.Black), image.Pt(0, 0), draw.Over)
|
||||
|
||||
file, err := os.Create(path.Join(cfg.ConfigDir(), "summary.png"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
// file, err := os.Create(path.Join(cfg.ConfigDir(), "summary.png"))
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
// add title
|
||||
if err := addText(base, summary.Title, "", image.Pt(paddingLg, 60), titleFontPath, titleFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
// add images
|
||||
if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120), featuredImageSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)), featuredImageSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)*2), featuredImageSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
// top artists text
|
||||
if err := addText(base, "Top Artists", "", image.Pt(featureTextStart, 132), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
for rank, artist := range summary.TopArtists {
|
||||
if rank == 0 {
|
||||
if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// top albums text
|
||||
if err := addText(base, "Top Albums", "", image.Pt(featureTextStart, 132+featuredImageSize+paddingLg), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
for rank, album := range summary.TopAlbums {
|
||||
if rank == 0 {
|
||||
if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// top tracks text
|
||||
// // add title
|
||||
// if err := addText(base, summary.Title, "", image.Pt(paddingLg, 60), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// // add images
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)*2), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// // top artists text
|
||||
// if err := addText(base, "Top Artists", "", image.Pt(featureTextStart, 132), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// for rank, artist := range summary.TopArtists {
|
||||
// if rank == 0 {
|
||||
// if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// } else {
|
||||
// if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // top albums text
|
||||
// if err := addText(base, "Top Albums", "", image.Pt(featureTextStart, 132+featuredImageSize+paddingLg), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// for rank, album := range summary.TopAlbums {
|
||||
// if rank == 0 {
|
||||
// if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// } else {
|
||||
// if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // top tracks text
|
||||
|
||||
// stats text
|
||||
// // stats text
|
||||
|
||||
if err := png.Encode(file, base); err != nil {
|
||||
return fmt.Errorf("GenerateImage: png.Encode: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// if err := png.Encode(file, base); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: png.Encode: %w", err)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func addImage(baseImage *image.RGBA, path string, point image.Point, height int) error {
|
||||
templateFile, err := os.Open(path)
|
||||
|
|
|
|||
141
internal/summary/summary.go
Normal file
141
internal/summary/summary.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
package summary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/gabehf/koito/internal/models"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
TopArtists []*models.Artist `json:"top_artists"` // ListenCount and TimeListened are overriden with stats from timeframe
|
||||
TopAlbums []*models.Album `json:"top_albums"` // ListenCount and TimeListened are overriden with stats from timeframe
|
||||
TopTracks []*models.Track `json:"top_tracks"` // ListenCount and TimeListened are overriden with stats from timeframe
|
||||
MinutesListened int `json:"minutes_listened"`
|
||||
AvgMinutesPerDay int `json:"avg_minutes_listened_per_day"`
|
||||
Plays int `json:"plays"`
|
||||
AvgPlaysPerDay float32 `json:"avg_plays_per_day"`
|
||||
UniqueTracks int `json:"unique_tracks"`
|
||||
UniqueAlbums int `json:"unique_albums"`
|
||||
UniqueArtists int `json:"unique_artists"`
|
||||
NewTracks int `json:"new_tracks"`
|
||||
NewAlbums int `json:"new_albums"`
|
||||
NewArtists int `json:"new_artists"`
|
||||
}
|
||||
|
||||
func GenerateSummary(ctx context.Context, store db.DB, userId int32, timeframe db.Timeframe, title string) (summary *Summary, err error) {
|
||||
// l := logger.FromContext(ctx)
|
||||
|
||||
summary = new(Summary)
|
||||
|
||||
topArtists, err := store.GetTopArtistsPaginated(ctx, db.GetItemsOpts{Page: 1, Limit: 5, From: timeframe.T1u, To: timeframe.T2u, Period: timeframe.Period})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopArtists = topArtists.Items
|
||||
// replace ListenCount and TimeListened with stats from timeframe
|
||||
for i, artist := range summary.TopArtists {
|
||||
timelistened, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{ArtistID: artist.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
listens, err := store.CountListensToItem(ctx, db.TimeListenedOpts{ArtistID: artist.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopArtists[i].TimeListened = timelistened
|
||||
summary.TopArtists[i].ListenCount = listens
|
||||
}
|
||||
|
||||
topAlbums, err := store.GetTopAlbumsPaginated(ctx, db.GetItemsOpts{Page: 1, Limit: 5, From: timeframe.T1u, To: timeframe.T2u, Period: timeframe.Period})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopAlbums = topAlbums.Items
|
||||
// replace ListenCount and TimeListened with stats from timeframe
|
||||
for i, album := range summary.TopAlbums {
|
||||
timelistened, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{AlbumID: album.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
listens, err := store.CountListensToItem(ctx, db.TimeListenedOpts{AlbumID: album.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopAlbums[i].TimeListened = timelistened
|
||||
summary.TopAlbums[i].ListenCount = listens
|
||||
}
|
||||
|
||||
topTracks, err := store.GetTopTracksPaginated(ctx, db.GetItemsOpts{Page: 1, Limit: 5, From: timeframe.T1u, To: timeframe.T2u, Period: timeframe.Period})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopTracks = topTracks.Items
|
||||
// replace ListenCount and TimeListened with stats from timeframe
|
||||
for i, track := range summary.TopTracks {
|
||||
timelistened, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{TrackID: track.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
listens, err := store.CountListensToItem(ctx, db.TimeListenedOpts{TrackID: track.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopTracks[i].TimeListened = timelistened
|
||||
summary.TopTracks[i].ListenCount = listens
|
||||
}
|
||||
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
daycount := int(t2.Sub(t1).Hours() / 24)
|
||||
// bandaid
|
||||
if daycount == 0 {
|
||||
daycount = 1
|
||||
}
|
||||
|
||||
tmp, err := store.CountTimeListened(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.MinutesListened = int(tmp) / 60
|
||||
summary.AvgMinutesPerDay = summary.MinutesListened / daycount
|
||||
tmp, err = store.CountListens(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.Plays = int(tmp)
|
||||
summary.AvgPlaysPerDay = float32(summary.Plays) / float32(daycount)
|
||||
tmp, err = store.CountTracks(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.UniqueTracks = int(tmp)
|
||||
tmp, err = store.CountAlbums(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.UniqueAlbums = int(tmp)
|
||||
tmp, err = store.CountArtists(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.UniqueArtists = int(tmp)
|
||||
tmp, err = store.CountNewTracks(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.NewTracks = int(tmp)
|
||||
tmp, err = store.CountNewAlbums(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.NewAlbums = int(tmp)
|
||||
tmp, err = store.CountNewArtists(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.NewArtists = int(tmp)
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package summary_test
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/gabehf/koito/internal/cfg"
|
||||
"github.com/gabehf/koito/internal/summary"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.M) {
|
||||
|
|
@ -33,55 +30,55 @@ func TestMain(t *testing.M) {
|
|||
t.Run()
|
||||
}
|
||||
|
||||
func TestGenerateImage(t *testing.T) {
|
||||
s := summary.Summary{
|
||||
Title: "20XX Rewind",
|
||||
TopArtistImage: path.Join("..", "..", "test_assets", "yuu.jpg"),
|
||||
TopArtists: []struct {
|
||||
Name string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}{
|
||||
{"CHUU", 738, 7321},
|
||||
{"Paramore", 738, 7321},
|
||||
{"ano", 738, 7321},
|
||||
{"NELKE", 738, 7321},
|
||||
{"ILLIT", 738, 7321},
|
||||
},
|
||||
TopAlbumImage: "",
|
||||
TopAlbums: []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}{
|
||||
{"Only cry in the rain", 738, 7321},
|
||||
{"Paramore", 738, 7321},
|
||||
{"ano", 738, 7321},
|
||||
{"NELKE", 738, 7321},
|
||||
{"ILLIT", 738, 7321},
|
||||
},
|
||||
TopTrackImage: "",
|
||||
TopTracks: []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}{
|
||||
{"虹の色よ鮮やかであれ (NELKE ver.)", 321, 12351},
|
||||
{"Paramore", 738, 7321},
|
||||
{"ano", 738, 7321},
|
||||
{"NELKE", 738, 7321},
|
||||
{"ILLIT", 738, 7321},
|
||||
},
|
||||
MinutesListened: 0,
|
||||
Plays: 0,
|
||||
AvgPlaysPerDay: 0,
|
||||
UniqueTracks: 0,
|
||||
UniqueAlbums: 0,
|
||||
UniqueArtists: 0,
|
||||
NewTracks: 0,
|
||||
NewAlbums: 0,
|
||||
NewArtists: 0,
|
||||
}
|
||||
func TestGenerateSummary(t *testing.T) {
|
||||
// s := summary.Summary{
|
||||
// Title: "20XX Rewind",
|
||||
// TopArtistImage: path.Join("..", "..", "test_assets", "yuu.jpg"),
|
||||
// TopArtists: []struct {
|
||||
// Name string
|
||||
// Plays int
|
||||
// MinutesListened int
|
||||
// }{
|
||||
// {"CHUU", 738, 7321},
|
||||
// {"Paramore", 738, 7321},
|
||||
// {"ano", 738, 7321},
|
||||
// {"NELKE", 738, 7321},
|
||||
// {"ILLIT", 738, 7321},
|
||||
// },
|
||||
// TopAlbumImage: "",
|
||||
// TopAlbums: []struct {
|
||||
// Title string
|
||||
// Plays int
|
||||
// MinutesListened int
|
||||
// }{
|
||||
// {"Only cry in the rain", 738, 7321},
|
||||
// {"Paramore", 738, 7321},
|
||||
// {"ano", 738, 7321},
|
||||
// {"NELKE", 738, 7321},
|
||||
// {"ILLIT", 738, 7321},
|
||||
// },
|
||||
// TopTrackImage: "",
|
||||
// TopTracks: []struct {
|
||||
// Title string
|
||||
// Plays int
|
||||
// MinutesListened int
|
||||
// }{
|
||||
// {"虹の色よ鮮やかであれ (NELKE ver.)", 321, 12351},
|
||||
// {"Paramore", 738, 7321},
|
||||
// {"ano", 738, 7321},
|
||||
// {"NELKE", 738, 7321},
|
||||
// {"ILLIT", 738, 7321},
|
||||
// },
|
||||
// MinutesListened: 0,
|
||||
// Plays: 0,
|
||||
// AvgPlaysPerDay: 0,
|
||||
// UniqueTracks: 0,
|
||||
// UniqueAlbums: 0,
|
||||
// UniqueArtists: 0,
|
||||
// NewTracks: 0,
|
||||
// NewAlbums: 0,
|
||||
// NewArtists: 0,
|
||||
// }
|
||||
|
||||
assert.NoError(t, summary.GenerateImage(&s))
|
||||
// assert.NoError(t, summary.GenerateImage(&s))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue