Pre-release version v0.0.13 (#52)

* feat: search/merge items by id

* feat: update track duration using musicbrainz

* chore: changelog

* fix: make username updates case insensitive

* feat: add minutes listened to ui and fix image drop

* chore: changelog

* fix: embed db migrations (#37)

* feat: Add support for ARM in publish workflow (#51)

* chore: changelog

* docs: search by id and custom theme support

---------

Co-authored-by: potatoattack <lvl70nub@gmail.com>
Co-authored-by: Benjamin Jonard <benjaminjonard@users.noreply.github.com>
This commit is contained in:
Gabe Farrell 2025-07-26 15:57:46 -04:00 committed by GitHub
parent 5537b6fb89
commit 5419178012
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1252 additions and 100 deletions

View file

@ -2,6 +2,7 @@ package psql
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
@ -19,40 +20,71 @@ import (
func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Album, error) {
l := logger.FromContext(ctx)
var row repository.ReleasesWithTitle
var err error
var ret = new(models.Album)
if opts.ID != 0 {
l.Debug().Msgf("Fetching album from DB with id %d", opts.ID)
row, err = d.q.GetRelease(ctx, opts.ID)
row, err := d.q.GetRelease(ctx, opts.ID)
if err != nil {
return nil, fmt.Errorf("GetAlbum: %w", err)
}
ret.ID = row.ID
ret.MbzID = row.MusicBrainzID
ret.Title = row.Title
ret.Image = row.Image
ret.VariousArtists = row.VariousArtists
err = json.Unmarshal(row.Artists, &ret.Artists)
if err != nil {
return nil, fmt.Errorf("GetAlbum: json.Unmarshal: %w", err)
}
} else if opts.MusicBrainzID != uuid.Nil {
l.Debug().Msgf("Fetching album from DB with MusicBrainz Release ID %s", opts.MusicBrainzID)
row, err = d.q.GetReleaseByMbzID(ctx, &opts.MusicBrainzID)
row, err := d.q.GetReleaseByMbzID(ctx, &opts.MusicBrainzID)
if err != nil {
return nil, fmt.Errorf("GetAlbum: %w", err)
}
ret.ID = row.ID
ret.MbzID = row.MusicBrainzID
ret.Title = row.Title
ret.Image = row.Image
ret.VariousArtists = row.VariousArtists
} else if opts.ArtistID != 0 && opts.Title != "" {
l.Debug().Msgf("Fetching album from DB with artist_id %d and title %s", opts.ArtistID, opts.Title)
row, err = d.q.GetReleaseByArtistAndTitle(ctx, repository.GetReleaseByArtistAndTitleParams{
row, err := d.q.GetReleaseByArtistAndTitle(ctx, repository.GetReleaseByArtistAndTitleParams{
ArtistID: opts.ArtistID,
Title: opts.Title,
})
if err != nil {
return nil, fmt.Errorf("GetAlbum: %w", err)
}
ret.ID = row.ID
ret.MbzID = row.MusicBrainzID
ret.Title = row.Title
ret.Image = row.Image
ret.VariousArtists = row.VariousArtists
} else if opts.ArtistID != 0 && len(opts.Titles) > 0 {
l.Debug().Msgf("Fetching release group from DB with artist_id %d and titles %v", opts.ArtistID, opts.Titles)
row, err = d.q.GetReleaseByArtistAndTitles(ctx, repository.GetReleaseByArtistAndTitlesParams{
row, err := d.q.GetReleaseByArtistAndTitles(ctx, repository.GetReleaseByArtistAndTitlesParams{
ArtistID: opts.ArtistID,
Column1: opts.Titles,
})
if err != nil {
return nil, fmt.Errorf("GetAlbum: %w", err)
}
ret.ID = row.ID
ret.MbzID = row.MusicBrainzID
ret.Title = row.Title
ret.Image = row.Image
ret.VariousArtists = row.VariousArtists
} else {
return nil, errors.New("GetAlbum: insufficient information to get album")
}
if err != nil {
return nil, fmt.Errorf("GetAlbum: %w", err)
}
count, err := d.q.CountListensFromRelease(ctx, repository.CountListensFromReleaseParams{
ListenedAt: time.Unix(0, 0),
ListenedAt_2: time.Now(),
ReleaseID: row.ID,
ReleaseID: ret.ID,
})
if err != nil {
return nil, fmt.Errorf("GetAlbum: CountListensFromRelease: %w", err)
@ -60,21 +92,16 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
AlbumID: row.ID,
AlbumID: ret.ID,
})
if err != nil {
return nil, fmt.Errorf("GetAlbum: CountTimeListenedToItem: %w", err)
}
return &models.Album{
ID: row.ID,
MbzID: row.MusicBrainzID,
Title: row.Title,
Image: row.Image,
VariousArtists: row.VariousArtists,
ListenCount: count,
TimeListened: seconds,
}, nil
ret.ListenCount = count
ret.TimeListened = seconds
return ret, nil
}
func (d *Psql) SaveAlbum(ctx context.Context, opts db.SaveAlbumOpts) (*models.Album, error) {

View file

@ -5,10 +5,9 @@ import (
"context"
"database/sql"
"fmt"
"path/filepath"
"runtime"
"time"
"github.com/gabehf/koito/db/migrations"
"github.com/gabehf/koito/internal/cfg"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/repository"
@ -54,13 +53,9 @@ func New() (*Psql, error) {
return nil, fmt.Errorf("psql.New: failed to open db for migrations: %w", err)
}
_, filename, _, ok := runtime.Caller(0)
if !ok {
return nil, fmt.Errorf("psql.New: unable to get caller info")
}
migrationsPath := filepath.Join(filepath.Dir(filename), "..", "..", "..", "db", "migrations")
goose.SetBaseFS(migrations.Files)
if err := goose.Up(sqlDB, migrationsPath); err != nil {
if err := goose.Up(sqlDB, "."); err != nil {
return nil, fmt.Errorf("psql.New: goose failed: %w", err)
}
_ = sqlDB.Close()

View file

@ -2,6 +2,7 @@ package psql
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
@ -34,6 +35,10 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
Image: t.Image,
Duration: t.Duration,
}
err = json.Unmarshal(t.Artists, &track.Artists)
if err != nil {
return nil, fmt.Errorf("GetTrack: json.Unmarshal: %w", err)
}
} else if opts.MusicBrainzID != uuid.Nil {
l.Debug().Msgf("Fetching track from DB with MusicBrainz ID %s", opts.MusicBrainzID)
t, err := d.q.GetTrackByMbzID(ctx, &opts.MusicBrainzID)

View file

@ -120,7 +120,7 @@ func (d *Psql) UpdateUser(ctx context.Context, opts db.UpdateUserOpts) error {
}
err = qtx.UpdateUserUsername(ctx, repository.UpdateUserUsernameParams{
ID: opts.ID,
Username: opts.Username,
Username: strings.ToLower(opts.Username),
})
if err != nil {
return fmt.Errorf("UpdateUser: UpdateUserUsername: %w", err)