feat: Remove global db.Dbpool with dependency injection (Phase 0)

- Add Database struct in internal/db/database.go with Pool, Ctx, and RunMigrations()
- Update server.go to use Database struct with NewServerInstance()
- Add backend.go with InitBackend(), BackendRepo(), BackendCtx(), BackendPool()
- Update music.go and sync.go to use BackendRepo() and BackendCtx() instead of db.Dbpool/db.Ctx
- Update token_handler.go to accept pool parameter
- Update routes.go to use s.db.Pool for middleware
- Update cmd/main.go to use NewServerInstance() and HTTPServer()
- Update test_helpers.go to initialize backend with test database
- Update test files to use backend.BackendPool() and backend.BackendCtx()

Benefits:
- Easier to mock database for unit tests
- Follows Go best practices (dependency injection)
- Better architecture with explicit dependencies
- RunMigrations() replaces old Migrate_db() function

Note: Global db.Dbpool and db.Ctx still exist in dbHelper.go for backward compatibility
with test_helpers.go, but production code no longer uses them.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-06-01 18:50:05 +02:00
parent 6cc014ffa3
commit 24a9111333
11 changed files with 320 additions and 100 deletions
+40
View File
@@ -0,0 +1,40 @@
package backend
import (
"context"
"music-server/internal/db/repository"
"github.com/jackc/pgx/v5/pgxpool"
)
// Global variables - these are initialized by InitBackend
var (
backendPool *pgxpool.Pool
repo *repository.Queries
backendCtx context.Context = context.Background()
)
// InitBackend initializes the backend package with the database pool.
// This should be called once at application startup.
func InitBackend(pool *pgxpool.Pool) {
backendPool = pool
repo = repository.New(pool)
backendCtx = context.Background()
}
// BackendCtx returns the context used by backend operations.
// This is exposed for use by the backend functions.
func BackendCtx() context.Context {
return backendCtx
}
// BackendRepo returns the repository queries instance.
// This is exposed for use by the backend functions.
func BackendRepo() *repository.Queries {
return repo
}
// BackendPool returns the underlying database pool.
// This is exposed for test utilities that need direct pool access.
func BackendPool() *pgxpool.Pool {
return backendPool
}
+17 -16
View File
@@ -2,7 +2,6 @@ package backend
import (
"math/rand"
"music-server/internal/db"
"music-server/internal/db/repository"
"music-server/internal/logging"
"os"
@@ -28,18 +27,20 @@ var gamesNew []repository.Game
var songQueNew []repository.Song
var lastFetchedNew repository.Song
var repo *repository.Queries
func initRepo() {
if repo == nil {
repo = repository.New(db.Dbpool)
// This function is kept for backward compatibility
// but now uses the backend package's initialized repo
// If not initialized, this will panic intentionally
if BackendRepo() == nil {
panic("backend not initialized - call backend.InitBackend() first")
}
}
func getAllGames() []repository.Game {
if len(gamesNew) == 0 {
initRepo()
gamesNew, _ = repo.FindAllGames(db.Ctx)
gamesNew, _ = BackendRepo().FindAllGames(BackendCtx())
}
return gamesNew
@@ -58,7 +59,7 @@ func Reset() {
songQueNew = nil
currentSong = -1
initRepo()
gamesNew, _ = repo.FindAllGames(db.Ctx)
gamesNew, _ = BackendRepo().FindAllGames(BackendCtx())
}
func AddLatestToQue() {
@@ -76,8 +77,8 @@ func AddLatestPlayed() {
currentSongData := songQueNew[currentSong]
initRepo()
repo.AddGamePlayed(db.Ctx, currentSongData.GameID)
repo.AddSongPlayed(db.Ctx, repository.AddSongPlayedParams{GameID: currentSongData.GameID, SongName: currentSongData.SongName})
BackendRepo().AddGamePlayed(BackendCtx(), currentSongData.GameID)
BackendRepo().AddSongPlayed(BackendCtx(), repository.AddSongPlayedParams{GameID: currentSongData.GameID, SongName: currentSongData.SongName})
}
func SetPlayed(songNumber int) {
@@ -86,8 +87,8 @@ func SetPlayed(songNumber int) {
}
songData := songQueNew[songNumber]
initRepo()
repo.AddGamePlayed(db.Ctx, songData.GameID)
repo.AddSongPlayed(db.Ctx, repository.AddSongPlayedParams{GameID: songData.GameID, SongName: songData.SongName})
BackendRepo().AddGamePlayed(BackendCtx(), songData.GameID)
BackendRepo().AddSongPlayed(BackendCtx(), repository.AddSongPlayedParams{GameID: songData.GameID, SongName: songData.SongName})
}
func GetRandomSong() string {
@@ -130,7 +131,7 @@ func GetRandomSongClassic() string {
var listOfAllSongs []repository.Song
for _, game := range gamesNew {
songList, _ := repo.FindSongsFromGame(db.Ctx, game.ID)
songList, _ := BackendRepo().FindSongsFromGame(BackendCtx(), game.ID)
listOfAllSongs = append(listOfAllSongs, songList...)
}
@@ -138,10 +139,10 @@ func GetRandomSongClassic() string {
var song repository.Song
for !songFound {
song = listOfAllSongs[rand.Intn(len(listOfAllSongs))]
gameData, err := repo.GetGameById(db.Ctx, song.GameID)
gameData, err := BackendRepo().GetGameById(BackendCtx(), song.GameID)
if err != nil {
repo.RemoveBrokenSong(db.Ctx, song.Path)
BackendRepo().RemoveBrokenSong(BackendCtx(), song.Path)
logging.GetLogger().Warn("Song not found, removed from database",
zap.String("song", song.SongName),
zap.String("game", gameData.GameName),
@@ -153,7 +154,7 @@ func GetRandomSongClassic() string {
openFile, err := os.Open(song.Path)
if err != nil || (song.FileName != nil && gameData.Path+*song.FileName != song.Path) {
//File not found
repo.RemoveBrokenSong(db.Ctx, song.Path)
BackendRepo().RemoveBrokenSong(BackendCtx(), song.Path)
logging.GetLogger().Warn("Song not found, removed from database",
zap.String("song", song.SongName),
zap.String("game", gameData.GameName),
@@ -270,7 +271,7 @@ func getSongFromList(games []repository.Game) repository.Song {
var song repository.Song
for !songFound {
game := getRandomGame(games)
songs, _ := repo.FindSongsFromGame(db.Ctx, game.ID)
songs, _ := BackendRepo().FindSongsFromGame(BackendCtx(), game.ID)
if len(songs) == 0 {
continue
}
@@ -281,7 +282,7 @@ func getSongFromList(games []repository.Game) repository.Song {
openFile, err := os.Open(song.Path)
if err != nil || (song.FileName != nil && game.Path+*song.FileName != song.Path) || (song.FileName != nil && strings.HasSuffix(*song.FileName, ".wav")) {
//File not found
repo.RemoveBrokenSong(db.Ctx, song.Path)
BackendRepo().RemoveBrokenSong(BackendCtx(), song.Path)
logging.GetLogger().Warn("Song not found, removed from database",
zap.String("song", song.SongName),
zap.String("game", game.GameName),
+23 -24
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"io"
"io/fs"
"music-server/internal/db"
"music-server/internal/db/repository"
"music-server/internal/logging"
"os"
@@ -80,8 +79,8 @@ func (gs GameStatus) String() string {
}
func ResetDB() {
repo.ClearSongs(db.Ctx)
repo.ClearGames(db.Ctx)
repo.ClearSongs(BackendCtx())
repo.ClearGames(BackendCtx())
}
func SyncProgress() ProgressResponse {
@@ -206,13 +205,13 @@ func syncGamesNew(full bool) {
catchedErrors = nil
brokenSongs = nil
gamesBeforeSync, err = repo.FindAllGames(db.Ctx)
gamesBeforeSync, err = repo.FindAllGames(BackendCtx())
handleError("FindAllGames Before", err, "")
logging.GetLogger().Info("Starting sync", zap.Int("games_before", len(gamesBeforeSync)))
allGames, err = repo.GetAllGamesIncludingDeleted(db.Ctx)
allGames, err = repo.GetAllGamesIncludingDeleted(BackendCtx())
handleError("GetAllGamesIncludingDeleted", err, "")
err = repo.SetGameDeletionDate(db.Ctx)
err = repo.SetGameDeletionDate(BackendCtx())
handleError("SetGameDeletionDate", err, "")
directories, err := os.ReadDir(musicPath)
@@ -237,7 +236,7 @@ func syncGamesNew(full bool) {
syncWg.Wait()
checkBrokenSongsNew()
gamesAfterSync, err = repo.FindAllGames(db.Ctx)
gamesAfterSync, err = repo.FindAllGames(BackendCtx())
handleError("FindAllGames After", err, "")
finished := time.Now()
@@ -249,7 +248,7 @@ func syncGamesNew(full bool) {
}
func checkBrokenSongsNew() {
allSongs, err := repo.FetchAllSongs(db.Ctx)
allSongs, err := repo.FetchAllSongs(BackendCtx())
handleError("FetchAllSongs", err, "")
var brokenWg sync.WaitGroup
poolBroken, _ := ants.NewPool(200, ants.WithPreAlloc(true))
@@ -263,7 +262,7 @@ func checkBrokenSongsNew() {
})
}
brokenWg.Wait()
err = repo.RemoveBrokenSongs(db.Ctx, brokenSongs)
err = repo.RemoveBrokenSongs(BackendCtx(), brokenSongs)
handleError("RemoveBrokenSongs", err, "")
}
@@ -336,7 +335,7 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
break
}
}
err = repo.InsertGameWithExistingId(db.Ctx, repository.InsertGameWithExistingIdParams{ID: id, GameName: file.Name(), Path: gameDir, Hash: dirHash})
err = repo.InsertGameWithExistingId(BackendCtx(), repository.InsertGameWithExistingIdParams{ID: id, GameName: file.Name(), Path: gameDir, Hash: dirHash})
handleError("InsertGameWithExistingId", err, "")
if err != nil {
logging.GetLogger().Debug("Game already exists, removing old ID file",
@@ -370,7 +369,7 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
zap.String("game", file.Name()),
zap.String("hash", dirHash),
zap.String("status", status.String()))
err = repo.UpdateGameHash(db.Ctx, repository.UpdateGameHashParams{Hash: dirHash, ID: id})
err = repo.UpdateGameHash(BackendCtx(), repository.UpdateGameHashParams{Hash: dirHash, ID: id})
handleError("UpdateGameHash", err, "")
gamesChangedContent = append(gamesChangedContent, file.Name())
newCheckSongs(entries, gameDir, id)
@@ -381,7 +380,7 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
zap.String("newName", file.Name()),
zap.String("hash", dirHash),
zap.String("status", status.String()))
err = repo.UpdateGameName(db.Ctx, repository.UpdateGameNameParams{Name: file.Name(), Path: gameDir, ID: id})
err = repo.UpdateGameName(BackendCtx(), repository.UpdateGameNameParams{Name: file.Name(), Path: gameDir, ID: id})
handleError("UpdateGameName", err, "")
newCheckSongs(entries, gameDir, id)
if gamesChangedTitle == nil {
@@ -416,7 +415,7 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
zap.String("game", file.Name()),
zap.String("hash", dirHash),
zap.String("status", status.String()))
err = repo.RemoveDeletionDate(db.Ctx, id)
err = repo.RemoveDeletionDate(BackendCtx(), id)
handleError("RemoveDeletionDate", err, "")
}
foldersSynced++
@@ -428,13 +427,13 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
func insertGameNew(name string, path string, hash string) int32 {
var duplicateError = errors.New("ERROR: duplicate key value violates unique")
id, err := repo.InsertGame(db.Ctx, repository.InsertGameParams{GameName: name, Path: path, Hash: hash})
id, err := repo.InsertGame(BackendCtx(), repository.InsertGameParams{GameName: name, Path: path, Hash: hash})
handleError("InsertGame", err, "")
if err != nil {
logging.GetLogger().Warn("ID collision detected, resetting sequence")
if strings.HasPrefix(err.Error(), duplicateError.Error()) {
logging.GetLogger().Debug("Resetting game ID sequence")
_, err = repo.ResetGameIdSeq(db.Ctx)
_, err = repo.ResetGameIdSeq(BackendCtx())
handleError("ResetGameIdSeq", err, "")
id = insertGameNew(name, path, hash)
}
@@ -478,7 +477,7 @@ func newCheckSong(entry os.DirEntry, gameDir string, id int32) bool {
fileName := entry.Name()
songName, _ := strings.CutSuffix(fileName, ".mp3")
song, err := repo.GetSongWithHash(db.Ctx, songHash)
song, err := repo.GetSongWithHash(BackendCtx(), songHash)
handleError("GetSongWithHash", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
if err == nil {
if song.SongName == songName && song.Path == path {
@@ -491,31 +490,31 @@ func newCheckSong(entry os.DirEntry, gameDir string, id int32) bool {
zap.String("song_name", songName),
zap.String("song_hash", songHash))
count, err := repo.CheckSongWithHash(db.Ctx, songHash)
count, err := repo.CheckSongWithHash(BackendCtx(), songHash)
handleError("CheckSongWithHash", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
if err != nil {
count2, err := repo.CheckSong(db.Ctx, path)
count2, err := repo.CheckSong(BackendCtx(), path)
handleError("CheckSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
if count2 > 0 {
err = repo.AddHashToSong(db.Ctx, repository.AddHashToSongParams{Hash: songHash, Path: path})
err = repo.AddHashToSong(BackendCtx(), repository.AddHashToSongParams{Hash: songHash, Path: path})
handleError("AddHashToSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
count, err = repo.CheckSongWithHash(db.Ctx, songHash)
count, err = repo.CheckSongWithHash(BackendCtx(), songHash)
handleError("CheckSongWithHash 2", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
}
}
//count, _ := repo.CheckSong(ctx, path)
if count > 0 {
err = repo.UpdateSong(db.Ctx, repository.UpdateSongParams{SongName: songName, FileName: &fileName, Path: path, Hash: songHash})
err = repo.UpdateSong(BackendCtx(), repository.UpdateSongParams{SongName: songName, FileName: &fileName, Path: path, Hash: songHash})
handleError("UpdateSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
} else {
count2, err := repo.CheckSong(db.Ctx, path)
count2, err := repo.CheckSong(BackendCtx(), path)
handleError("CheckSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
if count2 > 0 {
err = repo.AddHashToSong(db.Ctx, repository.AddHashToSongParams{Hash: songHash, Path: path})
err = repo.AddHashToSong(BackendCtx(), repository.AddHashToSongParams{Hash: songHash, Path: path})
handleError("AddHashToSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
} else {
err = repo.AddSong(db.Ctx, repository.AddSongParams{GameID: id, SongName: songName, Path: path, FileName: &fileName, Hash: songHash})
err = repo.AddSong(BackendCtx(), repository.AddSongParams{GameID: id, SongName: songName, Path: path, FileName: &fileName, Hash: songHash})
handleError("AddSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
}