mirror of
https://github.com/JuLi0n21/pwa-player.git
synced 2026-04-19 15:30:05 +00:00
add sqlc, fix background img fetching
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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")
|
||||
|
||||
276
grpc-backend/internal/db/beatmap.sql.go
Normal file
276
grpc-backend/internal/db/beatmap.sql.go
Normal file
@@ -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
|
||||
}
|
||||
223
grpc-backend/internal/db/collection.sql.go
Normal file
223
grpc-backend/internal/db/collection.sql.go
Normal file
@@ -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
|
||||
}
|
||||
31
grpc-backend/internal/db/db.go
Normal file
31
grpc-backend/internal/db/db.go
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
36
grpc-backend/internal/db/models.go
Normal file
36
grpc-backend/internal/db/models.go
Normal file
@@ -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"`
|
||||
}
|
||||
26
grpc-backend/internal/db/querier.go
Normal file
26
grpc-backend/internal/db/querier.go
Normal file
@@ -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)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
14
grpc-backend/sqlc.yaml
Normal file
14
grpc-backend/sqlc.yaml
Normal file
@@ -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
|
||||
30
grpc-backend/sqlc/query/beatmap.sql
Normal file
30
grpc-backend/sqlc/query/beatmap.sql
Normal file
@@ -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 ?;
|
||||
42
grpc-backend/sqlc/query/collection.sql
Normal file
42
grpc-backend/sqlc/query/collection.sql
Normal file
@@ -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 ?;
|
||||
36
grpc-backend/sqlc/schema/schema.sql
Normal file
36
grpc-backend/sqlc/schema/schema.sql
Normal file
@@ -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);
|
||||
Reference in New Issue
Block a user