diff --git a/grpc-backend/database.go b/grpc-backend/database.go index 671887d..7e7ea82 100644 --- a/grpc-backend/database.go +++ b/grpc-backend/database.go @@ -1,6 +1,8 @@ package main import ( + sqlcdb "backend/internal/db" + "context" "database/sql" "errors" "fmt" @@ -9,7 +11,6 @@ import ( "path" "path/filepath" "strconv" - "strings" "github.com/juli0n21/go-osu-parser/parser" _ "modernc.org/sqlite" @@ -20,7 +21,7 @@ var ErrBeatmapCountNotMatch = errors.New("beatmap count not matching") var osuDB *parser.OsuDB var osuRoot string -func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql.DB, error) { +func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql.DB, *sqlcdb.Queries, error) { osuDB = osuDb osuRoot = osuroot @@ -30,13 +31,13 @@ func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql. if _, err := os.Stat(dir); os.IsNotExist(err) { err = os.MkdirAll(dir, 0755) if err != nil { - return nil, fmt.Errorf("failed to create directory %s: %v", dir, err) + return nil, nil, fmt.Errorf("failed to create directory %s: %v", dir, err) } } db, err := sql.Open("sqlite", connectionString) if err != nil { - return nil, fmt.Errorf("failed to open SQLite database %s: %v", connectionString, err) + return nil, nil, fmt.Errorf("failed to open SQLite database %s: %v", connectionString, err) } _, err = db.Exec("PRAGMA temp_store = MEMORY;") @@ -45,31 +46,33 @@ func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql. } if err = createDB(db); err != nil { - return nil, err + return nil, nil, err } if err = checkhealth(db, osuDB); err != nil { if err = rebuildBeatmapDb(db, osuDB); err != nil { - return nil, err + return nil, nil, err } } if err = createCollectionDB(db); err != nil { - return nil, err + return nil, nil, err } collectionDB, err := parser.ParseCollectionsDB(path.Join(osuRoot, "collection.db")) if err != nil { - return nil, err + return nil, nil, err } if err = checkCollectionHealth(db, collectionDB); err != nil { if err = rebuildCollectionDb(db, collectionDB); err != nil { - return nil, err + return nil, nil, err } } - return db, nil + sqlcQueries := sqlcdb.New(db) + + return db, sqlcQueries, nil } func createDB(db *sql.DB) error { @@ -329,14 +332,20 @@ func getBeatmapCount(db *sql.DB) int { return count } -func getRecent(db *sql.DB, limit, offset int) ([]Song, error) { - rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap GROUP BY Folder ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ?", limit, offset) +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 []Song{}, err + return nil, err } - defer rows.Close() - return scanSongs(rows) + 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) { @@ -531,16 +540,18 @@ func scanSongs(rows *sql.Rows) ([]Song, error) { 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 []Song{}, err + 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 = fmt.Sprintf("404.png") + s.Image = "404.png" } else { - if len(bm.Events) > 1 && len(bm.Events[0].EventParams) > 1 { - s.Image = fmt.Sprintf("%s/%s", s.Folder, strings.Trim(bm.Events[0].EventParams[0], "\"")) + if bgImage := bm.BackgroundImage(); bgImage != "" { + s.Image = fmt.Sprintf("%s/%s", s.Folder, bgImage) + } else { + s.Image = "404.png" } } @@ -568,12 +579,9 @@ func extractImageFromFile(osuRoot, folder, file string) string { return "404.png" } - if bm.Version > 3 { - if len(bm.Events) > 0 && len(bm.Events[0].EventParams) > 0 { - return fmt.Sprintf(`%s/%s`, folder, strings.Trim(bm.Events[0].EventParams[0], "\"")) - } - } else { - fmt.Println(bm.Events) + bgImage := bm.BackgroundImage() + if bgImage != "" { + return filepath.Join(folder, bgImage) } return "404.png" @@ -613,3 +621,32 @@ func scanCollectionPreview(row *sql.Row) (CollectionPreview, error) { } 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 ccacb9f..8e79b34 100644 --- a/grpc-backend/go.mod +++ b/grpc-backend/go.mod @@ -1,31 +1,33 @@ module backend -go 1.23.5 - -toolchain go1.24.2 +go 1.24.5 require ( - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 + 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.8 - google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 - google.golang.org/grpc v1.72.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 google.golang.org/protobuf v1.36.6 - modernc.org/sqlite v1.37.0 + modernc.org/sqlite v1.38.0 ) +//replace github.com/juli0n21/go-osu-parser => G:\Projects\go-osu-parser + +//require github.com/juli0n21/go-osu-parser v0.0.0-00010101000000-000000000000 + require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect - modernc.org/libc v1.62.1 // indirect + golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect + 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 + modernc.org/libc v1.66.3 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.9.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/grpc-backend/go.sum b/grpc-backend/go.sum index 78b9bdb..a726585 100644 --- a/grpc-backend/go.sum +++ b/grpc-backend/go.sum @@ -12,12 +12,12 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +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.8 h1:aQtuhAniGvpUw446arhq/3aUOK9YvZEkL7aYUGlViAo= -github.com/juli0n21/go-osu-parser v0.0.8/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0= +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/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,59 +26,61 @@ 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.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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= +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= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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/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.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= -modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= -modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= +modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= +modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= -modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= -modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= -modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= +modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/grpc-backend/handlers.go b/grpc-backend/handlers.go index 683137b..6161890 100644 --- a/grpc-backend/handlers.go +++ b/grpc-backend/handlers.go @@ -11,6 +11,7 @@ import ( "os" v1 "backend/gen" + "backend/internal/db" "github.com/joho/godotenv" "google.golang.org/grpc/codes" @@ -29,6 +30,7 @@ type Server struct { Db *sql.DB OsuDb *parser.OsuDB Env map[string]string + Sqlc *db.Queries v1.UnimplementedMusicBackendServer } @@ -97,7 +99,7 @@ func (s *Server) Recent(ctx context.Context, req *v1.RecentRequest) (*v1.RecentR limit := defaultLimit(int(req.Limit)) offset := int(req.Offset) - recent, err := getRecent(s.Db, limit, offset) + recent, err := getRecent(context.Background(), s.Sqlc, limit, offset) if err != nil { fmt.Println(err) return nil, status.Errorf(codes.Internal, "failed to get recents") diff --git a/grpc-backend/internal/db/beatmap.sql.go b/grpc-backend/internal/db/beatmap.sql.go new file mode 100644 index 0000000..3eaff36 --- /dev/null +++ b/grpc-backend/internal/db/beatmap.sql.go @@ -0,0 +1,276 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: beatmap.sql + +package db + +import ( + "context" + "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 = ? +` + +func (q *Queries) GetBeatmapByHash(ctx context.Context, md5hash sql.NullString) (Beatmap, error) { + row := q.db.QueryRowContext(ctx, getBeatmapByHash, md5hash) + var i Beatmap + err := row.Scan( + &i.Beatmapid, + &i.Artist, + &i.Artistunicode, + &i.Title, + &i.Titleunicode, + &i.Creator, + &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, + ) + return i, err +} + +const getBeatmapCount = `-- name: GetBeatmapCount :one +SELECT COUNT(*) FROM Beatmap +` + +func (q *Queries) GetBeatmapCount(ctx context.Context) (int64, error) { + row := q.db.QueryRowContext(ctx, getBeatmapCount) + 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 +FROM Beatmap GROUP BY Folder ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ? +` + +type GetRecentBeatmapsParams struct { + Limit int64 `json:"limit"` + 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) { + rows, err := q.db.QueryContext(ctx, getRecentBeatmaps, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetRecentBeatmapsRow{} + for rows.Next() { + var i GetRecentBeatmapsRow + if err := rows.Scan( + &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 insertBeatmap = `-- name: InsertBeatmap :exec +INSERT INTO Beatmap ( + BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator, + Difficulty, Audio, MD5Hash, File, RankedStatus, + LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId, + Source, Tags, LastPlayed, Folder +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +` + +type InsertBeatmapParams struct { + Beatmapid sql.NullInt64 `json:"beatmapid"` + Artist sql.NullString `json:"artist"` + Artistunicode sql.NullString `json:"artistunicode"` + Title sql.NullString `json:"title"` + Titleunicode sql.NullString `json:"titleunicode"` + Creator sql.NullString `json:"creator"` + Difficulty sql.NullString `json:"difficulty"` + Audio sql.NullString `json:"audio"` + Md5hash sql.NullString `json:"md5hash"` + File sql.NullString `json:"file"` + Rankedstatus sql.NullString `json:"rankedstatus"` + Lastmodifiedtime sql.NullTime `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"` + Folder sql.NullString `json:"folder"` +} + +func (q *Queries) InsertBeatmap(ctx context.Context, arg InsertBeatmapParams) error { + _, err := q.db.ExecContext(ctx, insertBeatmap, + arg.Beatmapid, + arg.Artist, + arg.Artistunicode, + arg.Title, + arg.Titleunicode, + arg.Creator, + arg.Difficulty, + arg.Audio, + arg.Md5hash, + arg.File, + arg.Rankedstatus, + arg.Lastmodifiedtime, + arg.Totaltime, + arg.Audiopreviewtime, + arg.Beatmapsetid, + arg.Source, + arg.Tags, + arg.Lastplayed, + arg.Folder, + ) + return err +} + +const searchBeatmaps = `-- name: SearchBeatmaps :many +SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +FROM Beatmap +WHERE Title LIKE ? OR Artist LIKE ? +LIMIT ? OFFSET ? +` + +type SearchBeatmapsParams struct { + Title sql.NullString `json:"title"` + Artist sql.NullString `json:"artist"` + Limit int64 `json:"limit"` + 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) { + rows, err := q.db.QueryContext(ctx, searchBeatmaps, + arg.Title, + arg.Artist, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SearchBeatmapsRow{} + for rows.Next() { + var i SearchBeatmapsRow + if err := rows.Scan( + &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 +} diff --git a/grpc-backend/internal/db/collection.sql.go b/grpc-backend/internal/db/collection.sql.go new file mode 100644 index 0000000..eafc35f --- /dev/null +++ b/grpc-backend/internal/db/collection.sql.go @@ -0,0 +1,223 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: collection.sql + +package db + +import ( + "context" + "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 +JOIN Beatmap b ON c.MD5Hash = b.MD5Hash +WHERE c.Name = ? +LIMIT ? OFFSET ? +` + +type GetCollectionByNameParams struct { + Name sql.NullString `json:"name"` + Limit int64 `json:"limit"` + Offset int64 `json:"offset"` +} + +type GetCollectionByNameRow 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) GetCollectionByName(ctx context.Context, arg GetCollectionByNameParams) ([]GetCollectionByNameRow, error) { + rows, err := q.db.QueryContext(ctx, getCollectionByName, arg.Name, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetCollectionByNameRow{} + for rows.Next() { + var i GetCollectionByNameRow + 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 = ? +` + +func (q *Queries) GetCollectionCountByName(ctx context.Context, name sql.NullString) (int64, error) { + row := q.db.QueryRowContext(ctx, getCollectionCountByName, name) + var count int64 + err := row.Scan(&count) + 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 +JOIN Beatmap b ON c.MD5Hash = b.MD5Hash +WHERE c.Name LIKE ? +GROUP BY c.Name +LIMIT ? OFFSET ? +` + +type GetCollectionsParams struct { + Name sql.NullString `json:"name"` + Limit int64 `json:"limit"` + Offset int64 `json:"offset"` +} + +type GetCollectionsRow struct { + Name sql.NullString `json:"name"` + Count int64 `json:"count"` + Folder interface{} `json:"folder"` + File interface{} `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) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetCollectionsRow{} + for rows.Next() { + var i GetCollectionsRow + if err := rows.Scan( + &i.Name, + &i.Count, + &i.Folder, + &i.File, + ); 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 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/db.go b/grpc-backend/internal/db/db.go new file mode 100644 index 0000000..0c56c2b --- /dev/null +++ b/grpc-backend/internal/db/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package db + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/grpc-backend/internal/db/models.go b/grpc-backend/internal/db/models.go new file mode 100644 index 0000000..91bd3ec --- /dev/null +++ b/grpc-backend/internal/db/models.go @@ -0,0 +1,36 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package db + +import ( + "database/sql" +) + +type Beatmap struct { + Beatmapid sql.NullInt64 `json:"beatmapid"` + Artist sql.NullString `json:"artist"` + Artistunicode sql.NullString `json:"artistunicode"` + Title sql.NullString `json:"title"` + Titleunicode sql.NullString `json:"titleunicode"` + Creator sql.NullString `json:"creator"` + Difficulty sql.NullString `json:"difficulty"` + Audio sql.NullString `json:"audio"` + Md5hash sql.NullString `json:"md5hash"` + File sql.NullString `json:"file"` + Rankedstatus sql.NullString `json:"rankedstatus"` + Lastmodifiedtime sql.NullTime `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"` + Folder sql.NullString `json:"folder"` +} + +type Collection struct { + Name sql.NullString `json:"name"` + Md5hash sql.NullString `json:"md5hash"` +} diff --git a/grpc-backend/internal/db/querier.go b/grpc-backend/internal/db/querier.go new file mode 100644 index 0000000..46c86fd --- /dev/null +++ b/grpc-backend/internal/db/querier.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package db + +import ( + "context" + "database/sql" +) + +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) + GetCollectionByName(ctx context.Context, arg GetCollectionByNameParams) ([]GetCollectionByNameRow, 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) + InsertBeatmap(ctx context.Context, arg InsertBeatmapParams) error + InsertCollection(ctx context.Context, arg InsertCollectionParams) error + SearchBeatmaps(ctx context.Context, arg SearchBeatmapsParams) ([]SearchBeatmapsRow, error) +} + +var _ Querier = (*Queries)(nil) diff --git a/grpc-backend/main.go b/grpc-backend/main.go index ee83c16..9b1f1ea 100644 --- a/grpc-backend/main.go +++ b/grpc-backend/main.go @@ -66,7 +66,7 @@ func main() { log.Fatalf("Couldnt Update Endpoint url with Proxy: %v", err) } - db, err := initDB("./data/music.db", osuDb, osuRoot) + db, sqlc, err := initDB("./data/music.db", osuDb, osuRoot) if err != nil { log.Fatal(err) } @@ -76,6 +76,7 @@ func main() { Db: db, OsuDir: osuRoot, Env: envMap, + Sqlc: sqlc, } if err := runGrpcAndGateway(s, port); err != nil { diff --git a/grpc-backend/models.go b/grpc-backend/models.go index 00ecc58..ffad2cd 100644 --- a/grpc-backend/models.go +++ b/grpc-backend/models.go @@ -1,9 +1,11 @@ package main -import v1 "backend/gen" +import ( + v1 "backend/gen" + "backend/internal/db" + "fmt" +) -// Song represents a song entity -// @Description Song represents a song with metadata type Song struct { BeatmapID int `json:"beatmap_id" example:"123456"` MD5Hash string `json:"md5_hash" example:"abcd1234efgh5678"` @@ -32,6 +34,24 @@ func (song Song) toProto() *v1.Song { } } +// 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 { diff --git a/grpc-backend/sqlc.yaml b/grpc-backend/sqlc.yaml new file mode 100644 index 0000000..a833650 --- /dev/null +++ b/grpc-backend/sqlc.yaml @@ -0,0 +1,14 @@ +version: "2" +sql: + - engine: "sqlite" + schema: "sqlc/schema" + queries: "sqlc/query" + database: + uri: "file:data/music.db" + gen: + go: + package: "db" + out: "internal/db" + emit_json_tags: true + emit_interface: true + emit_empty_slices: true diff --git a/grpc-backend/sqlc/query/beatmap.sql b/grpc-backend/sqlc/query/beatmap.sql new file mode 100644 index 0000000..8deb2c5 --- /dev/null +++ b/grpc-backend/sqlc/query/beatmap.sql @@ -0,0 +1,30 @@ +-- name: InsertBeatmap :exec +INSERT INTO Beatmap ( + BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator, + Difficulty, Audio, MD5Hash, File, RankedStatus, + LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId, + Source, Tags, LastPlayed, Folder +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + +-- name: GetBeatmapByHash :one +SELECT * FROM Beatmap WHERE MD5Hash = ?; + +-- name: GetBeatmapCount :one +SELECT COUNT(*) FROM Beatmap; + +-- name: GetRecentBeatmaps :many +SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +FROM Beatmap GROUP BY Folder ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ?; + +-- name: SearchBeatmaps :many +SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime +FROM Beatmap +WHERE Title LIKE ? OR Artist LIKE ? +LIMIT ? OFFSET ?; + +-- name: GetArtists :many +SELECT Artist, COUNT(Artist) AS count +FROM Beatmap +WHERE Artist LIKE ? OR Title LIKE ? +GROUP BY Artist +LIMIT ? OFFSET ?; diff --git a/grpc-backend/sqlc/query/collection.sql b/grpc-backend/sqlc/query/collection.sql new file mode 100644 index 0000000..6a1f47e --- /dev/null +++ b/grpc-backend/sqlc/query/collection.sql @@ -0,0 +1,42 @@ +-- name: InsertCollection :exec +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 +JOIN Beatmap b ON c.MD5Hash = b.MD5Hash +WHERE c.Name LIKE ? +GROUP BY c.Name +LIMIT ? OFFSET ?; + +-- 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 ?; + +-- 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 +JOIN Beatmap b ON c.MD5Hash = b.MD5Hash +WHERE c.Name = ? +LIMIT ? OFFSET ?; diff --git a/grpc-backend/sqlc/schema/schema.sql b/grpc-backend/sqlc/schema/schema.sql new file mode 100644 index 0000000..49033ff --- /dev/null +++ b/grpc-backend/sqlc/schema/schema.sql @@ -0,0 +1,36 @@ +-- Beatmap Table +CREATE TABLE IF NOT EXISTS Beatmap ( + BeatmapId INTEGER DEFAULT 0, + Artist TEXT DEFAULT '?????', + ArtistUnicode TEXT DEFAULT '?????', + Title TEXT DEFAULT '???????', + TitleUnicode TEXT DEFAULT '???????', + Creator TEXT DEFAULT '?????', + Difficulty TEXT DEFAULT '1', + Audio TEXT DEFAULT 'unknown.mp3', + MD5Hash TEXT DEFAULT '00000000000000000000000000000000', + File TEXT DEFAULT 'unknown.osu', + RankedStatus TEXT DEFAULT 'Unknown', + LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00', + 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', + Folder TEXT DEFAULT 'Unknown Folder', + UNIQUE (Artist, Title, MD5Hash, Difficulty) +); + +CREATE INDEX IF NOT EXISTS idx_beatmap_md5hash ON Beatmap(MD5Hash); +CREATE INDEX IF NOT EXISTS idx_beatmap_lastModifiedTime ON Beatmap(LastModifiedTime); +CREATE INDEX IF NOT EXISTS idx_beatmap_title_artist ON Beatmap(Title, Artist); + +-- Collection Table +CREATE TABLE IF NOT EXISTS Collection ( + Name TEXT DEFAULT '', + MD5Hash TEXT DEFAULT '00000000000000000000000000000000' +); + +CREATE INDEX IF NOT EXISTS idx_collection_name ON Collection(Name); +CREATE INDEX IF NOT EXISTS idx_collection_md5hash ON Collection(MD5Hash);