From 7c29f64f6dcbe4b15ccacb35bdc9e3b0b7d5da73 Mon Sep 17 00:00:00 2001 From: JuLi0n21 Date: Wed, 16 Jul 2025 22:10:49 +0200 Subject: [PATCH] replace sql mostly with sqlc --- grpc-backend/database.go | 357 +-------------------- grpc-backend/go.mod | 8 +- grpc-backend/go.sum | 40 +-- grpc-backend/handlers.go | 85 +++-- grpc-backend/internal/db/beatmap.sql.go | 187 +++++------ grpc-backend/internal/db/collection.sql.go | 207 ++++++------ grpc-backend/internal/db/models.go | 4 +- grpc-backend/internal/db/querier.go | 11 +- grpc-backend/models.go | 125 -------- grpc-backend/sqlc.yaml | 3 + grpc-backend/sqlc/query/beatmap.sql | 9 +- grpc-backend/sqlc/query/collection.sql | 23 +- grpc-backend/sqlc/schema/schema.sql | 4 +- grpc-backend/sqlc_converters.go | 96 ++++++ 14 files changed, 418 insertions(+), 741 deletions(-) delete mode 100644 grpc-backend/models.go create mode 100644 grpc-backend/sqlc_converters.go diff --git a/grpc-backend/database.go b/grpc-backend/database.go index 7e7ea82..4dbf503 100644 --- a/grpc-backend/database.go +++ b/grpc-backend/database.go @@ -10,7 +10,6 @@ import ( "os" "path" "path/filepath" - "strconv" "github.com/juli0n21/go-osu-parser/parser" _ "modernc.org/sqlite" @@ -49,7 +48,9 @@ func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql. return nil, nil, err } - if err = checkhealth(db, osuDB); err != nil { + sqlcQueries := sqlcdb.New(db) + + if err = checkhealth(sqlcQueries, osuDB); err != nil { if err = rebuildBeatmapDb(db, osuDB); err != nil { return nil, nil, err } @@ -70,8 +71,6 @@ func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql. } } - sqlcQueries := sqlcdb.New(db) - return db, sqlcQueries, nil } @@ -90,13 +89,13 @@ func createDB(db *sql.DB) error { MD5Hash TEXT DEFAULT '00000000000000000000000000000000', File TEXT DEFAULT 'unknown.osu', RankedStatus TEXT DEFAULT Unknown, - LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00', + LastModifiedTime INTEGER DEFAULT 0, TotalTime INTEGER DEFAULT 0, AudioPreviewTime INTEGER DEFAULT 0, BeatmapSetId INTEGER DEFAULT -1, Source TEXT DEFAULT '', Tags TEXT DEFAULT '', - LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00', + LastPlayed INTEGER DEFAULT 0, Folder TEXT DEFAULT 'Unknown Folder', UNIQUE (Artist, Title, MD5Hash, Difficulty) ); @@ -146,34 +145,24 @@ func createCollectionDB(db *sql.DB) error { return nil } -func checkhealth(db *sql.DB, osuDb *parser.OsuDB) error { +func checkhealth(db *sqlcdb.Queries, osuDb *parser.OsuDB) error { - rows, err := db.Query(`SELECT COUNT(*) FROM Beatmap GROUP BY BeatmapSetId;`) + count, err := db.GetBeatmapSetCount(context.TODO()) if err != nil { return err } - defer rows.Close() - var count int - if err = rows.Scan(&count); err != nil { - return err - } - - if count != int(osuDb.FolderCount) { + if count != int64(osuDb.FolderCount) { log.Println("Folder count missmatch rebuilding db...") return ErrBeatmapCountNotMatch } - rows, err = db.Query(`SELECT COUNT(*) FROM Beatmap;`) + count, err = db.GetBeatmapCount(context.TODO()) if err != nil { return err } - if err = rows.Scan(&count); err != nil { - return err - } - - if count != int(osuDb.NumberOfBeatmaps) { + if count != int64(osuDb.NumberOfBeatmaps) { log.Println("Beatmap count missmatch rebuilding db...") return ErrBeatmapCountNotMatch } @@ -310,268 +299,6 @@ func rebuildCollectionDb(db *sql.DB, collectionDb *parser.Collections) error { return tx.Commit() } -func getBeatmapCount(db *sql.DB) int { - rows, err := db.Query("SELECT COUNT(*) FROM Beatmap") - if err != nil { - log.Println(err) - return 0 - } - defer rows.Close() - - var count int - if rows.Next() { - err = rows.Scan(&count) - if err != nil { - log.Println(err) - return 0 - } - } else { - return 0 - } - - return count -} - -func getRecent(ctx context.Context, q *sqlcdb.Queries, limit, offset int) ([]Song, error) { - rows, err := q.GetRecentBeatmaps(ctx, sqlcdb.GetRecentBeatmapsParams{ - Limit: int64(limit), Offset: int64(offset), - }) - if err != nil { - return nil, err - } - - var songs []Song - - for _, song := range rows { - songs = append(songs, convertToSong(song)) - } - return songs, nil -} - -func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) { - rows, err := db.Query( - ` - SELECT - BeatmapId, - MD5Hash, - Title, - Artist, - Creator, - Folder, - File, - Audio, - TotalTime - FROM Beatmap WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ? - `, "%"+q+"%", "%"+q+"%", limit, offset) - if err != nil { - return ActiveSearch{}, err - } - defer rows.Close() - s, err := scanSongs(rows) - if err != nil { - return ActiveSearch{}, err - } - return ActiveSearch{Songs: s}, nil -} - -func getArtists(db *sql.DB, q string, limit, offset int) ([]Artist, error) { - rows, err := db.Query("SELECT Artist, COUNT(Artist) FROM Beatmap WHERE Artist LIKE ? OR Title LIKE ? GROUP BY Artist LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset) - if err != nil { - return []Artist{}, err - } - defer rows.Close() - - artist := []Artist{} - for rows.Next() { - var a string - var c int - err := rows.Scan(&a, &c) - if err != nil { - return []Artist{}, err - } - artist = append(artist, Artist{Artist: a, Count: c}) - } - - return artist, nil -} - -func getFavorites(db *sql.DB, q string, limit, offset int) ([]Song, error) { - rows, err := db.Query("SELECT * FROM Songs WHERE IsFavorite = 1 LIMIT ? OFFSET ?", limit, offset) - if err != nil { - return nil, err - } - defer rows.Close() - return scanSongs(rows) -} - -func getCollection(db *sql.DB, limit, offset, index int) (Collection, error) { - rows, err := db.Query(` - WITH cols AS ( - SELECT - c.Name, - ROW_NUMBER() OVER (ORDER BY c.Name) AS RowNumber - FROM Collection c - GROUP BY c.Name - ) - - SELECT - c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist, - b.Creator, b.Folder, b.File, b.Audio, b.TotalTime - FROM Collection c - Join Beatmap b ON c.MD5Hash = b.MD5Hash - WHERE c.Name = (SELECT Name FROM cols WHERE RowNumber = ?) - LIMIT ? - OFFSET ?;`, index, limit, offset) - if err != nil { - return Collection{}, err - } - defer rows.Close() - - var c Collection - for rows.Next() { - s := Song{} - if err := rows.Scan(&c.Name, &s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil { - return Collection{}, err - } - - s.Image = extractImageFromFile(osuRoot, s.Folder, s.File) - - c.Songs = append(c.Songs, s) - } - - row := db.QueryRow(`SELECT COUNT(*) FROM Collection WHERE Name = ?`, c.Name) - var count string - row.Scan(&count) - - if i, err := strconv.Atoi(count); err == nil { - c.Items = i - } - - return c, nil -} - -func getCollectionByName(db *sql.DB, limit, offset int, name string) (Collection, error) { - rows, err := db.Query(` - SELECT - c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist, - b.Creator, b.Folder, b.File, b.Audio, b.TotalTime - FROM Collection c - Join Beatmap b ON c.MD5Hash = b.MD5Hash - WHERE c.Name = ? - LIMIT ? - OFFSET ?;`, name, limit, offset) - if err != nil { - return Collection{}, err - } - defer rows.Close() - - var c Collection - for rows.Next() { - s := Song{} - if err := rows.Scan(&c.Name, &s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil { - return Collection{}, err - } - - s.Image = extractImageFromFile(osuRoot, s.Folder, s.File) - - c.Songs = append(c.Songs, s) - } - - row := db.QueryRow(`SELECT COUNT(*) FROM Collection WHERE Name = ?`, c.Name) - var count string - row.Scan(&count) - - if i, err := strconv.Atoi(count); err == nil { - c.Items = i - } else { - return Collection{}, err - } - - return c, nil -} - -func getCollections(db *sql.DB, q string, limit, offset int) ([]CollectionPreview, error) { - rows, err := db.Query(` - SELECT - c.Name, - COUNT(b.MD5Hash) AS Count, - MIN(b.Folder) AS Folder, - MIN(b.File) AS File - FROM Collection c - JOIN Beatmap b ON c.MD5Hash = b.MD5Hash - WHERE c.Name LIKE ? - GROUP BY c.Name - LIMIT ? - OFFSET ?`, "%"+q+"%", limit, offset) - if err != nil { - return []CollectionPreview{}, err - } - defer rows.Close() - - var collections []CollectionPreview - for rows.Next() { - var c CollectionPreview - var folder, file, count string - if err := rows.Scan(&c.Name, &count, &folder, &file); err != nil { - return []CollectionPreview{}, err - } - - if i, err := strconv.Atoi(count); err == nil { - c.Items = i - } - c.Image = extractImageFromFile(osuRoot, folder, file) - - collections = append(collections, c) - } - - return collections, nil -} - -func getSong(db *sql.DB, hash string) (Song, error) { - - row := db.QueryRow("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash = ?", hash) - s, err := scanSong(row) - return s, err - -} - -func scanSongs(rows *sql.Rows) ([]Song, error) { - songs := []Song{} - for rows.Next() { - var s Song - if err := rows.Scan(&s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil { - return nil, err - } - - bm, err := parser.ParseOsuFile(fmt.Sprintf("%sSongs/%s/%s", osuRoot, s.Folder, s.File)) - if err != nil { - fmt.Println(err) - s.Image = "404.png" - } else { - if bgImage := bm.BackgroundImage(); bgImage != "" { - s.Image = fmt.Sprintf("%s/%s", s.Folder, bgImage) - } else { - s.Image = "404.png" - } - } - - songs = append(songs, s) - } - return songs, nil -} - -func scanSong(row *sql.Row) (Song, error) { - - s := Song{} - if err := row.Scan(&s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil { - return Song{}, err - } - - s.Image = extractImageFromFile(osuRoot, s.Folder, s.File) - - return s, nil -} - func extractImageFromFile(osuRoot, folder, file string) string { bm, err := parser.ParseOsuFile(filepath.Join(osuRoot, "Songs", folder, file)) if err != nil { @@ -586,67 +313,3 @@ func extractImageFromFile(osuRoot, folder, file string) string { return "404.png" } - -func scanCollections(rows *sql.Rows) ([]Collection, error) { - - var collection []Collection - for rows.Next() { - var c Collection - if err := rows.Scan(&c); err != nil { - return []Collection{}, err - } - collection = append(collection, c) - } - return collection, nil -} - -func scanCollectionPreviews(rows *sql.Rows) ([]CollectionPreview, error) { - - var collection []CollectionPreview - for rows.Next() { - var c CollectionPreview - if err := rows.Scan(&c); err != nil { - return []CollectionPreview{}, err - } - collection = append(collection, c) - } - return collection, nil -} - -func scanCollectionPreview(row *sql.Row) (CollectionPreview, error) { - - var c CollectionPreview - if err := row.Scan(&c); err != nil { - return CollectionPreview{}, err - } - return c, nil -} - -func convertToSong(r sqlcdb.GetRecentBeatmapsRow) Song { - return Song{ - BeatmapID: safeInt64(r.Beatmapid), - MD5Hash: safeString(r.Md5hash), - Title: safeString(r.Title), - Artist: safeString(r.Artist), - Creator: safeString(r.Creator), - Folder: safeString(r.Folder), - File: safeString(r.File), - Audio: safeString(r.Audio), - TotalTime: int64(safeInt64(r.Totaltime)), - Image: extractImageFromFile(osuRoot, safeString(r.Folder), safeString(r.File)), - } -} - -func safeString(ns sql.NullString) string { - if ns.Valid { - return ns.String - } - return "" -} - -func safeInt64(n sql.NullInt64) int { - if n.Valid { - return int(n.Int64) - } - return 0 -} diff --git a/grpc-backend/go.mod b/grpc-backend/go.mod index 8e79b34..e9d74ee 100644 --- a/grpc-backend/go.mod +++ b/grpc-backend/go.mod @@ -5,9 +5,9 @@ go 1.24.5 require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/joho/godotenv v1.5.1 - github.com/juli0n21/go-osu-parser v0.0.10 - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 - google.golang.org/grpc v1.73.0 + github.com/juli0n21/go-osu-parser v0.0.11 + google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 + google.golang.org/grpc v1.74.0 google.golang.org/protobuf v1.36.6 modernc.org/sqlite v1.38.0 ) @@ -26,7 +26,7 @@ require ( golang.org/x/net v0.42.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect modernc.org/libc v1.66.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/grpc-backend/go.sum b/grpc-backend/go.sum index a726585..33bfe5b 100644 --- a/grpc-backend/go.sum +++ b/grpc-backend/go.sum @@ -1,7 +1,7 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -16,8 +16,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+u github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/juli0n21/go-osu-parser v0.0.9 h1:3hx+ZtLKRJGdyJLhRbZzGyJ60Q3yRk9+tDJqwBJOZEg= -github.com/juli0n21/go-osu-parser v0.0.9/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0= +github.com/juli0n21/go-osu-parser v0.0.11 h1:p0kZc7zQ4YdmDO9/gor4zgvw2nfQPoDUtUXpV+hcOgU= +github.com/juli0n21/go-osu-parser v0.0.11/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0= 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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -26,16 +26,16 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= @@ -51,12 +51,12 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8= +google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY= +google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= diff --git a/grpc-backend/handlers.go b/grpc-backend/handlers.go index 6161890..7e05bc9 100644 --- a/grpc-backend/handlers.go +++ b/grpc-backend/handlers.go @@ -12,6 +12,7 @@ import ( v1 "backend/gen" "backend/internal/db" + sqlcdb "backend/internal/db" "github.com/joho/godotenv" "google.golang.org/grpc/codes" @@ -83,36 +84,39 @@ func (s *Server) Song(ctx context.Context, req *v1.SongRequest) (*v1.SongRespons return nil, status.Errorf(codes.InvalidArgument, "hash is required and cant be empty") } - song, err := getSong(s.Db, hash) + song, err := s.Sqlc.GetBeatmapByHash(ctx, sql.NullString{Valid: true, String: hash}) if err != nil { fmt.Println(err) return nil, status.Errorf(codes.NotFound, "beatmap not found by hash") } return &v1.SongResponse{ - Song: song.toProto(), + Song: toProtoSongSqlC(song), }, nil } func (s *Server) Recent(ctx context.Context, req *v1.RecentRequest) (*v1.RecentResponse, error) { - limit := defaultLimit(int(req.Limit)) offset := int(req.Offset) - recent, err := getRecent(context.Background(), s.Sqlc, limit, offset) + rows, err := s.Sqlc.GetRecentBeatmaps(ctx, sqlcdb.GetRecentBeatmapsParams{ + Limit: int64(limit), + Offset: int64(offset), + }) if err != nil { fmt.Println(err) return nil, status.Errorf(codes.Internal, "failed to get recents") } return &v1.RecentResponse{ - Songs: toProtoSongs(recent), + Songs: toProtoSongsSqlC(rows), }, nil } func (s *Server) Favorite(ctx context.Context, req *v1.FavoriteRequest) (*v1.FavoriteResponse, error) { + return nil, fmt.Errorf("not implemented!") - limit := defaultLimit(int(req.Limit)) + /*limit := defaultLimit(int(req.Limit)) offset := int(req.Offset) favorites, err := getFavorites(s.Db, req.Query, limit, offset) if err != nil { @@ -123,6 +127,7 @@ func (s *Server) Favorite(ctx context.Context, req *v1.FavoriteRequest) (*v1.Fav return &v1.FavoriteResponse{ Songs: toProtoSongs(favorites), }, nil + */ } func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v1.CollectionResponse, error) { @@ -132,29 +137,32 @@ func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v name := req.Name if name != "" { - c, err := getCollectionByName(s.Db, limit, offset, name) + c, err := s.Sqlc.GetCollectionByName(ctx, + sqlcdb.GetCollectionByNameParams{ + Name: sql.NullString{Valid: true, String: name}, + Limit: int64(limit), + Offset: int64(offset), + }, + ) if err != nil { fmt.Println(err) return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with name: %s", name)) } - return &v1.CollectionResponse{ - Songs: toProtoSongs(c.Songs), - Items: int32(c.Items), - Name: c.Name, - }, nil + + return toProtoCollectionSqlc(c), nil } - fmt.Println(limit, offset, req.Index) - c, err := getCollection(s.Db, limit, offset, int(req.Index)) + + c, err := s.Sqlc.GetCollectionByOffset(ctx, sqlcdb.GetCollectionByOffsetParams{ + Offset: int64(req.Index), + Limit: int64(limit), + Offset_2: int64(offset), + }) if err != nil { fmt.Println(err) return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with index: %d", req.Index)) } - fmt.Println(c) - return &v1.CollectionResponse{ - Songs: toProtoSongs(c.Songs), - Items: int32(c.Items), - Name: c.Name, - }, nil + + return toProtoCollectionoffsetSqlc(c), nil } @@ -163,38 +171,45 @@ func (s *Server) Search(ctx context.Context, req *v1.SearchSharedRequest) (*v1.S if q == "" { return nil, status.Error(codes.InvalidArgument, "query cant be empty") } + q = "%" + q + "%" limit := defaultLimit(int(req.Limit)) offset := int(req.Offset) - search, err := getSearch(s.Db, q, limit, offset) + search, err := s.Sqlc.SearchBeatmaps(ctx, sqlcdb.SearchBeatmapsParams{ + Title: sql.NullString{String: q, Valid: true}, + Artist: sql.NullString{String: q, Valid: true}, + Limit: int64(limit), + Offset: int64(offset), + }) if err != nil { fmt.Println(err) return nil, status.Error(codes.Internal, "failed to fetch search") } return &v1.SearchSharedResponse{ - Artist: search.Artist, - Songs: toProtoSongs(search.Songs), + Artist: "", + Songs: toProtoSongsSqlC(search), }, nil } func (s *Server) SearchCollections(ctx context.Context, req *v1.SearchCollectionRequest) (*v1.SearchCollectionResponse, error) { q := req.Query - - limit := defaultLimit(int(req.Limit)) + q = "%" + q + "%" + //limit := defaultLimit(int(req.Limit)) + limit := 10000 offset := int(req.Offset) - fmt.Println(req) - preview, err := getCollections(s.Db, q, limit, offset) + preview, err := s.Sqlc.SearchCollection(ctx, sqlcdb.SearchCollectionParams{ + Name: sql.NullString{String: q, Valid: true}, + Limit: int64(limit), + Offset: int64(offset), + }) if err != nil { fmt.Println(err) return nil, status.Errorf(codes.Internal, "failed to search for collections") } - - return &v1.SearchCollectionResponse{ - Collections: toProtoCollectionPreview(preview), - }, nil + return toProtoCollectionsSearchPreview(preview), nil } func (s *Server) SearchArtists(ctx context.Context, req *v1.SearchArtistRequest) (*v1.SearchArtistResponse, error) { @@ -206,13 +221,17 @@ func (s *Server) SearchArtists(ctx context.Context, req *v1.SearchArtistRequest) limit := defaultLimit(int(req.Limit)) offset := int(req.Offset) - a, err := getArtists(s.Db, q, limit, offset) + _, err := s.Sqlc.SearchArtists(ctx, sqlcdb.SearchArtistsParams{ + Artist: sql.NullString{Valid: true, String: q}, + Limit: int64(limit), + Offset: int64(offset), + }) if err != nil { fmt.Println(err) return nil, status.Error(codes.Internal, "failed to search artists") } return &v1.SearchArtistResponse{ - Artists: toProtoArtist(a), + Artists: nil, }, nil } diff --git a/grpc-backend/internal/db/beatmap.sql.go b/grpc-backend/internal/db/beatmap.sql.go index 3eaff36..b537d66 100644 --- a/grpc-backend/internal/db/beatmap.sql.go +++ b/grpc-backend/internal/db/beatmap.sql.go @@ -10,54 +10,6 @@ import ( "database/sql" ) -const getArtists = `-- name: GetArtists :many -SELECT Artist, COUNT(Artist) AS count -FROM Beatmap -WHERE Artist LIKE ? OR Title LIKE ? -GROUP BY Artist -LIMIT ? OFFSET ? -` - -type GetArtistsParams struct { - Artist sql.NullString `json:"artist"` - Title sql.NullString `json:"title"` - Limit int64 `json:"limit"` - Offset int64 `json:"offset"` -} - -type GetArtistsRow struct { - Artist sql.NullString `json:"artist"` - Count int64 `json:"count"` -} - -func (q *Queries) GetArtists(ctx context.Context, arg GetArtistsParams) ([]GetArtistsRow, error) { - rows, err := q.db.QueryContext(ctx, getArtists, - arg.Artist, - arg.Title, - arg.Limit, - arg.Offset, - ) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetArtistsRow{} - for rows.Next() { - var i GetArtistsRow - if err := rows.Scan(&i.Artist, &i.Count); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getBeatmapByHash = `-- name: GetBeatmapByHash :one SELECT beatmapid, artist, artistunicode, title, titleunicode, creator, difficulty, audio, md5hash, file, rankedstatus, lastmodifiedtime, totaltime, audiopreviewtime, beatmapsetid, source, tags, lastplayed, folder FROM Beatmap WHERE MD5Hash = ? ` @@ -100,8 +52,19 @@ func (q *Queries) GetBeatmapCount(ctx context.Context) (int64, error) { return count, err } +const getBeatmapSetCount = `-- name: GetBeatmapSetCount :one +SELECT COUNT(*) FROM Beatmap GROUP BY BeatmapSetId +` + +func (q *Queries) GetBeatmapSetCount(ctx context.Context) (int64, error) { + row := q.db.QueryRowContext(ctx, getBeatmapSetCount) + var count int64 + err := row.Scan(&count) + return count, err +} + const getRecentBeatmaps = `-- name: GetRecentBeatmaps :many -SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +SELECT beatmapid, artist, artistunicode, title, titleunicode, creator, difficulty, audio, md5hash, file, rankedstatus, lastmodifiedtime, totaltime, audiopreviewtime, beatmapsetid, source, tags, lastplayed, folder FROM Beatmap GROUP BY Folder ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ? ` @@ -110,37 +73,35 @@ type GetRecentBeatmapsParams struct { Offset int64 `json:"offset"` } -type GetRecentBeatmapsRow struct { - Beatmapid sql.NullInt64 `json:"beatmapid"` - Md5hash sql.NullString `json:"md5hash"` - Title sql.NullString `json:"title"` - Artist sql.NullString `json:"artist"` - Creator sql.NullString `json:"creator"` - Folder sql.NullString `json:"folder"` - File sql.NullString `json:"file"` - Audio sql.NullString `json:"audio"` - Totaltime sql.NullInt64 `json:"totaltime"` -} - -func (q *Queries) GetRecentBeatmaps(ctx context.Context, arg GetRecentBeatmapsParams) ([]GetRecentBeatmapsRow, error) { +func (q *Queries) GetRecentBeatmaps(ctx context.Context, arg GetRecentBeatmapsParams) ([]Beatmap, error) { rows, err := q.db.QueryContext(ctx, getRecentBeatmaps, arg.Limit, arg.Offset) if err != nil { return nil, err } defer rows.Close() - items := []GetRecentBeatmapsRow{} + items := []Beatmap{} for rows.Next() { - var i GetRecentBeatmapsRow + var i Beatmap if err := rows.Scan( &i.Beatmapid, - &i.Md5hash, - &i.Title, &i.Artist, + &i.Artistunicode, + &i.Title, + &i.Titleunicode, &i.Creator, - &i.Folder, - &i.File, + &i.Difficulty, &i.Audio, + &i.Md5hash, + &i.File, + &i.Rankedstatus, + &i.Lastmodifiedtime, &i.Totaltime, + &i.Audiopreviewtime, + &i.Beatmapsetid, + &i.Source, + &i.Tags, + &i.Lastplayed, + &i.Folder, ); err != nil { return nil, err } @@ -176,13 +137,13 @@ type InsertBeatmapParams struct { Md5hash sql.NullString `json:"md5hash"` File sql.NullString `json:"file"` Rankedstatus sql.NullString `json:"rankedstatus"` - Lastmodifiedtime sql.NullTime `json:"lastmodifiedtime"` + Lastmodifiedtime sql.NullInt64 `json:"lastmodifiedtime"` Totaltime sql.NullInt64 `json:"totaltime"` Audiopreviewtime sql.NullInt64 `json:"audiopreviewtime"` Beatmapsetid sql.NullInt64 `json:"beatmapsetid"` Source sql.NullString `json:"source"` Tags sql.NullString `json:"tags"` - Lastplayed sql.NullTime `json:"lastplayed"` + Lastplayed sql.NullInt64 `json:"lastplayed"` Folder sql.NullString `json:"folder"` } @@ -211,8 +172,56 @@ func (q *Queries) InsertBeatmap(ctx context.Context, arg InsertBeatmapParams) er return err } +const searchArtists = `-- name: SearchArtists :many +SELECT Artist, COUNT(Artist) AS count +FROM Beatmap +WHERE Artist LIKE ? OR Title LIKE ? +GROUP BY Artist +LIMIT ? OFFSET ? +` + +type SearchArtistsParams struct { + Artist sql.NullString `json:"artist"` + Title sql.NullString `json:"title"` + Limit int64 `json:"limit"` + Offset int64 `json:"offset"` +} + +type SearchArtistsRow struct { + Artist sql.NullString `json:"artist"` + Count int64 `json:"count"` +} + +func (q *Queries) SearchArtists(ctx context.Context, arg SearchArtistsParams) ([]SearchArtistsRow, error) { + rows, err := q.db.QueryContext(ctx, searchArtists, + arg.Artist, + arg.Title, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SearchArtistsRow{} + for rows.Next() { + var i SearchArtistsRow + if err := rows.Scan(&i.Artist, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const searchBeatmaps = `-- name: SearchBeatmaps :many -SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +SELECT beatmapid, artist, artistunicode, title, titleunicode, creator, difficulty, audio, md5hash, file, rankedstatus, lastmodifiedtime, totaltime, audiopreviewtime, beatmapsetid, source, tags, lastplayed, folder FROM Beatmap WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ? @@ -225,19 +234,7 @@ type SearchBeatmapsParams struct { Offset int64 `json:"offset"` } -type SearchBeatmapsRow struct { - Beatmapid sql.NullInt64 `json:"beatmapid"` - Md5hash sql.NullString `json:"md5hash"` - Title sql.NullString `json:"title"` - Artist sql.NullString `json:"artist"` - Creator sql.NullString `json:"creator"` - Folder sql.NullString `json:"folder"` - File sql.NullString `json:"file"` - Audio sql.NullString `json:"audio"` - Totaltime sql.NullInt64 `json:"totaltime"` -} - -func (q *Queries) SearchBeatmaps(ctx context.Context, arg SearchBeatmapsParams) ([]SearchBeatmapsRow, error) { +func (q *Queries) SearchBeatmaps(ctx context.Context, arg SearchBeatmapsParams) ([]Beatmap, error) { rows, err := q.db.QueryContext(ctx, searchBeatmaps, arg.Title, arg.Artist, @@ -248,19 +245,29 @@ func (q *Queries) SearchBeatmaps(ctx context.Context, arg SearchBeatmapsParams) return nil, err } defer rows.Close() - items := []SearchBeatmapsRow{} + items := []Beatmap{} for rows.Next() { - var i SearchBeatmapsRow + var i Beatmap if err := rows.Scan( &i.Beatmapid, - &i.Md5hash, - &i.Title, &i.Artist, + &i.Artistunicode, + &i.Title, + &i.Titleunicode, &i.Creator, - &i.Folder, - &i.File, + &i.Difficulty, &i.Audio, + &i.Md5hash, + &i.File, + &i.Rankedstatus, + &i.Lastmodifiedtime, &i.Totaltime, + &i.Audiopreviewtime, + &i.Beatmapsetid, + &i.Source, + &i.Tags, + &i.Lastplayed, + &i.Folder, ); err != nil { return nil, err } diff --git a/grpc-backend/internal/db/collection.sql.go b/grpc-backend/internal/db/collection.sql.go index eafc35f..681ec18 100644 --- a/grpc-backend/internal/db/collection.sql.go +++ b/grpc-backend/internal/db/collection.sql.go @@ -10,82 +10,6 @@ import ( "database/sql" ) -const getCollection = `-- name: GetCollection :many -SELECT - c.Name, - b.BeatmapId, - b.MD5Hash, - b.Title, - b.Artist, - b.Creator, - b.Folder, - b.File, - b.Audio, - b.TotalTime -FROM Collection c -JOIN Beatmap b ON c.MD5Hash = b.MD5Hash -JOIN ( - SELECT DISTINCT Name - FROM Collection - ORDER BY Name - LIMIT 1 OFFSET ? -) selected_name ON c.Name = selected_name.Name -LIMIT ? OFFSET ? -` - -type GetCollectionParams struct { - Offset int64 `json:"offset"` - Limit int64 `json:"limit"` - Offset_2 int64 `json:"offset_2"` -} - -type GetCollectionRow struct { - Name sql.NullString `json:"name"` - Beatmapid sql.NullInt64 `json:"beatmapid"` - Md5hash sql.NullString `json:"md5hash"` - Title sql.NullString `json:"title"` - Artist sql.NullString `json:"artist"` - Creator sql.NullString `json:"creator"` - Folder sql.NullString `json:"folder"` - File sql.NullString `json:"file"` - Audio sql.NullString `json:"audio"` - Totaltime sql.NullInt64 `json:"totaltime"` -} - -func (q *Queries) GetCollection(ctx context.Context, arg GetCollectionParams) ([]GetCollectionRow, error) { - rows, err := q.db.QueryContext(ctx, getCollection, arg.Offset, arg.Limit, arg.Offset_2) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetCollectionRow{} - for rows.Next() { - var i GetCollectionRow - if err := rows.Scan( - &i.Name, - &i.Beatmapid, - &i.Md5hash, - &i.Title, - &i.Artist, - &i.Creator, - &i.Folder, - &i.File, - &i.Audio, - &i.Totaltime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getCollectionByName = `-- name: GetCollectionByName :many SELECT c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist, b.Creator, b.Folder, b.File, b.Audio, b.TotalTime FROM Collection c @@ -147,6 +71,83 @@ func (q *Queries) GetCollectionByName(ctx context.Context, arg GetCollectionByNa return items, nil } +const getCollectionByOffset = `-- name: GetCollectionByOffset :many +SELECT + c.Name, + b.BeatmapId, + b.MD5Hash, + b.Title, + b.Artist, + b.Creator, + b.Folder, + b.File, + b.Audio, + b.TotalTime +FROM Collection c +JOIN Beatmap b ON c.MD5Hash = b.MD5Hash +WHERE c.Name = ( + SELECT Name + FROM Collection + GROUP BY Name + ORDER BY Name + LIMIT 1 OFFSET ?1 +) +LIMIT ?2 OFFSET ?3 +` + +type GetCollectionByOffsetParams struct { + Offset int64 `json:"offset"` + Limit int64 `json:"limit"` + Offset_2 int64 `json:"offset_2"` +} + +type GetCollectionByOffsetRow struct { + Name sql.NullString `json:"name"` + Beatmapid sql.NullInt64 `json:"beatmapid"` + Md5hash sql.NullString `json:"md5hash"` + Title sql.NullString `json:"title"` + Artist sql.NullString `json:"artist"` + Creator sql.NullString `json:"creator"` + Folder sql.NullString `json:"folder"` + File sql.NullString `json:"file"` + Audio sql.NullString `json:"audio"` + Totaltime sql.NullInt64 `json:"totaltime"` +} + +func (q *Queries) GetCollectionByOffset(ctx context.Context, arg GetCollectionByOffsetParams) ([]GetCollectionByOffsetRow, error) { + rows, err := q.db.QueryContext(ctx, getCollectionByOffset, arg.Offset, arg.Limit, arg.Offset_2) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetCollectionByOffsetRow{} + for rows.Next() { + var i GetCollectionByOffsetRow + if err := rows.Scan( + &i.Name, + &i.Beatmapid, + &i.Md5hash, + &i.Title, + &i.Artist, + &i.Creator, + &i.Folder, + &i.File, + &i.Audio, + &i.Totaltime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getCollectionCountByName = `-- name: GetCollectionCountByName :one SELECT COUNT(*) FROM Collection WHERE Name = ? ` @@ -158,37 +159,55 @@ func (q *Queries) GetCollectionCountByName(ctx context.Context, name sql.NullStr return count, err } -const getCollections = `-- name: GetCollections :many -SELECT c.Name, COUNT(b.MD5Hash) AS Count, MIN(b.Folder) AS Folder, MIN(b.File) AS File -FROM Collection c +const insertCollection = `-- name: InsertCollection :exec +INSERT INTO Collection (Name, MD5Hash) VALUES (?, ?) +` + +type InsertCollectionParams struct { + Name sql.NullString `json:"name"` + Md5hash sql.NullString `json:"md5hash"` +} + +func (q *Queries) InsertCollection(ctx context.Context, arg InsertCollectionParams) error { + _, err := q.db.ExecContext(ctx, insertCollection, arg.Name, arg.Md5hash) + return err +} + +const searchCollection = `-- name: SearchCollection :many +SELECT + c.Name, + COUNT(b.MD5Hash) AS Count, + b.Folder, + b.File + FROM Collection c JOIN Beatmap b ON c.MD5Hash = b.MD5Hash WHERE c.Name LIKE ? GROUP BY c.Name LIMIT ? OFFSET ? ` -type GetCollectionsParams struct { +type SearchCollectionParams struct { Name sql.NullString `json:"name"` Limit int64 `json:"limit"` Offset int64 `json:"offset"` } -type GetCollectionsRow struct { +type SearchCollectionRow struct { Name sql.NullString `json:"name"` Count int64 `json:"count"` - Folder interface{} `json:"folder"` - File interface{} `json:"file"` + Folder sql.NullString `json:"folder"` + File sql.NullString `json:"file"` } -func (q *Queries) GetCollections(ctx context.Context, arg GetCollectionsParams) ([]GetCollectionsRow, error) { - rows, err := q.db.QueryContext(ctx, getCollections, arg.Name, arg.Limit, arg.Offset) +func (q *Queries) SearchCollection(ctx context.Context, arg SearchCollectionParams) ([]SearchCollectionRow, error) { + rows, err := q.db.QueryContext(ctx, searchCollection, arg.Name, arg.Limit, arg.Offset) if err != nil { return nil, err } defer rows.Close() - items := []GetCollectionsRow{} + items := []SearchCollectionRow{} for rows.Next() { - var i GetCollectionsRow + var i SearchCollectionRow if err := rows.Scan( &i.Name, &i.Count, @@ -207,17 +226,3 @@ func (q *Queries) GetCollections(ctx context.Context, arg GetCollectionsParams) } return items, nil } - -const insertCollection = `-- name: InsertCollection :exec -INSERT INTO Collection (Name, MD5Hash) VALUES (?, ?) -` - -type InsertCollectionParams struct { - Name sql.NullString `json:"name"` - Md5hash sql.NullString `json:"md5hash"` -} - -func (q *Queries) InsertCollection(ctx context.Context, arg InsertCollectionParams) error { - _, err := q.db.ExecContext(ctx, insertCollection, arg.Name, arg.Md5hash) - return err -} diff --git a/grpc-backend/internal/db/models.go b/grpc-backend/internal/db/models.go index 91bd3ec..855c938 100644 --- a/grpc-backend/internal/db/models.go +++ b/grpc-backend/internal/db/models.go @@ -20,13 +20,13 @@ type Beatmap struct { Md5hash sql.NullString `json:"md5hash"` File sql.NullString `json:"file"` Rankedstatus sql.NullString `json:"rankedstatus"` - Lastmodifiedtime sql.NullTime `json:"lastmodifiedtime"` + Lastmodifiedtime sql.NullInt64 `json:"lastmodifiedtime"` Totaltime sql.NullInt64 `json:"totaltime"` Audiopreviewtime sql.NullInt64 `json:"audiopreviewtime"` Beatmapsetid sql.NullInt64 `json:"beatmapsetid"` Source sql.NullString `json:"source"` Tags sql.NullString `json:"tags"` - Lastplayed sql.NullTime `json:"lastplayed"` + Lastplayed sql.NullInt64 `json:"lastplayed"` Folder sql.NullString `json:"folder"` } diff --git a/grpc-backend/internal/db/querier.go b/grpc-backend/internal/db/querier.go index 46c86fd..7fc7c1a 100644 --- a/grpc-backend/internal/db/querier.go +++ b/grpc-backend/internal/db/querier.go @@ -10,17 +10,18 @@ import ( ) type Querier interface { - GetArtists(ctx context.Context, arg GetArtistsParams) ([]GetArtistsRow, error) GetBeatmapByHash(ctx context.Context, md5hash sql.NullString) (Beatmap, error) GetBeatmapCount(ctx context.Context) (int64, error) - GetCollection(ctx context.Context, arg GetCollectionParams) ([]GetCollectionRow, error) + GetBeatmapSetCount(ctx context.Context) (int64, error) GetCollectionByName(ctx context.Context, arg GetCollectionByNameParams) ([]GetCollectionByNameRow, error) + GetCollectionByOffset(ctx context.Context, arg GetCollectionByOffsetParams) ([]GetCollectionByOffsetRow, error) GetCollectionCountByName(ctx context.Context, name sql.NullString) (int64, error) - GetCollections(ctx context.Context, arg GetCollectionsParams) ([]GetCollectionsRow, error) - GetRecentBeatmaps(ctx context.Context, arg GetRecentBeatmapsParams) ([]GetRecentBeatmapsRow, error) + GetRecentBeatmaps(ctx context.Context, arg GetRecentBeatmapsParams) ([]Beatmap, error) InsertBeatmap(ctx context.Context, arg InsertBeatmapParams) error InsertCollection(ctx context.Context, arg InsertCollectionParams) error - SearchBeatmaps(ctx context.Context, arg SearchBeatmapsParams) ([]SearchBeatmapsRow, error) + SearchArtists(ctx context.Context, arg SearchArtistsParams) ([]SearchArtistsRow, error) + SearchBeatmaps(ctx context.Context, arg SearchBeatmapsParams) ([]Beatmap, error) + SearchCollection(ctx context.Context, arg SearchCollectionParams) ([]SearchCollectionRow, error) } var _ Querier = (*Queries)(nil) diff --git a/grpc-backend/models.go b/grpc-backend/models.go deleted file mode 100644 index ffad2cd..0000000 --- a/grpc-backend/models.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - v1 "backend/gen" - "backend/internal/db" - "fmt" -) - -type Song struct { - BeatmapID int `json:"beatmap_id" example:"123456"` - MD5Hash string `json:"md5_hash" example:"abcd1234efgh5678"` - Title string `json:"title" example:"Shape of You"` - Artist string `json:"artist" example:"Ed Sheeran"` - Creator string `json:"creator" example:"JohnDoe"` - Folder string `json:"folder" example:"osu/Songs/123456"` - File string `json:"file" example:"beatmap.osu"` - Audio string `json:"audio" example:"audio.mp3"` - TotalTime int64 `json:"total_time" example:"240"` - Image string `json:"image" example:"cover.jpg"` -} - -func (song Song) toProto() *v1.Song { - return &v1.Song{ - BeatmapId: int32(song.BeatmapID), - Md5Hash: song.MD5Hash, - Title: song.Title, - Artist: song.Artist, - Creator: song.Creator, - Folder: song.Folder, - File: song.File, - Audio: song.Audio, - TotalTime: song.TotalTime, - Image: song.Image, - } -} - -// The function `toSongProto` converts a `db.Beatmap` struct into a `v1.Song` struct by mapping the -// fields and performing some data type conversions. -func toSongProto(song db.Beatmap) *v1.Song { - fmt.Println(song) - return &v1.Song{ - BeatmapId: int32(safeInt64(song.Beatmapid)), - Md5Hash: safeString(song.Md5hash), - Title: safeString(song.Title), - Artist: safeString(song.Artist), - Creator: safeString(song.Creator), - Folder: safeString(song.Folder), - File: safeString(song.File), - Audio: safeString(song.Audio), - TotalTime: int64(safeInt64(song.Totaltime)), - Image: extractImageFromFile(osuRoot, safeString(song.Folder), safeString(song.File)), - } -} - -func toProtoSongs(local []Song) []*v1.Song { - songs := make([]*v1.Song, len(local)) - for i, s := range local { - songs[i] = s.toProto() - } - - return songs -} - -// CollectionPreview represents a preview of a song collection -// @Description CollectionPreview contains summary data of a song collection -type CollectionPreview struct { - Name string `json:"name" example:"Collection Name"` - Image string `json:"image" example:"cover.jpg"` - Items int `json:"items" example:"10"` -} - -func (c CollectionPreview) toProto() *v1.CollectionPreview { - return &v1.CollectionPreview{ - Name: c.Name, - Image: c.Image, - Items: int32(c.Items), - } -} - -func toProtoCollectionPreview(local []CollectionPreview) []*v1.CollectionPreview { - collection := make([]*v1.CollectionPreview, len(local)) - for i, c := range local { - collection[i] = c.toProto() - } - - return collection -} - -// Collection represents a full song collection -// @Description Collection holds a list of songs -type Collection struct { - Name string `json:"name" example:"Best of 2023"` - Items int `json:"items" example:"15"` - Songs []Song `json:"songs"` -} - -// ActiveSearch represents an active song search query -// @Description ActiveSearch holds search results for a given artist -type ActiveSearch struct { - Artist string `json:"artist" example:"Ed Sheeran"` - Songs []Song `json:"songs"` -} - -// Artist represents an active song search query -// @Description Artist holds search results for a given artist -type Artist struct { - Artist string `json:"artist" example:"Miku"` - Count int `json:"count" example:"21"` -} - -func (a Artist) toProto() *v1.Artist { - return &v1.Artist{ - Artist: a.Artist, - Items: int32(a.Count), - } -} - -func toProtoArtist(local []Artist) []*v1.Artist { - artists := make([]*v1.Artist, len(local)) - for i, a := range local { - artists[i] = a.toProto() - } - - return artists -} diff --git a/grpc-backend/sqlc.yaml b/grpc-backend/sqlc.yaml index a833650..b234974 100644 --- a/grpc-backend/sqlc.yaml +++ b/grpc-backend/sqlc.yaml @@ -12,3 +12,6 @@ sql: emit_json_tags: true emit_interface: true emit_empty_slices: true + overrides: + - db_type: "INTEGER" + go_type: "int" \ No newline at end of file diff --git a/grpc-backend/sqlc/query/beatmap.sql b/grpc-backend/sqlc/query/beatmap.sql index 8deb2c5..2a1214d 100644 --- a/grpc-backend/sqlc/query/beatmap.sql +++ b/grpc-backend/sqlc/query/beatmap.sql @@ -12,17 +12,20 @@ SELECT * FROM Beatmap WHERE MD5Hash = ?; -- name: GetBeatmapCount :one SELECT COUNT(*) FROM Beatmap; +-- name: GetBeatmapSetCount :one +SELECT COUNT(*) FROM Beatmap GROUP BY BeatmapSetId; + -- name: GetRecentBeatmaps :many -SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +SELECT * FROM Beatmap GROUP BY Folder ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ?; -- name: SearchBeatmaps :many -SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +SELECT * FROM Beatmap WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?; --- name: GetArtists :many +-- name: SearchArtists :many SELECT Artist, COUNT(Artist) AS count FROM Beatmap WHERE Artist LIKE ? OR Title LIKE ? diff --git a/grpc-backend/sqlc/query/collection.sql b/grpc-backend/sqlc/query/collection.sql index 6a1f47e..0d74997 100644 --- a/grpc-backend/sqlc/query/collection.sql +++ b/grpc-backend/sqlc/query/collection.sql @@ -4,15 +4,19 @@ INSERT INTO Collection (Name, MD5Hash) VALUES (?, ?); -- name: GetCollectionCountByName :one SELECT COUNT(*) FROM Collection WHERE Name = ?; --- name: GetCollections :many -SELECT c.Name, COUNT(b.MD5Hash) AS Count, MIN(b.Folder) AS Folder, MIN(b.File) AS File -FROM Collection c +-- name: SearchCollection :many +SELECT + c.Name, + COUNT(b.MD5Hash) AS Count, + b.Folder, + b.File + FROM Collection c JOIN Beatmap b ON c.MD5Hash = b.MD5Hash WHERE c.Name LIKE ? GROUP BY c.Name LIMIT ? OFFSET ?; --- name: GetCollection :many +-- name: GetCollectionByOffset :many SELECT c.Name, b.BeatmapId, @@ -26,13 +30,14 @@ SELECT b.TotalTime FROM Collection c JOIN Beatmap b ON c.MD5Hash = b.MD5Hash -JOIN ( - SELECT DISTINCT Name +WHERE c.Name = ( + SELECT Name FROM Collection + GROUP BY Name ORDER BY Name - LIMIT 1 OFFSET ? -) selected_name ON c.Name = selected_name.Name -LIMIT ? OFFSET ?; + LIMIT 1 OFFSET ?1 +) +LIMIT ?2 OFFSET ?3; -- name: GetCollectionByName :many SELECT c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist, b.Creator, b.Folder, b.File, b.Audio, b.TotalTime diff --git a/grpc-backend/sqlc/schema/schema.sql b/grpc-backend/sqlc/schema/schema.sql index 49033ff..286ecad 100644 --- a/grpc-backend/sqlc/schema/schema.sql +++ b/grpc-backend/sqlc/schema/schema.sql @@ -11,13 +11,13 @@ CREATE TABLE IF NOT EXISTS Beatmap ( MD5Hash TEXT DEFAULT '00000000000000000000000000000000', File TEXT DEFAULT 'unknown.osu', RankedStatus TEXT DEFAULT 'Unknown', - LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00', + LastModifiedTime INTEGER DEFAULT 0, TotalTime INTEGER DEFAULT 0, AudioPreviewTime INTEGER DEFAULT 0, BeatmapSetId INTEGER DEFAULT -1, Source TEXT DEFAULT '', Tags TEXT DEFAULT '', - LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00', + LastPlayed INTEGER DEFAULT 0, Folder TEXT DEFAULT 'Unknown Folder', UNIQUE (Artist, Title, MD5Hash, Difficulty) ); diff --git a/grpc-backend/sqlc_converters.go b/grpc-backend/sqlc_converters.go new file mode 100644 index 0000000..4baef5d --- /dev/null +++ b/grpc-backend/sqlc_converters.go @@ -0,0 +1,96 @@ +package main + +import ( + v1 "backend/gen" + "backend/internal/db" +) + +func toProtoSongsSqlC(songs []db.Beatmap) []*v1.Song { + protoSongs := make([]*v1.Song, len(songs)) + for i, s := range songs { + protoSongs[i] = toProtoSongSqlC(s) + } + + return protoSongs +} + +func toProtoSongSqlC(song db.Beatmap) *v1.Song { + return &v1.Song{ + BeatmapId: int32(song.Beatmapid.Int64), + Md5Hash: song.Md5hash.String, + Title: song.Title.String, + Artist: song.Artist.String, + Creator: song.Creator.String, + Folder: song.Folder.String, + File: song.File.String, + Audio: song.Audio.String, + TotalTime: song.Totaltime.Int64, + Image: extractImageFromFile(osuRoot, song.Folder.String, song.File.String), + } +} + +func toProtoCollectionSqlc(rows []db.GetCollectionByNameRow) *v1.CollectionResponse { + songs := make([]*v1.Song, len(rows)) + + for i, b := range rows { + songs[i] = toProtoSongSqlC(db.Beatmap{ + Beatmapid: b.Beatmapid, + Md5hash: b.Md5hash, + Title: b.Title, + Artist: b.Artist, + Creator: b.Creator, + Folder: b.Folder, + File: b.File, + Audio: b.Audio, + Totaltime: b.Totaltime, + }) + } + + return &v1.CollectionResponse{ + Name: rows[0].Name.String, + Items: int32(len(rows)), + Songs: songs, + } +} + +func toProtoCollectionoffsetSqlc(rows []db.GetCollectionByOffsetRow) *v1.CollectionResponse { + songs := make([]*v1.Song, len(rows)) + + for i, b := range rows { + songs[i] = toProtoSongSqlC(db.Beatmap{ + Beatmapid: b.Beatmapid, + Md5hash: b.Md5hash, + Title: b.Title, + Artist: b.Artist, + Creator: b.Creator, + Folder: b.Folder, + File: b.File, + Audio: b.Audio, + Totaltime: b.Totaltime, + }) + } + + return &v1.CollectionResponse{ + Name: rows[0].Name.String, + Items: int32(len(rows)), + Songs: songs, + } +} + +func toProtoCollectionsSearchPreview(rows []db.SearchCollectionRow) *v1.SearchCollectionResponse { + + collection := make([]*v1.CollectionPreview, len(rows)) + for i, c := range rows { + collection[i] = toProtoCollectionPreviewSqlc(c) + } + + return &v1.SearchCollectionResponse{Collections: collection} +} + +func toProtoCollectionPreviewSqlc(row db.SearchCollectionRow) *v1.CollectionPreview { + return &v1.CollectionPreview{ + Name: row.Name.String, + Items: int32(row.Count), + Image: extractImageFromFile(osuRoot, row.Folder.String, row.File.String), + } +}