mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 21:48:18 -08:00
* add dev branch container to workflow * correctly set the default range of ActivityGrid * fix: set name/short_name to koito (#61) * fix dev container push workflow * fix: race condition with using getComputedStyle primary color for dynamic activity grid darkening (#76) * Fix race condition with using getComputedStyle primary color for dynamic activity grid darkening Instead just use the color from the current theme directly. Tested works on initial load and theme changes. Fixes https://github.com/gabehf/Koito/issues/75 * Rework theme provider to provide the actual Theme object throughtout the app, in addition to the name Split name out of the Theme struct to simplify custom theme saving/reading * fix: set first artist listed as primary by default (#81) * feat: add server-side configuration with default theme (#90) * docs: add example for usage of the main listenbrainz instance (#71) * docs: add example for usage of the main listenbrainz instance * Update scrobbler.md --------- Co-authored-by: Gabe Farrell <90876006+gabehf@users.noreply.github.com> * feat: add server-side cfg and default theme * fix: repair custom theme --------- Co-authored-by: m0d3rnX <jesper@posteo.de> * docs: add default theme cfg option to docs * feat: add ability to manually scrobble track (#91) * feat: add button to manually scrobble from ui * fix: ensure timestamp is in the past, log fix * test: add integration test * feat: add first listened to dates for media items (#92) * fix: ensure error checks for ErrNoRows * feat: add now playing endpoint and ui (#93) * wip * feat: add now playing * fix: set default theme when config is not set * feat: fetch images from subsonic server (#94) * fix: useQuery instead of useEffect for now playing * feat: custom artist separator regex (#95) * Fix race condition with using getComputedStyle primary color for dynamic activity grid darkening Instead just use the color from the current theme directly. Tested works on initial load and theme changes. Fixes https://github.com/gabehf/Koito/issues/75 * Rework theme provider to provide the actual Theme object throughtout the app, in addition to the name Split name out of the Theme struct to simplify custom theme saving/reading * feat: add server-side configuration with default theme (#90) * docs: add example for usage of the main listenbrainz instance (#71) * docs: add example for usage of the main listenbrainz instance * Update scrobbler.md --------- Co-authored-by: Gabe Farrell <90876006+gabehf@users.noreply.github.com> * feat: add server-side cfg and default theme * fix: repair custom theme --------- Co-authored-by: m0d3rnX <jesper@posteo.de> * fix: rebase errors --------- Co-authored-by: pet <128837728+againstpetra@users.noreply.github.com> Co-authored-by: mlandry <mike.landry@gmail.com> Co-authored-by: m0d3rnX <jesper@posteo.de>
616 lines
16 KiB
Go
616 lines
16 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.30.0
|
|
// source: year.sql
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const artistsOnlyPlayedOnceInYear = `-- name: ArtistsOnlyPlayedOnceInYear :many
|
|
SELECT
|
|
a.id AS artist_id,
|
|
a.name,
|
|
COUNT(l.*) AS listen_count
|
|
FROM listens l
|
|
JOIN artist_tracks at ON at.track_id = l.track_id
|
|
JOIN artists_with_name a ON a.id = at.artist_id
|
|
WHERE EXTRACT(YEAR FROM l.listened_at) = $1::int AND l.user_id = $2::int
|
|
GROUP BY a.id, a.name
|
|
HAVING COUNT(*) = 1
|
|
`
|
|
|
|
type ArtistsOnlyPlayedOnceInYearParams struct {
|
|
Year int32
|
|
UserID int32
|
|
}
|
|
|
|
type ArtistsOnlyPlayedOnceInYearRow struct {
|
|
ArtistID int32
|
|
Name string
|
|
ListenCount int64
|
|
}
|
|
|
|
func (q *Queries) ArtistsOnlyPlayedOnceInYear(ctx context.Context, arg ArtistsOnlyPlayedOnceInYearParams) ([]ArtistsOnlyPlayedOnceInYearRow, error) {
|
|
rows, err := q.db.Query(ctx, artistsOnlyPlayedOnceInYear, arg.Year, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ArtistsOnlyPlayedOnceInYearRow
|
|
for rows.Next() {
|
|
var i ArtistsOnlyPlayedOnceInYearRow
|
|
if err := rows.Scan(&i.ArtistID, &i.Name, &i.ListenCount); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getArtistCountInYear = `-- name: GetArtistCountInYear :one
|
|
SELECT
|
|
COUNT(DISTINCT at.artist_id) AS artist_count
|
|
FROM listens l
|
|
JOIN artist_tracks at ON at.track_id = l.track_id
|
|
WHERE l.user_id = $1::int
|
|
AND EXTRACT(YEAR FROM l.listened_at) = $2::int
|
|
`
|
|
|
|
type GetArtistCountInYearParams struct {
|
|
UserID int32
|
|
Year int32
|
|
}
|
|
|
|
func (q *Queries) GetArtistCountInYear(ctx context.Context, arg GetArtistCountInYearParams) (int64, error) {
|
|
row := q.db.QueryRow(ctx, getArtistCountInYear, arg.UserID, arg.Year)
|
|
var artist_count int64
|
|
err := row.Scan(&artist_count)
|
|
return artist_count, err
|
|
}
|
|
|
|
const getArtistWithLongestGapInYear = `-- name: GetArtistWithLongestGapInYear :one
|
|
WITH first_listens AS (
|
|
SELECT
|
|
l.user_id,
|
|
at.artist_id,
|
|
MIN(l.listened_at::date) AS first_listen_of_year
|
|
FROM listens l
|
|
JOIN artist_tracks at ON at.track_id = l.track_id
|
|
WHERE EXTRACT(YEAR FROM l.listened_at) = $1::int
|
|
GROUP BY l.user_id, at.artist_id
|
|
),
|
|
last_listens AS (
|
|
SELECT
|
|
l.user_id,
|
|
at.artist_id,
|
|
MAX(l.listened_at::date) AS last_listen
|
|
FROM listens l
|
|
JOIN artist_tracks at ON at.track_id = l.track_id
|
|
WHERE l.listened_at < $2::date
|
|
GROUP BY l.user_id, at.artist_id
|
|
),
|
|
comebacks AS (
|
|
SELECT
|
|
f.user_id,
|
|
f.artist_id,
|
|
f.first_listen_of_year,
|
|
p.last_listen,
|
|
(f.first_listen_of_year - p.last_listen) AS gap_days
|
|
FROM first_listens f
|
|
JOIN last_listens p
|
|
ON f.user_id = p.user_id AND f.artist_id = p.artist_id
|
|
),
|
|
ranked AS (
|
|
SELECT user_id, artist_id, first_listen_of_year, last_listen, gap_days,
|
|
RANK() OVER (PARTITION BY user_id ORDER BY gap_days DESC) AS r
|
|
FROM comebacks
|
|
)
|
|
SELECT
|
|
c.user_id,
|
|
c.artist_id,
|
|
awn.name AS artist_name,
|
|
c.last_listen,
|
|
c.first_listen_of_year,
|
|
c.gap_days
|
|
FROM ranked c
|
|
JOIN artists_with_name awn ON awn.id = c.artist_id
|
|
WHERE r = 1
|
|
`
|
|
|
|
type GetArtistWithLongestGapInYearParams struct {
|
|
Year int32
|
|
FirstDayOfYear pgtype.Date
|
|
}
|
|
|
|
type GetArtistWithLongestGapInYearRow struct {
|
|
UserID int32
|
|
ArtistID int32
|
|
ArtistName string
|
|
LastListen interface{}
|
|
FirstListenOfYear interface{}
|
|
GapDays int32
|
|
}
|
|
|
|
func (q *Queries) GetArtistWithLongestGapInYear(ctx context.Context, arg GetArtistWithLongestGapInYearParams) (GetArtistWithLongestGapInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getArtistWithLongestGapInYear, arg.Year, arg.FirstDayOfYear)
|
|
var i GetArtistWithLongestGapInYearRow
|
|
err := row.Scan(
|
|
&i.UserID,
|
|
&i.ArtistID,
|
|
&i.ArtistName,
|
|
&i.LastListen,
|
|
&i.FirstListenOfYear,
|
|
&i.GapDays,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getArtistsWithOnlyOnePlayInYear = `-- name: GetArtistsWithOnlyOnePlayInYear :many
|
|
WITH first_artist_plays_in_year AS (
|
|
SELECT
|
|
l.user_id,
|
|
at.artist_id,
|
|
MIN(l.listened_at) AS first_listen
|
|
FROM listens l
|
|
JOIN artist_tracks at ON at.track_id = l.track_id
|
|
WHERE EXTRACT(YEAR FROM l.listened_at) = 2024
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM listens l2
|
|
JOIN artist_tracks at2 ON at2.track_id = l2.track_id
|
|
WHERE l2.user_id = l.user_id
|
|
AND at2.artist_id = at.artist_id
|
|
AND l2.listened_at < DATE '2024-01-01'
|
|
)
|
|
GROUP BY l.user_id, at.artist_id
|
|
)
|
|
SELECT
|
|
f.user_id,
|
|
f.artist_id,
|
|
f.first_listen, a.name,
|
|
COUNT(l.*) AS total_plays_in_year
|
|
FROM first_artist_plays_in_year f
|
|
JOIN listens l ON l.user_id = f.user_id
|
|
JOIN artist_tracks at ON at.track_id = l.track_id JOIN artists_with_name a ON at.artist_id = a.id
|
|
WHERE at.artist_id = f.artist_id
|
|
AND EXTRACT(YEAR FROM l.listened_at) = 2024
|
|
GROUP BY f.user_id, f.artist_id, f.first_listen, a.name HAVING COUNT(*) = 1
|
|
`
|
|
|
|
type GetArtistsWithOnlyOnePlayInYearRow struct {
|
|
UserID int32
|
|
ArtistID int32
|
|
FirstListen interface{}
|
|
Name string
|
|
TotalPlaysInYear int64
|
|
}
|
|
|
|
func (q *Queries) GetArtistsWithOnlyOnePlayInYear(ctx context.Context) ([]GetArtistsWithOnlyOnePlayInYearRow, error) {
|
|
rows, err := q.db.Query(ctx, getArtistsWithOnlyOnePlayInYear)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []GetArtistsWithOnlyOnePlayInYearRow
|
|
for rows.Next() {
|
|
var i GetArtistsWithOnlyOnePlayInYearRow
|
|
if err := rows.Scan(
|
|
&i.UserID,
|
|
&i.ArtistID,
|
|
&i.FirstListen,
|
|
&i.Name,
|
|
&i.TotalPlaysInYear,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getFirstListenInYear = `-- name: GetFirstListenInYear :one
|
|
SELECT
|
|
l.track_id, l.listened_at, l.client, l.user_id,
|
|
t.id, t.musicbrainz_id, t.duration, t.release_id, t.title,
|
|
get_artists_for_track(t.id) as artists
|
|
FROM listens l
|
|
LEFT JOIN tracks_with_title t ON l.track_id = t.id
|
|
WHERE EXTRACT(YEAR FROM l.listened_at) = 2025
|
|
ORDER BY l.listened_at ASC
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetFirstListenInYearRow struct {
|
|
TrackID int32
|
|
ListenedAt time.Time
|
|
Client *string
|
|
UserID int32
|
|
ID pgtype.Int4
|
|
MusicBrainzID *uuid.UUID
|
|
Duration pgtype.Int4
|
|
ReleaseID pgtype.Int4
|
|
Title pgtype.Text
|
|
Artists []byte
|
|
}
|
|
|
|
func (q *Queries) GetFirstListenInYear(ctx context.Context) (GetFirstListenInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getFirstListenInYear)
|
|
var i GetFirstListenInYearRow
|
|
err := row.Scan(
|
|
&i.TrackID,
|
|
&i.ListenedAt,
|
|
&i.Client,
|
|
&i.UserID,
|
|
&i.ID,
|
|
&i.MusicBrainzID,
|
|
&i.Duration,
|
|
&i.ReleaseID,
|
|
&i.Title,
|
|
&i.Artists,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getListenPercentageInTimeWindowInYear = `-- name: GetListenPercentageInTimeWindowInYear :one
|
|
WITH user_listens_in_year AS (
|
|
SELECT
|
|
listened_at
|
|
FROM listens
|
|
WHERE user_id = $1::int
|
|
AND EXTRACT(YEAR FROM listened_at) = $2::int
|
|
),
|
|
windowed AS (
|
|
SELECT
|
|
COUNT(*) AS in_window
|
|
FROM user_listens_in_year
|
|
WHERE EXTRACT(HOUR FROM listened_at) >= $3::int
|
|
AND EXTRACT(HOUR FROM listened_at) < $4::int
|
|
),
|
|
total AS (
|
|
SELECT COUNT(*) AS total_listens
|
|
FROM user_listens_in_year
|
|
)
|
|
SELECT
|
|
w.in_window,
|
|
t.total_listens,
|
|
ROUND((w.in_window::decimal / t.total_listens) * 100, 2) AS percent_of_total
|
|
FROM windowed w, total t
|
|
`
|
|
|
|
type GetListenPercentageInTimeWindowInYearParams struct {
|
|
UserID int32
|
|
Year int32
|
|
HourWindowStart int32
|
|
HourWindowEnd int32
|
|
}
|
|
|
|
type GetListenPercentageInTimeWindowInYearRow struct {
|
|
InWindow int64
|
|
TotalListens int64
|
|
PercentOfTotal pgtype.Numeric
|
|
}
|
|
|
|
func (q *Queries) GetListenPercentageInTimeWindowInYear(ctx context.Context, arg GetListenPercentageInTimeWindowInYearParams) (GetListenPercentageInTimeWindowInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getListenPercentageInTimeWindowInYear,
|
|
arg.UserID,
|
|
arg.Year,
|
|
arg.HourWindowStart,
|
|
arg.HourWindowEnd,
|
|
)
|
|
var i GetListenPercentageInTimeWindowInYearRow
|
|
err := row.Scan(&i.InWindow, &i.TotalListens, &i.PercentOfTotal)
|
|
return i, err
|
|
}
|
|
|
|
const getMostReplayedTrackInYear = `-- name: GetMostReplayedTrackInYear :one
|
|
WITH ordered_listens AS (
|
|
SELECT
|
|
user_id,
|
|
track_id,
|
|
listened_at,
|
|
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY listened_at) AS rn
|
|
FROM listens
|
|
WHERE EXTRACT(YEAR FROM listened_at) = $2::int
|
|
),
|
|
streaks AS (
|
|
SELECT
|
|
user_id,
|
|
track_id,
|
|
listened_at,
|
|
rn,
|
|
ROW_NUMBER() OVER (PARTITION BY user_id, track_id ORDER BY listened_at) AS track_rn
|
|
FROM ordered_listens
|
|
),
|
|
grouped_streaks AS (
|
|
SELECT
|
|
user_id,
|
|
track_id,
|
|
rn - track_rn AS group_id,
|
|
COUNT(*) AS streak_length
|
|
FROM streaks
|
|
GROUP BY user_id, track_id, rn - track_rn
|
|
),
|
|
ranked_streaks AS (
|
|
SELECT user_id, track_id, group_id, streak_length,
|
|
RANK() OVER (PARTITION BY user_id ORDER BY streak_length DESC) AS r
|
|
FROM grouped_streaks
|
|
)
|
|
SELECT
|
|
t.id, t.musicbrainz_id, t.duration, t.release_id, t.title,
|
|
get_artists_for_track(t.id) as artists,
|
|
streak_length
|
|
FROM ranked_streaks rs JOIN tracks_with_title t ON rs.track_id = t.id
|
|
WHERE user_id = $1::int AND r = 1
|
|
`
|
|
|
|
type GetMostReplayedTrackInYearParams struct {
|
|
UserID int32
|
|
Year int32
|
|
}
|
|
|
|
type GetMostReplayedTrackInYearRow struct {
|
|
ID int32
|
|
MusicBrainzID *uuid.UUID
|
|
Duration int32
|
|
ReleaseID int32
|
|
Title string
|
|
Artists []byte
|
|
StreakLength int64
|
|
}
|
|
|
|
func (q *Queries) GetMostReplayedTrackInYear(ctx context.Context, arg GetMostReplayedTrackInYearParams) (GetMostReplayedTrackInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getMostReplayedTrackInYear, arg.UserID, arg.Year)
|
|
var i GetMostReplayedTrackInYearRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.MusicBrainzID,
|
|
&i.Duration,
|
|
&i.ReleaseID,
|
|
&i.Title,
|
|
&i.Artists,
|
|
&i.StreakLength,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getPercentageOfTotalListensFromTopArtistsInYear = `-- name: GetPercentageOfTotalListensFromTopArtistsInYear :one
|
|
WITH user_artist_listens AS (
|
|
SELECT
|
|
at.artist_id,
|
|
COUNT(*) AS listen_count
|
|
FROM listens l
|
|
JOIN artist_tracks at ON at.track_id = l.track_id
|
|
WHERE l.user_id = $2::int
|
|
AND EXTRACT(YEAR FROM l.listened_at) = $3::int
|
|
GROUP BY at.artist_id
|
|
),
|
|
top_artists AS (
|
|
SELECT
|
|
artist_id,
|
|
listen_count
|
|
FROM user_artist_listens
|
|
ORDER BY listen_count DESC
|
|
LIMIT $1
|
|
),
|
|
totals AS (
|
|
SELECT
|
|
(SELECT SUM(listen_count) FROM top_artists) AS top_artist_total,
|
|
(SELECT SUM(listen_count) FROM user_artist_listens) AS overall_total
|
|
)
|
|
SELECT
|
|
top_artist_total,
|
|
overall_total,
|
|
ROUND((top_artist_total::decimal / overall_total) * 100, 2) AS percent_of_total
|
|
FROM totals
|
|
`
|
|
|
|
type GetPercentageOfTotalListensFromTopArtistsInYearParams struct {
|
|
Limit int32
|
|
UserID int32
|
|
Year int32
|
|
}
|
|
|
|
type GetPercentageOfTotalListensFromTopArtistsInYearRow struct {
|
|
TopArtistTotal int64
|
|
OverallTotal int64
|
|
PercentOfTotal pgtype.Numeric
|
|
}
|
|
|
|
func (q *Queries) GetPercentageOfTotalListensFromTopArtistsInYear(ctx context.Context, arg GetPercentageOfTotalListensFromTopArtistsInYearParams) (GetPercentageOfTotalListensFromTopArtistsInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getPercentageOfTotalListensFromTopArtistsInYear, arg.Limit, arg.UserID, arg.Year)
|
|
var i GetPercentageOfTotalListensFromTopArtistsInYearRow
|
|
err := row.Scan(&i.TopArtistTotal, &i.OverallTotal, &i.PercentOfTotal)
|
|
return i, err
|
|
}
|
|
|
|
const getPercentageOfTotalListensFromTopTracksInYear = `-- name: GetPercentageOfTotalListensFromTopTracksInYear :one
|
|
WITH user_listens AS (
|
|
SELECT
|
|
l.track_id,
|
|
COUNT(*) AS listen_count
|
|
FROM listens l
|
|
WHERE l.user_id = $2::int
|
|
AND EXTRACT(YEAR FROM l.listened_at) = $3::int
|
|
GROUP BY l.track_id
|
|
),
|
|
top_tracks AS (
|
|
SELECT
|
|
track_id,
|
|
listen_count
|
|
FROM user_listens
|
|
ORDER BY listen_count DESC
|
|
LIMIT $1
|
|
),
|
|
totals AS (
|
|
SELECT
|
|
(SELECT SUM(listen_count) FROM top_tracks) AS top_tracks_total,
|
|
(SELECT SUM(listen_count) FROM user_listens) AS overall_total
|
|
)
|
|
SELECT
|
|
top_tracks_total,
|
|
overall_total,
|
|
ROUND((top_tracks_total::decimal / overall_total) * 100, 2) AS percent_of_total
|
|
FROM totals
|
|
`
|
|
|
|
type GetPercentageOfTotalListensFromTopTracksInYearParams struct {
|
|
Limit int32
|
|
UserID int32
|
|
Year int32
|
|
}
|
|
|
|
type GetPercentageOfTotalListensFromTopTracksInYearRow struct {
|
|
TopTracksTotal int64
|
|
OverallTotal int64
|
|
PercentOfTotal pgtype.Numeric
|
|
}
|
|
|
|
func (q *Queries) GetPercentageOfTotalListensFromTopTracksInYear(ctx context.Context, arg GetPercentageOfTotalListensFromTopTracksInYearParams) (GetPercentageOfTotalListensFromTopTracksInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getPercentageOfTotalListensFromTopTracksInYear, arg.Limit, arg.UserID, arg.Year)
|
|
var i GetPercentageOfTotalListensFromTopTracksInYearRow
|
|
err := row.Scan(&i.TopTracksTotal, &i.OverallTotal, &i.PercentOfTotal)
|
|
return i, err
|
|
}
|
|
|
|
const getTracksPlayedAtLeastOncePerMonthInYear = `-- name: GetTracksPlayedAtLeastOncePerMonthInYear :many
|
|
WITH monthly_plays AS (
|
|
SELECT
|
|
l.track_id,
|
|
EXTRACT(MONTH FROM l.listened_at) AS month
|
|
FROM listens l
|
|
WHERE EXTRACT(YEAR FROM l.listened_at) = $1::int
|
|
GROUP BY l.track_id, EXTRACT(MONTH FROM l.listened_at)
|
|
),
|
|
monthly_counts AS (
|
|
SELECT
|
|
track_id,
|
|
COUNT(DISTINCT month) AS months_played
|
|
FROM monthly_plays
|
|
GROUP BY track_id
|
|
)
|
|
SELECT
|
|
t.id AS track_id,
|
|
t.title
|
|
FROM monthly_counts mc
|
|
JOIN tracks_with_title t ON t.id = mc.track_id
|
|
WHERE mc.months_played = 12
|
|
`
|
|
|
|
type GetTracksPlayedAtLeastOncePerMonthInYearRow struct {
|
|
TrackID int32
|
|
Title string
|
|
}
|
|
|
|
func (q *Queries) GetTracksPlayedAtLeastOncePerMonthInYear(ctx context.Context, userID int32) ([]GetTracksPlayedAtLeastOncePerMonthInYearRow, error) {
|
|
rows, err := q.db.Query(ctx, getTracksPlayedAtLeastOncePerMonthInYear, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []GetTracksPlayedAtLeastOncePerMonthInYearRow
|
|
for rows.Next() {
|
|
var i GetTracksPlayedAtLeastOncePerMonthInYearRow
|
|
if err := rows.Scan(&i.TrackID, &i.Title); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getWeekWithMostListensInYear = `-- name: GetWeekWithMostListensInYear :one
|
|
SELECT
|
|
DATE_TRUNC('week', listened_at + INTERVAL '1 day') - INTERVAL '1 day' AS week_start,
|
|
COUNT(*) AS listen_count
|
|
FROM listens
|
|
WHERE EXTRACT(YEAR FROM listened_at) = $1::int
|
|
AND user_id = $2::int
|
|
GROUP BY week_start
|
|
ORDER BY listen_count DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetWeekWithMostListensInYearParams struct {
|
|
Year int32
|
|
UserID int32
|
|
}
|
|
|
|
type GetWeekWithMostListensInYearRow struct {
|
|
WeekStart int32
|
|
ListenCount int64
|
|
}
|
|
|
|
func (q *Queries) GetWeekWithMostListensInYear(ctx context.Context, arg GetWeekWithMostListensInYearParams) (GetWeekWithMostListensInYearRow, error) {
|
|
row := q.db.QueryRow(ctx, getWeekWithMostListensInYear, arg.Year, arg.UserID)
|
|
var i GetWeekWithMostListensInYearRow
|
|
err := row.Scan(&i.WeekStart, &i.ListenCount)
|
|
return i, err
|
|
}
|
|
|
|
const tracksOnlyPlayedOnceInYear = `-- name: TracksOnlyPlayedOnceInYear :many
|
|
SELECT
|
|
t.id AS track_id,
|
|
t.title,
|
|
get_artists_for_track(t.id) as artists,
|
|
COUNT(l.*) AS listen_count
|
|
FROM listens l
|
|
JOIN tracks_with_title t ON t.id = l.track_id
|
|
WHERE EXTRACT(YEAR FROM l.listened_at) = $2::int AND l.user_id = $3::int
|
|
GROUP BY t.id, t.title
|
|
HAVING COUNT(*) = 1
|
|
LIMIT $1
|
|
`
|
|
|
|
type TracksOnlyPlayedOnceInYearParams struct {
|
|
Limit int32
|
|
Year int32
|
|
UserID int32
|
|
}
|
|
|
|
type TracksOnlyPlayedOnceInYearRow struct {
|
|
TrackID int32
|
|
Title string
|
|
Artists []byte
|
|
ListenCount int64
|
|
}
|
|
|
|
func (q *Queries) TracksOnlyPlayedOnceInYear(ctx context.Context, arg TracksOnlyPlayedOnceInYearParams) ([]TracksOnlyPlayedOnceInYearRow, error) {
|
|
rows, err := q.db.Query(ctx, tracksOnlyPlayedOnceInYear, arg.Limit, arg.Year, arg.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []TracksOnlyPlayedOnceInYearRow
|
|
for rows.Next() {
|
|
var i TracksOnlyPlayedOnceInYearRow
|
|
if err := rows.Scan(
|
|
&i.TrackID,
|
|
&i.Title,
|
|
&i.Artists,
|
|
&i.ListenCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|