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