diff --git a/cmd/main.go b/cmd/main.go index e5ea5e4..e853666 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,7 +8,6 @@ import ( //go:embed swagger var swagger embed.FS - //go:embed search var search embed.FS diff --git a/db/migrations/000001_create_tables.down.sql b/db/migrations/000001_create_tables.down.sql new file mode 100644 index 0000000..623d6b7 --- /dev/null +++ b/db/migrations/000001_create_tables.down.sql @@ -0,0 +1,4 @@ +DROP TABLE game; +DROP TABLE song; +DROP TABLE song_list; +DROP TABLE vgmq; diff --git a/db/migrations/000001_create_tables.up.sql b/db/migrations/000001_create_tables.up.sql new file mode 100644 index 0000000..d54a1a9 --- /dev/null +++ b/db/migrations/000001_create_tables.up.sql @@ -0,0 +1,43 @@ +CREATE TABLE game ( + id serial4 NOT NULL, + game_name varchar NOT NULL, + added timestamp NOT NULL, + deleted timestamp NULL, + last_changed timestamp NULL, + "path" varchar NOT NULL, + times_played int4 DEFAULT 0 NULL, + last_played timestamp NULL, + number_of_songs int4 NULL, + hash varchar NULL, + CONSTRAINT game_pkey PRIMARY KEY (id) +); + +CREATE TABLE song ( + game_id int4 NOT NULL, + song_name varchar NOT NULL, + "path" varchar NOT NULL, + times_played int4 DEFAULT 0 NULL, + hash varchar NULL, + file_name varchar NULL, + CONSTRAINT song_pkey PRIMARY KEY (game_id, path) +); + +CREATE TABLE vgmq ( + song_no int4 NOT NULL, + "path" varchar(50) NULL, + clue varchar(200) NULL, + answered bool DEFAULT false NOT NULL, + answer varchar(50) NULL, + CONSTRAINT vgmq_pk PRIMARY KEY (song_no) +); +CREATE UNIQUE INDEX vgmq_song_no_uindex ON vgmq USING btree (song_no); + +CREATE TABLE song_list ( + match_date date NOT NULL, + match_id int4 NOT NULL, + song_no int4 NOT NULL, + game_name varchar(50) NULL, + song_name varchar(50) NULL, + CONSTRAINT song_list_pkey PRIMARY KEY (match_date, match_id, song_no) +); +CREATE INDEX song_list_game_name_idx ON song_list USING btree (game_name); diff --git a/db/migrations/000002_adjust_fields_for_sqlc.down.sql b/db/migrations/000002_adjust_fields_for_sqlc.down.sql new file mode 100644 index 0000000..fa37e7a --- /dev/null +++ b/db/migrations/000002_adjust_fields_for_sqlc.down.sql @@ -0,0 +1,3 @@ +Alter table game +alter column number_of_songs set null, +alter column hash set null; diff --git a/db/migrations/000002_adjust_fields_for_sqlc.up.sql b/db/migrations/000002_adjust_fields_for_sqlc.up.sql new file mode 100644 index 0000000..d9f6617 --- /dev/null +++ b/db/migrations/000002_adjust_fields_for_sqlc.up.sql @@ -0,0 +1,19 @@ +BEGIN; +UPDATE game +SET number_of_songs = 0 +WHERE number_of_songs IS NULL; +UPDATE game +SET hash = '' +WHERE hash IS NULL; +UPDATE song +SET hash = '' +WHERE hash IS NULL; +COMMIT; +BEGIN; +Alter table game +alter column number_of_songs set not null, +alter column number_of_songs set default 0, +ALTER COLUMN hash SET NOT NULL; +ALTER TABLE song +ALTER COLUMN hash SET NOT NULL; +COMMIT; diff --git a/db/queries/game.sql b/db/queries/game.sql new file mode 100644 index 0000000..ba02059 --- /dev/null +++ b/db/queries/game.sql @@ -0,0 +1,49 @@ +-- name: ResetGameIdSeq :one +SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1); + +-- name: GetGameNameById :one +SELECT game_name FROM game WHERE id = $1; + +-- name: GetGameById :one +SELECT * +FROM game +WHERE id = $1 +AND deleted IS NULL; + +-- name: SetGameDeletionDate :exec +UPDATE game SET deleted=now() WHERE deleted IS NULL; + +-- name: ClearGames :exec +DELETE FROM game; + +-- name: UpdateGameName :exec +UPDATE game SET game_name=sqlc.arg(name), path=sqlc.arg(path), last_changed=now() WHERE id=sqlc.arg(id); + +-- name: UpdateGameHash :exec +UPDATE game SET hash=sqlc.arg(hash), last_changed=now() WHERE id=sqlc.arg(id); + +-- name: RemoveDeletionDate :exec +UPDATE game SET deleted=NULL WHERE id=$1; + +-- name: GetIdByGameName :one +SELECT id FROM game WHERE game_name = $1; + +-- name: InsertGame :one +INSERT INTO game (game_name, path, hash, added) VALUES ($1, $2, $3, now()) returning id; + +-- name: InsertGameWithExistingId :exec +INSERT INTO game (id, game_name, path, hash, added) VALUES ($1, $2, $3, $4, now()); + +-- name: FindAllGames :many +SELECT * +FROM game +WHERE deleted IS NULL +ORDER BY game_name; + +-- name: GetAllGamesIncludingDeleted :many +SELECT * +FROM game +ORDER BY game_name; + +-- name: AddGamePlayed :exec +UPDATE game SET times_played = times_played + 1, last_played = now() WHERE id = $1; diff --git a/db/queries/song.sql b/db/queries/song.sql new file mode 100644 index 0000000..788470f --- /dev/null +++ b/db/queries/song.sql @@ -0,0 +1,41 @@ +-- name: ClearSongs :exec +DELETE FROM song; + +-- name: ClearSongsByGameId :exec +DELETE FROM song WHERE game_id = $1; + +-- name: AddSong :exec +INSERT INTO song(game_id, song_name, path, file_name, hash) VALUES ($1, $2, $3, $4, $5); + +-- name: CheckSong :one +SELECT COUNT(*) FROM song WHERE path = $1; + +-- name: CheckSongWithHash :one +SELECT COUNT(*) FROM song WHERE hash = $1; + +-- name: GetSongWithHash :one +SELECT * FROM song WHERE hash = $1; + +-- name: UpdateSong :exec +UPDATE song SET song_name=$1, file_name=$2, path=$3 where hash=$4; + +-- name: AddHashToSong :exec +UPDATE song SET hash=$1 where path=$2; + +-- name: FindSongsFromGame :many +SELECT * +FROM song +WHERE game_id = $1; + +-- name: AddSongPlayed :exec +UPDATE song SET times_played = times_played + 1 +WHERE game_id = $1 AND song_name = $2; + +-- name: FetchAllSongs :many +SELECT * FROM song; + +-- name: RemoveBrokenSong :exec +DELETE FROM song WHERE path = $1; + +-- name: RemoveBrokenSongs :exec +DELETE FROM song where path = any (sqlc.slice('paths')); diff --git a/db/queries/song_list.sql b/db/queries/song_list.sql new file mode 100644 index 0000000..1ddc294 --- /dev/null +++ b/db/queries/song_list.sql @@ -0,0 +1,9 @@ +-- name: InsertSongInList :exec +INSERT INTO song_list (match_date, match_id, song_no, game_name, song_name) +VALUES ($1, $2, $3, $4, $5); + +-- name: GetSongList :many +SELECT * +FROM song_list +WHERE match_date = $1 +ORDER BY song_no DESC; diff --git a/db/queries/vgmq.sql b/db/queries/vgmq.sql new file mode 100644 index 0000000..e69de29 diff --git a/db/repository/db.go b/db/repository/db.go new file mode 100644 index 0000000..a0b40e7 --- /dev/null +++ b/db/repository/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package repository + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/db/repository/game.sql.go b/db/repository/game.sql.go new file mode 100644 index 0000000..c55f9b8 --- /dev/null +++ b/db/repository/game.sql.go @@ -0,0 +1,246 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: game.sql + +package repository + +import ( + "context" +) + +const addGamePlayed = `-- name: AddGamePlayed :exec +UPDATE game SET times_played = times_played + 1, last_played = now() WHERE id = $1 +` + +func (q *Queries) AddGamePlayed(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, addGamePlayed, id) + return err +} + +const clearGames = `-- name: ClearGames :exec +DELETE FROM game +` + +func (q *Queries) ClearGames(ctx context.Context) error { + _, err := q.db.Exec(ctx, clearGames) + return err +} + +const findAllGames = `-- name: FindAllGames :many +SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash +FROM game +WHERE deleted IS NULL +ORDER BY game_name +` + +func (q *Queries) FindAllGames(ctx context.Context) ([]Game, error) { + rows, err := q.db.Query(ctx, findAllGames) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Game + for rows.Next() { + var i Game + if err := rows.Scan( + &i.ID, + &i.GameName, + &i.Added, + &i.Deleted, + &i.LastChanged, + &i.Path, + &i.TimesPlayed, + &i.LastPlayed, + &i.NumberOfSongs, + &i.Hash, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAllGamesIncludingDeleted = `-- name: GetAllGamesIncludingDeleted :many +SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash +FROM game +ORDER BY game_name +` + +func (q *Queries) GetAllGamesIncludingDeleted(ctx context.Context) ([]Game, error) { + rows, err := q.db.Query(ctx, getAllGamesIncludingDeleted) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Game + for rows.Next() { + var i Game + if err := rows.Scan( + &i.ID, + &i.GameName, + &i.Added, + &i.Deleted, + &i.LastChanged, + &i.Path, + &i.TimesPlayed, + &i.LastPlayed, + &i.NumberOfSongs, + &i.Hash, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getGameById = `-- name: GetGameById :one +SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash +FROM game +WHERE id = $1 +AND deleted IS NULL +` + +func (q *Queries) GetGameById(ctx context.Context, id int32) (Game, error) { + row := q.db.QueryRow(ctx, getGameById, id) + var i Game + err := row.Scan( + &i.ID, + &i.GameName, + &i.Added, + &i.Deleted, + &i.LastChanged, + &i.Path, + &i.TimesPlayed, + &i.LastPlayed, + &i.NumberOfSongs, + &i.Hash, + ) + return i, err +} + +const getGameNameById = `-- name: GetGameNameById :one +SELECT game_name FROM game WHERE id = $1 +` + +func (q *Queries) GetGameNameById(ctx context.Context, id int32) (string, error) { + row := q.db.QueryRow(ctx, getGameNameById, id) + var game_name string + err := row.Scan(&game_name) + return game_name, err +} + +const getIdByGameName = `-- name: GetIdByGameName :one +SELECT id FROM game WHERE game_name = $1 +` + +func (q *Queries) GetIdByGameName(ctx context.Context, gameName string) (int32, error) { + row := q.db.QueryRow(ctx, getIdByGameName, gameName) + var id int32 + err := row.Scan(&id) + return id, err +} + +const insertGame = `-- name: InsertGame :one +INSERT INTO game (game_name, path, hash, added) VALUES ($1, $2, $3, now()) returning id +` + +type InsertGameParams struct { + GameName string `json:"game_name"` + Path string `json:"path"` + Hash string `json:"hash"` +} + +func (q *Queries) InsertGame(ctx context.Context, arg InsertGameParams) (int32, error) { + row := q.db.QueryRow(ctx, insertGame, arg.GameName, arg.Path, arg.Hash) + var id int32 + err := row.Scan(&id) + return id, err +} + +const insertGameWithExistingId = `-- name: InsertGameWithExistingId :exec +INSERT INTO game (id, game_name, path, hash, added) VALUES ($1, $2, $3, $4, now()) +` + +type InsertGameWithExistingIdParams struct { + ID int32 `json:"id"` + GameName string `json:"game_name"` + Path string `json:"path"` + Hash string `json:"hash"` +} + +func (q *Queries) InsertGameWithExistingId(ctx context.Context, arg InsertGameWithExistingIdParams) error { + _, err := q.db.Exec(ctx, insertGameWithExistingId, + arg.ID, + arg.GameName, + arg.Path, + arg.Hash, + ) + return err +} + +const removeDeletionDate = `-- name: RemoveDeletionDate :exec +UPDATE game SET deleted=NULL WHERE id=$1 +` + +func (q *Queries) RemoveDeletionDate(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, removeDeletionDate, id) + return err +} + +const resetGameIdSeq = `-- name: ResetGameIdSeq :one +SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1) +` + +func (q *Queries) ResetGameIdSeq(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, resetGameIdSeq) + var setval int64 + err := row.Scan(&setval) + return setval, err +} + +const setGameDeletionDate = `-- name: SetGameDeletionDate :exec +UPDATE game SET deleted=now() WHERE deleted IS NULL +` + +func (q *Queries) SetGameDeletionDate(ctx context.Context) error { + _, err := q.db.Exec(ctx, setGameDeletionDate) + return err +} + +const updateGameHash = `-- name: UpdateGameHash :exec +UPDATE game SET hash=$1, last_changed=now() WHERE id=$2 +` + +type UpdateGameHashParams struct { + Hash string `json:"hash"` + ID int32 `json:"id"` +} + +func (q *Queries) UpdateGameHash(ctx context.Context, arg UpdateGameHashParams) error { + _, err := q.db.Exec(ctx, updateGameHash, arg.Hash, arg.ID) + return err +} + +const updateGameName = `-- name: UpdateGameName :exec +UPDATE game SET game_name=$1, path=$2, last_changed=now() WHERE id=$3 +` + +type UpdateGameNameParams struct { + Name string `json:"name"` + Path string `json:"path"` + ID int32 `json:"id"` +} + +func (q *Queries) UpdateGameName(ctx context.Context, arg UpdateGameNameParams) error { + _, err := q.db.Exec(ctx, updateGameName, arg.Name, arg.Path, arg.ID) + return err +} diff --git a/db/repository/models.go b/db/repository/models.go new file mode 100644 index 0000000..5c8af68 --- /dev/null +++ b/db/repository/models.go @@ -0,0 +1,47 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package repository + +import ( + "time" +) + +type Game struct { + ID int32 `json:"id"` + GameName string `json:"game_name"` + Added time.Time `json:"added"` + Deleted *time.Time `json:"deleted"` + LastChanged *time.Time `json:"last_changed"` + Path string `json:"path"` + TimesPlayed *int32 `json:"times_played"` + LastPlayed *time.Time `json:"last_played"` + NumberOfSongs int32 `json:"number_of_songs"` + Hash string `json:"hash"` +} + +type Song struct { + GameID int32 `json:"game_id"` + SongName string `json:"song_name"` + Path string `json:"path"` + TimesPlayed *int32 `json:"times_played"` + Hash string `json:"hash"` + FileName *string `json:"file_name"` +} + +type SongList struct { + MatchDate time.Time `json:"match_date"` + MatchID int32 `json:"match_id"` + SongNo int32 `json:"song_no"` + GameName *string `json:"game_name"` + SongName *string `json:"song_name"` +} + +type Vgmq struct { + SongNo int32 `json:"song_no"` + Path *string `json:"path"` + Clue *string `json:"clue"` + Answered bool `json:"answered"` + Answer *string `json:"answer"` +} diff --git a/db/repository/song.sql.go b/db/repository/song.sql.go new file mode 100644 index 0000000..b3902a7 --- /dev/null +++ b/db/repository/song.sql.go @@ -0,0 +1,223 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: song.sql + +package repository + +import ( + "context" +) + +const addHashToSong = `-- name: AddHashToSong :exec +UPDATE song SET hash=$1 where path=$2 +` + +type AddHashToSongParams struct { + Hash string `json:"hash"` + Path string `json:"path"` +} + +func (q *Queries) AddHashToSong(ctx context.Context, arg AddHashToSongParams) error { + _, err := q.db.Exec(ctx, addHashToSong, arg.Hash, arg.Path) + return err +} + +const addSong = `-- name: AddSong :exec +INSERT INTO song(game_id, song_name, path, file_name, hash) VALUES ($1, $2, $3, $4, $5) +` + +type AddSongParams struct { + GameID int32 `json:"game_id"` + SongName string `json:"song_name"` + Path string `json:"path"` + FileName *string `json:"file_name"` + Hash string `json:"hash"` +} + +func (q *Queries) AddSong(ctx context.Context, arg AddSongParams) error { + _, err := q.db.Exec(ctx, addSong, + arg.GameID, + arg.SongName, + arg.Path, + arg.FileName, + arg.Hash, + ) + return err +} + +const addSongPlayed = `-- name: AddSongPlayed :exec +UPDATE song SET times_played = times_played + 1 +WHERE game_id = $1 AND song_name = $2 +` + +type AddSongPlayedParams struct { + GameID int32 `json:"game_id"` + SongName string `json:"song_name"` +} + +func (q *Queries) AddSongPlayed(ctx context.Context, arg AddSongPlayedParams) error { + _, err := q.db.Exec(ctx, addSongPlayed, arg.GameID, arg.SongName) + return err +} + +const checkSong = `-- name: CheckSong :one +SELECT COUNT(*) FROM song WHERE path = $1 +` + +func (q *Queries) CheckSong(ctx context.Context, path string) (int64, error) { + row := q.db.QueryRow(ctx, checkSong, path) + var count int64 + err := row.Scan(&count) + return count, err +} + +const checkSongWithHash = `-- name: CheckSongWithHash :one +SELECT COUNT(*) FROM song WHERE hash = $1 +` + +func (q *Queries) CheckSongWithHash(ctx context.Context, hash string) (int64, error) { + row := q.db.QueryRow(ctx, checkSongWithHash, hash) + var count int64 + err := row.Scan(&count) + return count, err +} + +const clearSongs = `-- name: ClearSongs :exec +DELETE FROM song +` + +func (q *Queries) ClearSongs(ctx context.Context) error { + _, err := q.db.Exec(ctx, clearSongs) + return err +} + +const clearSongsByGameId = `-- name: ClearSongsByGameId :exec +DELETE FROM song WHERE game_id = $1 +` + +func (q *Queries) ClearSongsByGameId(ctx context.Context, gameID int32) error { + _, err := q.db.Exec(ctx, clearSongsByGameId, gameID) + return err +} + +const fetchAllSongs = `-- name: FetchAllSongs :many +SELECT game_id, song_name, path, times_played, hash, file_name FROM song +` + +func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) { + rows, err := q.db.Query(ctx, fetchAllSongs) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Song + for rows.Next() { + var i Song + if err := rows.Scan( + &i.GameID, + &i.SongName, + &i.Path, + &i.TimesPlayed, + &i.Hash, + &i.FileName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const findSongsFromGame = `-- name: FindSongsFromGame :many +SELECT game_id, song_name, path, times_played, hash, file_name +FROM song +WHERE game_id = $1 +` + +func (q *Queries) FindSongsFromGame(ctx context.Context, gameID int32) ([]Song, error) { + rows, err := q.db.Query(ctx, findSongsFromGame, gameID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Song + for rows.Next() { + var i Song + if err := rows.Scan( + &i.GameID, + &i.SongName, + &i.Path, + &i.TimesPlayed, + &i.Hash, + &i.FileName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSongWithHash = `-- name: GetSongWithHash :one +SELECT game_id, song_name, path, times_played, hash, file_name FROM song WHERE hash = $1 +` + +func (q *Queries) GetSongWithHash(ctx context.Context, hash string) (Song, error) { + row := q.db.QueryRow(ctx, getSongWithHash, hash) + var i Song + err := row.Scan( + &i.GameID, + &i.SongName, + &i.Path, + &i.TimesPlayed, + &i.Hash, + &i.FileName, + ) + return i, err +} + +const removeBrokenSong = `-- name: RemoveBrokenSong :exec +DELETE FROM song WHERE path = $1 +` + +func (q *Queries) RemoveBrokenSong(ctx context.Context, path string) error { + _, err := q.db.Exec(ctx, removeBrokenSong, path) + return err +} + +const removeBrokenSongs = `-- name: RemoveBrokenSongs :exec +DELETE FROM song where path = any ($1) +` + +func (q *Queries) RemoveBrokenSongs(ctx context.Context, paths []string) error { + _, err := q.db.Exec(ctx, removeBrokenSongs, paths) + return err +} + +const updateSong = `-- name: UpdateSong :exec +UPDATE song SET song_name=$1, file_name=$2, path=$3 where hash=$4 +` + +type UpdateSongParams struct { + SongName string `json:"song_name"` + FileName *string `json:"file_name"` + Path string `json:"path"` + Hash string `json:"hash"` +} + +func (q *Queries) UpdateSong(ctx context.Context, arg UpdateSongParams) error { + _, err := q.db.Exec(ctx, updateSong, + arg.SongName, + arg.FileName, + arg.Path, + arg.Hash, + ) + return err +} diff --git a/db/repository/song_list.sql.go b/db/repository/song_list.sql.go new file mode 100644 index 0000000..5fb58e6 --- /dev/null +++ b/db/repository/song_list.sql.go @@ -0,0 +1,68 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: song_list.sql + +package repository + +import ( + "context" + "time" +) + +const getSongList = `-- name: GetSongList :many +SELECT match_date, match_id, song_no, game_name, song_name +FROM song_list +WHERE match_date = $1 +ORDER BY song_no DESC +` + +func (q *Queries) GetSongList(ctx context.Context, matchDate time.Time) ([]SongList, error) { + rows, err := q.db.Query(ctx, getSongList, matchDate) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SongList + for rows.Next() { + var i SongList + if err := rows.Scan( + &i.MatchDate, + &i.MatchID, + &i.SongNo, + &i.GameName, + &i.SongName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertSongInList = `-- name: InsertSongInList :exec +INSERT INTO song_list (match_date, match_id, song_no, game_name, song_name) +VALUES ($1, $2, $3, $4, $5) +` + +type InsertSongInListParams struct { + MatchDate time.Time `json:"match_date"` + MatchID int32 `json:"match_id"` + SongNo int32 `json:"song_no"` + GameName *string `json:"game_name"` + SongName *string `json:"song_name"` +} + +func (q *Queries) InsertSongInList(ctx context.Context, arg InsertSongInListParams) error { + _, err := q.db.Exec(ctx, insertSongInList, + arg.MatchDate, + arg.MatchID, + arg.SongNo, + arg.GameName, + arg.SongName, + ) + return err +} diff --git a/go.mod b/go.mod index 7a6f59e..6013398 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,14 @@ module music-server go 1.22.2 require ( + github.com/MShekow/directory-checksum v1.4.6 github.com/gin-contrib/static v1.1.2 github.com/gin-gonic/gin v1.10.0 + github.com/golang-migrate/migrate v3.5.4+incompatible github.com/jackc/pgtype v1.14.3 github.com/jackc/pgx/v5 v5.5.5 + github.com/spf13/afero v1.11.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -14,12 +18,19 @@ require ( github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.3.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-errors/errors v1.5.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect @@ -27,18 +38,23 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lib/pq v1.10.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.1.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ea52b9c..8146602 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/MShekow/directory-checksum v1.4.6 h1:2fhlCYbpjEN1iH9S0tdmEM0p1wvNT9x5x0rIchGI7nE= +github.com/MShekow/directory-checksum v1.4.6/go.mod h1:bMfFBkaIlNk7O9VgEi8D2X7Q2Jfk3c7d67z3t6cpIi4= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= @@ -15,6 +19,16 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -23,8 +37,14 @@ github.com/gin-contrib/static v1.1.2 h1:c3kT4bFkUJn2aoRU3s6XnMjJT8J6nNWJkR0Nglqm github.com/gin-contrib/static v1.1.2/go.mod h1:Fw90ozjHCmZBWbgrsqrDvO28YbhKEKzKp8GixhR4yLw= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -37,8 +57,12 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= +github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= @@ -98,6 +122,7 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= @@ -126,13 +151,20 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -147,6 +179,8 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -168,8 +202,18 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -201,12 +245,16 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -215,9 +263,12 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -228,6 +279,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -264,13 +316,15 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/pkg/api/sync.go b/pkg/api/sync.go index a5296a5..5ea1edb 100644 --- a/pkg/api/sync.go +++ b/pkg/api/sync.go @@ -1,9 +1,10 @@ package api import ( - "github.com/gin-gonic/gin" "music-server/pkg/server" "net/http" + + "github.com/gin-gonic/gin" ) type Sync struct { @@ -25,6 +26,12 @@ func (s *Sync) SyncGamesQuick(ctx *gin.Context) { ctx.JSON(http.StatusOK, "Games are synced") } +func (s *Sync) SyncGamesNew(ctx *gin.Context) { + response := server.SyncGamesNew() + server.Reset() + ctx.JSON(http.StatusOK, response) +} + func (s *Sync) ResetGames(ctx *gin.Context) { server.ResetDB() ctx.JSON(http.StatusOK, "Games and songs are deleted from the database") diff --git a/pkg/conf/conf.go b/pkg/conf/conf.go index acf5ddb..9fd35ea 100644 --- a/pkg/conf/conf.go +++ b/pkg/conf/conf.go @@ -7,6 +7,7 @@ import ( "music-server/pkg/api" "music-server/pkg/db" "music-server/pkg/helpers" + "music-server/pkg/server" "os" "strconv" @@ -15,6 +16,12 @@ import ( ) func SetupDb() { + + /*err := server.ReadConf("conf.yaml") + if err != nil { + log.Fatal(err) + }*/ + // Get the value of an Environment Variable host := os.Getenv("DB_HOST") dbPort, dbPortErr := strconv.Atoi(os.Getenv("DB_PORT")) @@ -35,16 +42,35 @@ func SetupDb() { dbPort = 5432 } if username == "" { - username = "sebastian" + username = "postgres" } if password == "" { - password = "950100" + password = "postgres" } if dbName == "" { - dbName = "music_dev_local" + dbName = "music_test_local" } + db.Migrate_db(host, dbPort, username, password, dbName) + db.InitDB(host, dbPort, username, password, dbName) + + var dir string + if host != "localhost" { + dir = "/sorted/" + } else { + dir = "/Users/sebastian/ResilioSync/Sorterat_test/" + } + + server.Conf = &server.Config{ + Host: host, + Port: dbPort, + User: username, + Password: password, + Dbname: dbName, + Path: dir, + } + } func CloseDb() { @@ -60,6 +86,7 @@ func SetupRestServer(swagger embed.FS, search embed.FS) { syncGroup := router.Group("/sync") { syncGroup.GET("", sync.SyncGames) + syncGroup.GET("/new", sync.SyncGamesNew) syncGroup.GET("/quick", sync.SyncGamesQuick) syncGroup.GET("/reset", sync.ResetGames) } diff --git a/pkg/db/dbHelper.go b/pkg/db/dbHelper.go index 9e9a016..b6038c6 100644 --- a/pkg/db/dbHelper.go +++ b/pkg/db/dbHelper.go @@ -2,14 +2,21 @@ package db import ( "context" + "database/sql" "fmt" + "log" "os" + "github.com/golang-migrate/migrate" + "github.com/golang-migrate/migrate/database/postgres" + _ "github.com/golang-migrate/migrate/database/postgres" + _ "github.com/golang-migrate/migrate/source/file" "github.com/jackc/pgx/v5/pgxpool" + _ "github.com/lib/pq" ) -var dbpool *pgxpool.Pool -var ctx = context.Background() +var Dbpool *pgxpool.Pool +var Ctx = context.Background() func InitDB(host string, port int, user string, password string, dbname string) { @@ -20,14 +27,14 @@ func InitDB(host string, port int, user string, password string, dbname string) fmt.Println(psqlInfo) var err error - dbpool, err = pgxpool.New(ctx, psqlInfo) + Dbpool, err = pgxpool.New(Ctx, psqlInfo) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err) os.Exit(1) } var success string - err = dbpool.QueryRow(ctx, "select 'Successfully connected!'").Scan(&success) + err = Dbpool.QueryRow(Ctx, "select 'Successfully connected!'").Scan(&success) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err) os.Exit(1) @@ -37,11 +44,11 @@ func InitDB(host string, port int, user string, password string, dbname string) } func CloseDb() { - dbpool.Close() + Dbpool.Close() } func Testf() { - rows, dbErr := dbpool.Query(ctx, "select game_name from game") + rows, dbErr := Dbpool.Query(Ctx, "select game_name from game") if dbErr != nil { _, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr) os.Exit(1) @@ -57,8 +64,60 @@ func Testf() { } func resetGameIdSeq() { - _, err := dbpool.Query(ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);") + _, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);") if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) } } + +func Migrate_db(host string, port int, user string, password string, dbname string) { + migrationInfo := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", + user, password, host, port, dbname) + + fmt.Println("Migration Info: ", migrationInfo) + + db, err := sql.Open("postgres", migrationInfo) + if err != nil { + log.Println(err) + } + driver, err := postgres.WithInstance(db, &postgres.Config{}) + if err != nil { + log.Println(err) + } + m, err := migrate.NewWithDatabaseInstance( + "file://./db/migrations/", + "postgres", driver) + if err != nil { + log.Println(err) + } + + version, _, err := m.Version() + if err != nil { + log.Println("Migration version err: ", err) + } + + fmt.Println("Migration version before: ", version) + + err = m.Force(1) + //err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run + if err != nil { + log.Println("Force err: ", err) + } + + err = m.Migrate(2) + //err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run + if err != nil { + log.Println("Migration err: ", err) + } + + versionAfter, _, err := m.Version() + if err != nil { + log.Println("Migration version err: ", err) + } + + fmt.Println("Migration version after: ", versionAfter) + + fmt.Println("Migration done") + + db.Close() +} diff --git a/pkg/db/game.go b/pkg/db/game.go index 9787cbd..9ef3d36 100644 --- a/pkg/db/game.go +++ b/pkg/db/game.go @@ -12,7 +12,7 @@ import ( func GetGameName(gameId int) string { var gameName = "" - err := dbpool.QueryRow(ctx, + err := Dbpool.QueryRow(Ctx, "SELECT game_name FROM game WHERE id = $1", gameId).Scan(&gameName) if err != nil { if compareError.Error() != err.Error() { @@ -28,7 +28,7 @@ func GetGameById(gameId int) (models.GameData, error) { var numberOfSongs pgtype.Int4 var gameName, path string var added, deleted, lastChanged, lastPlayed pgtype.Timestamp - err := dbpool.QueryRow(ctx, + err := Dbpool.QueryRow(Ctx, "SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+ "FROM game WHERE id = $1 AND deleted IS NULL ", gameId).Scan(&id, &gameName, &added, &deleted, &lastChanged, &path, ×Played, &lastPlayed, &numberOfSongs) if err != nil { @@ -51,7 +51,7 @@ func GetGameById(gameId int) (models.GameData, error) { } func SetGameDeletionDate() { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "UPDATE game SET deleted=$1 WHERE deleted IS NULL", time.Now()) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) @@ -59,14 +59,14 @@ func SetGameDeletionDate() { } func ClearGames() { - _, err := dbpool.Exec(ctx, "DELETE FROM game") + _, err := Dbpool.Exec(Ctx, "DELETE FROM game") if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) } } func UpdateGameName(id int, name string, path string) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "UPDATE game SET game_name=$1, path=$2, last_changed=$3 WHERE id=$4", name, path, time.Now(), id) if err != nil { @@ -75,7 +75,7 @@ func UpdateGameName(id int, name string, path string) { } func RemoveDeletionDate(id int) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "UPDATE game SET deleted=null WHERE id=$1", id) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) @@ -84,7 +84,7 @@ func RemoveDeletionDate(id int) { func GetIdByGameName(name string) int { var gameId = -1 - err := dbpool.QueryRow(ctx, + err := Dbpool.QueryRow(Ctx, "SELECT id FROM game WHERE game_name = $1", name).Scan(&gameId) if err != nil { if compareError.Error() != err.Error() { @@ -97,7 +97,7 @@ func GetIdByGameName(name string) int { func InsertGame(name string, path string) int { gameId := -1 - err := dbpool.QueryRow(ctx, + err := Dbpool.QueryRow(Ctx, "INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id", name, path, time.Now()).Scan(&gameId) if err != nil { @@ -105,7 +105,7 @@ func InsertGame(name string, path string) int { _, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err) } resetGameIdSeq() - err2 := dbpool.QueryRow(ctx, + err2 := Dbpool.QueryRow(Ctx, "INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id", name, path, time.Now()).Scan(&gameId) if err2 != nil { @@ -119,7 +119,7 @@ func InsertGame(name string, path string) int { } func InsertGameWithExistingId(id int, name string, path string) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "INSERT INTO game(id, game_name, path, added) VALUES ($1, $2, $3, $4)", id, name, path, time.Now()) if err != nil { @@ -128,7 +128,7 @@ func InsertGameWithExistingId(id int, name string, path string) { } func FindAllGames() []models.GameData { - rows, err := dbpool.Query(ctx, + rows, err := Dbpool.Query(Ctx, "SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+ "FROM game WHERE deleted IS NULL "+ "ORDER BY game_name") @@ -165,7 +165,7 @@ func FindAllGames() []models.GameData { } func AddGamePlayed(id int) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "UPDATE game SET times_played=times_played+1, last_played=now() WHERE id=$1", id) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) diff --git a/pkg/db/song.go b/pkg/db/song.go index fc631cf..0025421 100644 --- a/pkg/db/song.go +++ b/pkg/db/song.go @@ -12,12 +12,12 @@ var compareError = errors.New("no rows in result set") func ClearSongs(gameId int) { if gameId == -1 { - _, err := dbpool.Exec(ctx, "DELETE FROM song") + _, err := Dbpool.Exec(Ctx, "DELETE FROM song") if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) } } else { - _, err := dbpool.Exec(ctx, "DELETE FROM song where game_id=$1", gameId) + _, err := Dbpool.Exec(Ctx, "DELETE FROM song where game_id=$1", gameId) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) } @@ -25,7 +25,7 @@ func ClearSongs(gameId int) { } func AddSong(song models.SongData) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "INSERT INTO song(game_id, song_name, path, file_name) VALUES ($1, $2, $3, $4)", song.GameId, song.SongName, song.Path, song.FileName) if err != nil { @@ -35,7 +35,7 @@ func AddSong(song models.SongData) { func CheckSong(songPath string) bool { var path string - err := dbpool.QueryRow(ctx, + err := Dbpool.QueryRow(Ctx, "SELECT path FROM song WHERE path = $1", songPath).Scan(&path) if err != nil { if compareError.Error() != err.Error() { @@ -46,7 +46,7 @@ func CheckSong(songPath string) bool { } func UpdateSong(songName string, fileName string, path string) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "UPDATE song SET song_name=$1, file_name=$2 WHERE path = $3", songName, fileName, path) if err != nil { @@ -55,7 +55,7 @@ func UpdateSong(songName string, fileName string, path string) { } func FindSongsFromGame(id int) []models.SongData { - rows, err := dbpool.Query(ctx, + rows, err := Dbpool.Query(Ctx, "SELECT song_name, path, file_name, times_played FROM song WHERE game_id = $1", id) if err != nil { if compareError.Error() != err.Error() { @@ -90,7 +90,7 @@ func FindSongsFromGame(id int) []models.SongData { } func AddSongPlayed(id int, name string) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, "UPDATE song SET times_played=times_played+1 WHERE game_id=$1 AND song_name=$2", id, name) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) @@ -98,7 +98,7 @@ func AddSongPlayed(id int, name string) { } func FetchAllSongs() []models.SongData { - rows, err := dbpool.Query(ctx, + rows, err := Dbpool.Query(Ctx, "SELECT song_name, path FROM song") if err != nil { if compareError.Error() != err.Error() { @@ -128,7 +128,7 @@ func FetchAllSongs() []models.SongData { } func RemoveBrokenSong(song models.SongData) { - _, err := dbpool.Exec(ctx, "DELETE FROM song where path=$1", song.Path) + _, err := Dbpool.Exec(Ctx, "DELETE FROM song where path=$1", song.Path) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) } @@ -141,7 +141,7 @@ func RemoveBrokenSongs(songs []models.SongData) { } joined = strings.TrimSuffix(joined, ",") - _, err := dbpool.Exec(ctx, "DELETE FROM song where path in ($1)", joined) + _, err := Dbpool.Exec(Ctx, "DELETE FROM song where path in ($1)", joined) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err) } diff --git a/pkg/db/songList.go b/pkg/db/songList.go index 05522d3..8989679 100644 --- a/pkg/db/songList.go +++ b/pkg/db/songList.go @@ -8,7 +8,7 @@ import ( ) func InsertSongInList(song models.SongListData) { - _, err := dbpool.Exec(ctx, + _, err := Dbpool.Exec(Ctx, `INSERT INTO song_list (match_date, match_id, song_no, game_name, song_name) VALUES ($1, $2, $3, $4, $5)`, song.MatchDate, song.MatchId, song.SongNo, song.GameName, song.SongName) if err != nil { @@ -17,7 +17,7 @@ func InsertSongInList(song models.SongListData) { } func GetSongList(matchId int) []models.SongListData { - rows, err := dbpool.Query(ctx, + rows, err := Dbpool.Query(Ctx, "SELECT match_date, match_id, song_no, game_name, song_name "+ "FROM song_list WHERE match_date = $1"+ "ORDER BY song_no DESC", matchId) diff --git a/pkg/server/config.go b/pkg/server/config.go new file mode 100644 index 0000000..2d692f5 --- /dev/null +++ b/pkg/server/config.go @@ -0,0 +1,34 @@ +package server + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Host string + Port int + User string + Password string + Dbname string + Path string +} + +var Conf *Config + +func ReadConf(filename string) error { + buf, err := os.ReadFile(filename) + if err != nil { + return err + } + + c := &Config{} + err = yaml.Unmarshal(buf, c) + if err != nil { + return fmt.Errorf("in file %q: %w", filename, err) + } + Conf = c + return err +} diff --git a/pkg/server/music.go b/pkg/server/music.go index 98ef93f..fb05a6a 100644 --- a/pkg/server/music.go +++ b/pkg/server/music.go @@ -48,6 +48,9 @@ func GetRandomSong() string { if games == nil || len(games) == 0 { games = db.FindAllGames() } + if games == nil || len(games) == 0 { + return "" + } song := getSongFromList(games) lastFetched = song diff --git a/pkg/server/sync.go b/pkg/server/sync.go index d0aa4dd..d63cc82 100644 --- a/pkg/server/sync.go +++ b/pkg/server/sync.go @@ -1,9 +1,14 @@ package server import ( + "crypto/md5" + "encoding/hex" + "errors" "fmt" + "io" "io/fs" "log" + "music-server/db/repository" "music-server/pkg/db" "music-server/pkg/models" "os" @@ -11,17 +16,65 @@ import ( "strconv" "strings" "sync" + "time" + + "github.com/MShekow/directory-checksum/directory_checksum" + "github.com/spf13/afero" ) +var allGames []repository.Game +var gamesBeforeSync []repository.Game +var gamesAfterSync []repository.Game +var gamesAdded []string +var gamesReAdded []string +var gamesChangedTitle map[string]string +var gamesChangedContent []string +var gamesRemoved []string +var catchedErrors []string +var brokenSongs []string + +type Response struct { + GamesAdded []string `json:"games_added"` + GamesReAdded []string `json:"games_re_added"` + GamesChangedTitle map[string]string `json:"games_changed_title"` + GamesChangedContent []string `json:"games_changed_content"` + GamesRemoved []string `json:"games_removed"` + CatchedErrors []string `json:"catched_errors"` +} + +type GameStatus int + +const ( + NotChanged GameStatus = iota + TitleChanged + GameChanged + NewGame +) + +var statusName = map[GameStatus]string{ + NotChanged: "Not changed", + TitleChanged: "Title changed", + GameChanged: "Game changed", + NewGame: "New game", +} + +func (gs GameStatus) String() string { + return statusName[gs] +} + +var syncWg sync.WaitGroup +var repo *repository.Queries + var wg sync.WaitGroup func SyncGames() { + start := time.Now() host := os.Getenv("DB_HOST") var dir string if host != "" { dir = "/sorted/" } else { - dir = "/Users/sebastian/Resilio Sync/Sorterat_test/" + dir = "/Users/sebastian/ResilioSync/Sorterat_test/" } fmt.Printf("dir: %s\n", dir) foldersToSkip := []string{".sync", "dist", "old"} @@ -37,15 +90,21 @@ func SyncGames() { for _, file := range files { syncGame(file, foldersToSkip, dir) } + finished := time.Now() + totalTime := finished.Sub(start) + out := time.Time{}.Add(totalTime) + fmt.Printf("\nTotal time: %v\n", totalTime) + fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000")) } func SyncGamesQuick() { + start := time.Now() host := os.Getenv("DB_HOST") var dir string if host != "" { dir = "/sorted/" } else { - dir = "/Users/sebastian/Resilio Sync/Sorterat_test/" + dir = "/Users/sebastian/ResilioSync/Sorterat_test/" } fmt.Printf("dir: %s\n", dir) foldersToSkip := []string{".sync", "dist", "old"} @@ -66,6 +125,11 @@ func SyncGamesQuick() { }() } wg.Wait() + finished := time.Now() + totalTime := finished.Sub(start) + out := time.Time{}.Add(totalTime) + fmt.Printf("\nTotal time: %v\n", totalTime) + fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000")) } func syncGame(file os.DirEntry, foldersToSkip []string, dir string) { @@ -133,7 +197,10 @@ func checkIfChanged(id int, name string, path string) { func addNewGame(name string, path string) { newId := db.GetIdByGameName(name) - if newId == -1 { + if newId != -1 { + checkBrokenSongs() + db.RemoveDeletionDate(newId) + } else { newId = db.InsertGame(name, path) } @@ -194,6 +261,401 @@ func checkBrokenSongs() { db.RemoveBrokenSongs(brokenSongs) } +func SyncGamesNew() Response { + repo = repository.New(db.Dbpool) + start := time.Now() + fmt.Printf("dir: %s\n", Conf.Path) + foldersToSkip := []string{".sync", "dist", "old"} + fmt.Println(foldersToSkip) + + var err error + + gamesBeforeSync, err = repo.FindAllGames(db.Ctx) + handleError("FindAllGames Before", err, "") + fmt.Printf("Games Before: %d\n", len(gamesBeforeSync)) + + allGames, err = repo.GetAllGamesIncludingDeleted(db.Ctx) + handleError("GetAllGamesIncludingDeleted", err, "") + err = repo.SetGameDeletionDate(db.Ctx) + handleError("SetGameDeletionDate", err, "") + + directories, err := os.ReadDir(Conf.Path) + if err != nil { + log.Fatal(err) + } + + syncWg.Add(len(directories)) + for _, dir := range directories { + go func() { + defer syncWg.Done() + syncGameNew(dir, foldersToSkip, Conf.Path) + }() + } + syncWg.Wait() + checkBrokenSongsNew() + + gamesAfterSync, err = repo.FindAllGames(db.Ctx) + handleError("FindAllGames After", err, "") + + fmt.Printf("\nGames Before: %d\n", len(gamesBeforeSync)) + fmt.Printf("Games After: %d\n", len(gamesAfterSync)) + + fmt.Printf("\nGames added: \n") + for _, game := range gamesAdded { + fmt.Printf("%s\n", game) + } + + fmt.Printf("\nGames readded: \n") + for _, game := range gamesReAdded { + fmt.Printf("%s\n", game) + } + + fmt.Printf("\nGames with changed title: \n") + for key, value := range gamesChangedTitle { + fmt.Printf("The game: %s changed title to: %s\n", key, value) + } + + fmt.Printf("\nGames with changed content: \n") + for _, game := range gamesChangedContent { + fmt.Printf("%s\n", game) + } + + fmt.Printf("\n\n") + var gamesRemovedTemp []string + for _, beforeGame := range gamesBeforeSync { + var found bool = false + for _, afterGame := range gamesAfterSync { + if beforeGame.GameName == afterGame.GameName { + found = true + fmt.Printf("Game: %s, Found: %v break\n", beforeGame.GameName, found) + break + } + } + if !found { + fmt.Printf("Game: %s, Found: %v\n", beforeGame.GameName, found) + gamesRemovedTemp = append(gamesRemovedTemp, beforeGame.GameName) + } + } + + for _, game := range gamesRemovedTemp { + var found bool = false + for key, _ := range gamesChangedTitle { + if game == key { + found = true + fmt.Printf("Game: %s, Found: %v break2\n", game, found) + break + } + } + if !found { + gamesRemoved = append(gamesRemoved, game) + } + } + + fmt.Printf("\nGames removed: \n") + for _, game := range gamesRemoved { + fmt.Printf("%s\n", game) + } + + fmt.Printf("\nErrors catched: \n") + for _, error := range catchedErrors { + fmt.Printf("%s\n", error) + } + + finished := time.Now() + totalTime := finished.Sub(start) + out := time.Time{}.Add(totalTime) + fmt.Printf("\nTotal time: %v\n", totalTime) + fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000")) + + return Response{ + GamesAdded: gamesAdded, + GamesReAdded: gamesReAdded, + GamesChangedTitle: gamesChangedTitle, + GamesChangedContent: gamesChangedContent, + GamesRemoved: gamesRemoved, + CatchedErrors: catchedErrors, + } +} + +func checkBrokenSongsNew() { + allSongs, err := repo.FetchAllSongs(db.Ctx) + handleError("FetchAllSongs", err, "") + var brokenWg sync.WaitGroup + brokenWg.Add(len(allSongs)) + for _, song := range allSongs { + go func() { + defer brokenWg.Done() + checkBrokenSongNew(song) + }() + } + brokenWg.Wait() + err = repo.RemoveBrokenSongs(db.Ctx, brokenSongs) + handleError("RemoveBrokenSongs", err, "") +} + +func checkBrokenSongNew(song repository.Song) { + //Check if file exists and open + openFile, err := os.Open(song.Path) + if err != nil { + //File not found + brokenSongs = append(brokenSongs, song.Path) + fmt.Printf("song broken: %v\n", song.Path) + } else { + err = openFile.Close() + if err != nil { + log.Println(err) + } + } +} + +func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string) { + if file.IsDir() && !contains(foldersToSkip, file.Name()) { + gameDir := baseDir + file.Name() + "/" + + dirHash := getHashForDir(gameDir) + + var status GameStatus = NewGame + var oldGame repository.Game + var id int32 = -1 + + //fmt.Printf("Games before: %d\n", len(gamesBeforeSync)) + + for _, currentGame := range allGames { + oldGame = currentGame + //fmt.Printf("%s | %s\n", oldGame.GameName, oldGame.Hash) + if oldGame.GameName == file.Name() && oldGame.Hash == dirHash { + status = NotChanged + id = oldGame.ID + //fmt.Printf("Game not changed\n") + break + } else if oldGame.GameName == file.Name() && oldGame.Hash != dirHash { + status = GameChanged + id = oldGame.ID + //fmt.Printf("Game changed\n") + break + } else if oldGame.GameName != file.Name() && oldGame.Hash == dirHash { + status = TitleChanged + id = oldGame.ID + //fmt.Printf("GameName changed\n") + break + } + } + + fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status) + + entries, err := os.ReadDir(gameDir) + if err != nil { + log.Println(err) + } + switch status { + case NewGame: + if id != -1 { + for _, entry := range entries { + fileInfo, err := entry.Info() + if err != nil { + log.Println(err) + } + id = getIdFromFileNew(fileInfo) + if id != -1 { + break + } + } + err = repo.InsertGameWithExistingId(db.Ctx, repository.InsertGameWithExistingIdParams{ID: id, GameName: file.Name(), Path: gameDir, Hash: dirHash}) + handleError("InsertGameWithExistingId", err, "") + if err != nil { + fmt.Printf("id = %v\n", id) + fileName := gameDir + "/." + strconv.Itoa(int(id)) + ".id" + fmt.Printf("fileName = %v\n", fileName) + + err := os.Remove(fileName) + if err != nil { + fmt.Printf("%s\n", err) + } + + newDirHash := getHashForDir(gameDir) + + id = insertGameNew(file.Name(), gameDir, newDirHash) + } + } else { + id = insertGameNew(file.Name(), gameDir, dirHash) + } + gamesAdded = append(gamesAdded, file.Name()) + newCheckSongs(entries, gameDir, id) + case GameChanged: + err = repo.UpdateGameHash(db.Ctx, repository.UpdateGameHashParams{Hash: dirHash, ID: id}) + handleError("UpdateGameHash", err, "") + gamesChangedContent = append(gamesChangedContent, file.Name()) + newCheckSongs(entries, gameDir, id) + case TitleChanged: + //println("TitleChanged") + err = repo.UpdateGameName(db.Ctx, repository.UpdateGameNameParams{Name: file.Name(), Path: gameDir, ID: id}) + handleError("UpdateGameName", err, "") + newCheckSongs(entries, gameDir, id) + if gamesChangedTitle == nil { + gamesChangedTitle = make(map[string]string) + } + gamesChangedTitle[oldGame.GameName] = file.Name() + case NotChanged: + //println("NotChanged") + var found bool = false + for _, beforeGame := range gamesBeforeSync { + if dirHash == beforeGame.Hash { + found = true + //fmt.Printf("Game %s | %s | %s | %s | %v\n", beforeGame.GameName, beforeGame.Hash, dirHash, status, beforeGame.Deleted) + } + } + if !found { + newCheckSongs(entries, gameDir, id) + gamesReAdded = append(gamesReAdded, file.Name()) + + } + } + err = repo.RemoveDeletionDate(db.Ctx, id) + handleError("RemoveDeletionDate", err, "") + } +} + +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}) + handleError("InsertGame", err, "") + if err != nil { + fmt.Printf("Handle id busy\n") + if strings.HasPrefix(err.Error(), duplicateError.Error()) { + fmt.Printf("Handeling this id\n") + _, err = repo.ResetGameIdSeq(db.Ctx) + handleError("ResetGameIdSeq", err, "") + id = insertGameNew(name, path, hash) + } + } + return id + +} + +func newCheckSongs(entries []os.DirEntry, gameDir string, id int32) int32 { + //hasher := md5.New() + var numberOfSongs int32 + var songWg sync.WaitGroup + songWg.Add(len(entries)) + for _, entry := range entries { + go func() { + defer songWg.Done() + newCheckSong(entry, gameDir, id) + }() + } + songWg.Wait() + return numberOfSongs +} + +func newCheckSong(entry os.DirEntry, gameDir string, id int32) { + fileInfo, err := entry.Info() + if err != nil { + log.Println(err) + } + + if isSong(fileInfo) { + path := gameDir + entry.Name() + + songHash := getHashForFile(path) + //numberOfSongs++ + + fileName := entry.Name() + songName, _ := strings.CutSuffix(fileName, ".mp3") + + song, err := repo.GetSongWithHash(db.Ctx, songHash) + handleError("GetSongWithHash", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash)) + if err == nil { + if song.SongName == songName && song.Path == path { + return + } + } + fmt.Printf("Song Changed\n") + + fmt.Printf("Path: %s | SongHash: %s\n", path, songHash) + + count, err := repo.CheckSongWithHash(db.Ctx, 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) + 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}) + handleError("AddHashToSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash)) + count, err = repo.CheckSongWithHash(db.Ctx, songHash) + handleError("CheckSongWithHash 2", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", 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}) + handleError("UpdateSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash)) + } else { + count2, err := repo.CheckSong(db.Ctx, 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}) + handleError("AddHashToSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash)) + } else { + err = repo.AddSong(db.Ctx, 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\n", id, path, entry.Name(), songHash)) + + } + } + } else if isCoverImage(fileInfo) { + //TODO: Later add cover art image here in db + } +} + +func handleError(funcName string, err error, msg string) { + var compareError = errors.New("no rows in result set") + if err != nil { + if compareError.Error() != err.Error() { + fmt.Printf("%s Error: %s\n", funcName, err) + if msg != "" { + fmt.Printf("%s\n", msg) + catchedErrors = append(catchedErrors, fmt.Sprintf("Func: %s\nError message: %s\nDebug message: %s\n", funcName, err, msg)) + } else { + catchedErrors = append(catchedErrors, fmt.Sprintf("Func: %s\nError message: %s\n", funcName, err)) + } + } + } +} + +func getHashForDir(gameDir string) string { + directory, _ := directory_checksum.ScanDirectory(gameDir, afero.NewOsFs()) + hash, _ := directory.ComputeDirectoryChecksums() + + //fmt.Printf("Hash: |%s|\n", hash) + return hash +} + +func getHashForFile(path string) string { + hasher := md5.New() + readFile, err := os.Open(path) + if err != nil { + panic(err) + } + defer readFile.Close() + hasher.Reset() + _, err = io.Copy(hasher, readFile) + if err != nil { + log.Fatal(err) + } + return hex.EncodeToString(hasher.Sum(nil)) +} + +func getIdFromFileNew(file os.FileInfo) int32 { + name := file.Name() + if !file.IsDir() && strings.HasSuffix(name, ".id") { + name = strings.Replace(name, ".id", "", 1) + name = strings.Replace(name, ".", "", 1) + i, _ := strconv.Atoi(name) + return int32(i) + } + return -1 +} + func isSong(entry fs.FileInfo) bool { return !entry.IsDir() && strings.HasSuffix(entry.Name(), ".mp3") } diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..95987a3 --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,37 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "./db/queries" + schema: "./db/migrations" + gen: + go: + emit_json_tags: true + package: "repository" + out: "db/repository" + sql_package: "pgx/v5" + overrides: + - db_type: "pg_catalog.timestamp" + go_type: + import: "time" + type: "Time" + - db_type: "pg_catalog.timestamp" + nullable: true + go_type: + pointer: true + import: "time" + type: "Time" + - db_type: "pg_catalog.varchar" + nullable: true + go_type: + pointer: true + type: "string" + - db_type: "int4" + nullable: true + go_type: + pointer: true + type: "int32" + - db_type: "date" + nullable: false + go_type: + import: "time" + type: "Time"