add sqlc, fix background img fetching

This commit is contained in:
2025-07-15 01:16:07 +02:00
parent cb8a52693a
commit a3440326e8
15 changed files with 869 additions and 91 deletions

View File

@@ -1,6 +1,8 @@
package main package main
import ( import (
sqlcdb "backend/internal/db"
"context"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
@@ -9,7 +11,6 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"github.com/juli0n21/go-osu-parser/parser" "github.com/juli0n21/go-osu-parser/parser"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
@@ -20,7 +21,7 @@ var ErrBeatmapCountNotMatch = errors.New("beatmap count not matching")
var osuDB *parser.OsuDB var osuDB *parser.OsuDB
var osuRoot string 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 osuDB = osuDb
osuRoot = osuroot osuRoot = osuroot
@@ -30,13 +31,13 @@ func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql.
if _, err := os.Stat(dir); os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0755) err = os.MkdirAll(dir, 0755)
if err != nil { 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) db, err := sql.Open("sqlite", connectionString)
if err != nil { 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;") _, 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 { if err = createDB(db); err != nil {
return nil, err return nil, nil, err
} }
if err = checkhealth(db, osuDB); err != nil { if err = checkhealth(db, osuDB); err != nil {
if err = rebuildBeatmapDb(db, osuDB); err != nil { if err = rebuildBeatmapDb(db, osuDB); err != nil {
return nil, err return nil, nil, err
} }
} }
if err = createCollectionDB(db); err != nil { if err = createCollectionDB(db); err != nil {
return nil, err return nil, nil, err
} }
collectionDB, err := parser.ParseCollectionsDB(path.Join(osuRoot, "collection.db")) collectionDB, err := parser.ParseCollectionsDB(path.Join(osuRoot, "collection.db"))
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if err = checkCollectionHealth(db, collectionDB); err != nil { if err = checkCollectionHealth(db, collectionDB); err != nil {
if err = rebuildCollectionDb(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 { func createDB(db *sql.DB) error {
@@ -329,14 +332,20 @@ func getBeatmapCount(db *sql.DB) int {
return count return count
} }
func getRecent(db *sql.DB, limit, offset int) ([]Song, error) { func getRecent(ctx context.Context, q *sqlcdb.Queries, 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) rows, err := q.GetRecentBeatmaps(ctx, sqlcdb.GetRecentBeatmapsParams{
Limit: int64(limit), Offset: int64(offset),
})
if err != nil { 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) { 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() { for rows.Next() {
var s Song 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 { 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)) bm, err := parser.ParseOsuFile(fmt.Sprintf("%sSongs/%s/%s", osuRoot, s.Folder, s.File))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
s.Image = fmt.Sprintf("404.png") s.Image = "404.png"
} else { } else {
if len(bm.Events) > 1 && len(bm.Events[0].EventParams) > 1 { if bgImage := bm.BackgroundImage(); bgImage != "" {
s.Image = fmt.Sprintf("%s/%s", s.Folder, strings.Trim(bm.Events[0].EventParams[0], "\"")) 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" return "404.png"
} }
if bm.Version > 3 { bgImage := bm.BackgroundImage()
if len(bm.Events) > 0 && len(bm.Events[0].EventParams) > 0 { if bgImage != "" {
return fmt.Sprintf(`%s/%s`, folder, strings.Trim(bm.Events[0].EventParams[0], "\"")) return filepath.Join(folder, bgImage)
}
} else {
fmt.Println(bm.Events)
} }
return "404.png" return "404.png"
@@ -613,3 +621,32 @@ func scanCollectionPreview(row *sql.Row) (CollectionPreview, error) {
} }
return c, nil 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
}

View File

@@ -1,31 +1,33 @@
module backend module backend
go 1.23.5 go 1.24.5
toolchain go1.24.2
require ( 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/joho/godotenv v1.5.1
github.com/juli0n21/go-osu-parser v0.0.8 github.com/juli0n21/go-osu-parser v0.0.10
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7
google.golang.org/grpc v1.72.1 google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6 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 ( require (
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
golang.org/x/net v0.37.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
modernc.org/libc v1.62.1 // indirect modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.9.1 // indirect modernc.org/memory v1.11.0 // indirect
) )

View File

@@ -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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 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.9 h1:3hx+ZtLKRJGdyJLhRbZzGyJ60Q3yRk9+tDJqwBJOZEg=
github.com/juli0n21/go-osu-parser v0.0.8/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0= 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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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= 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= 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 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 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.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= 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-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 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 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= 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 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= 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 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= 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 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= 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 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= 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 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -11,6 +11,7 @@ import (
"os" "os"
v1 "backend/gen" v1 "backend/gen"
"backend/internal/db"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@@ -29,6 +30,7 @@ type Server struct {
Db *sql.DB Db *sql.DB
OsuDb *parser.OsuDB OsuDb *parser.OsuDB
Env map[string]string Env map[string]string
Sqlc *db.Queries
v1.UnimplementedMusicBackendServer v1.UnimplementedMusicBackendServer
} }
@@ -97,7 +99,7 @@ func (s *Server) Recent(ctx context.Context, req *v1.RecentRequest) (*v1.RecentR
limit := defaultLimit(int(req.Limit)) limit := defaultLimit(int(req.Limit))
offset := int(req.Offset) offset := int(req.Offset)
recent, err := getRecent(s.Db, limit, offset) recent, err := getRecent(context.Background(), s.Sqlc, limit, offset)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return nil, status.Errorf(codes.Internal, "failed to get recents") return nil, status.Errorf(codes.Internal, "failed to get recents")

View 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
}

View 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
}

View 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,
}
}

View 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"`
}

View 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)

View File

@@ -66,7 +66,7 @@ func main() {
log.Fatalf("Couldnt Update Endpoint url with Proxy: %v", err) 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -76,6 +76,7 @@ func main() {
Db: db, Db: db,
OsuDir: osuRoot, OsuDir: osuRoot,
Env: envMap, Env: envMap,
Sqlc: sqlc,
} }
if err := runGrpcAndGateway(s, port); err != nil { if err := runGrpcAndGateway(s, port); err != nil {

View File

@@ -1,9 +1,11 @@
package main 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 { type Song struct {
BeatmapID int `json:"beatmap_id" example:"123456"` BeatmapID int `json:"beatmap_id" example:"123456"`
MD5Hash string `json:"md5_hash" example:"abcd1234efgh5678"` 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 { func toProtoSongs(local []Song) []*v1.Song {
songs := make([]*v1.Song, len(local)) songs := make([]*v1.Song, len(local))
for i, s := range local { for i, s := range local {

14
grpc-backend/sqlc.yaml Normal file
View 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

View 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 ?;

View 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 ?;

View 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);