add a few handlers

This commit is contained in:
2025-02-03 01:57:22 +01:00
parent 0d6a9a8b32
commit 0479db520b
6 changed files with 362 additions and 147 deletions

View File

@@ -14,7 +14,13 @@ import (
var ErrBeatmapCountNotMatch = errors.New("beatmap count not matching") var ErrBeatmapCountNotMatch = errors.New("beatmap count not matching")
func initDB(connectionString string, osuDb *parser.OsuDB) (*sql.DB, error) { var osuDB *parser.OsuDB
var fileName string
func initDB(connectionString string, osuDb *parser.OsuDB, osuRoot string) (*sql.DB, error) {
osuDB = osuDb
fileName = osuRoot
dir := filepath.Dir(connectionString) dir := filepath.Dir(connectionString)
@@ -30,12 +36,17 @@ func initDB(connectionString string, osuDb *parser.OsuDB) (*sql.DB, error) {
return nil, fmt.Errorf("failed to open SQLite database %s: %v", connectionString, err) return nil, fmt.Errorf("failed to open SQLite database %s: %v", connectionString, err)
} }
_, err = db.Exec("PRAGMA temp_store = MEMORY;")
if err != nil {
log.Fatal(err)
}
if err = createDB(db); err != nil { if err = createDB(db); err != nil {
return nil, err return nil, err
} }
if err = checkhealth(db, osuDb); err != nil { if err = checkhealth(db, osuDB); err != nil {
if err = rebuildDb(db, osuDb); err != nil { if err = rebuildDb(db, osuDB); err != nil {
return nil, err return nil, err
} }
} }
@@ -53,9 +64,9 @@ func createDB(db *sql.DB) error {
TitleUnicode TEXT DEFAULT '???????', TitleUnicode TEXT DEFAULT '???????',
Creator TEXT DEFAULT '?????', Creator TEXT DEFAULT '?????',
Difficulty TEXT DEFAULT '1', Difficulty TEXT DEFAULT '1',
AudioFileName TEXT DEFAULT 'unknown.mp3', Audio TEXT DEFAULT 'unknown.mp3',
MD5Hash TEXT DEFAULT '00000000000000000000000000000000', MD5Hash TEXT DEFAULT '00000000000000000000000000000000',
FileName TEXT DEFAULT 'unknown.osu', File TEXT DEFAULT 'unknown.osu',
RankedStatus TEXT DEFAULT Unknown, RankedStatus TEXT DEFAULT Unknown,
LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00', LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00',
TotalTime INTEGER DEFAULT 0, TotalTime INTEGER DEFAULT 0,
@@ -64,11 +75,25 @@ func createDB(db *sql.DB) error {
Source TEXT DEFAULT '', Source TEXT DEFAULT '',
Tags TEXT DEFAULT '', Tags TEXT DEFAULT '',
LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00', LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00',
FolderName TEXT DEFAULT 'Unknown Folder', Folder TEXT DEFAULT 'Unknown Folder',
UNIQUE (Artist, Title, MD5Hash, Difficulty) UNIQUE (Artist, Title, MD5Hash, Difficulty)
); );
`) `)
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_md5hash ON Beatmap(MD5Hash);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_lastModifiedTime ON Beatmap(LastModifiedTime);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_title_artist ON Beatmap(Title, Artist);")
if err != nil { if err != nil {
return err return err
} }
@@ -123,9 +148,9 @@ func rebuildDb(db *sql.DB, osuDb *parser.OsuDB) error {
stmt, err := db.Prepare(` stmt, err := db.Prepare(`
INSERT INTO Beatmap ( INSERT INTO Beatmap (
BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator, BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator,
Difficulty, AudioFileName, MD5Hash, FileName, RankedStatus, Difficulty, Audio, MD5Hash, File, RankedStatus,
LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId, LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId,
Source, Tags, LastPlayed, FolderName Source, Tags, LastPlayed, Folder
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`) `)
// ON CONFLICT (Artist, Title, MD5Hash) DO NOTHING // ON CONFLICT (Artist, Title, MD5Hash) DO NOTHING
@@ -143,7 +168,7 @@ func rebuildDb(db *sql.DB, osuDb *parser.OsuDB) error {
stmt = tx.Stmt(stmt) stmt = tx.Stmt(stmt)
for i, beatmap := range osuDb.Beatmaps { for i, beatmap := range osuDb.Beatmaps {
//fmt.Println(i, beatmap.Artist, beatmap.SongTitle, beatmap.MD5Hash) fmt.Println(i, beatmap.Artist, beatmap.SongTitle, beatmap.MD5Hash)
_, err := stmt.Exec( _, err := stmt.Exec(
beatmap.DifficultyID, beatmap.Artist, beatmap.ArtistUnicode, beatmap.DifficultyID, beatmap.Artist, beatmap.ArtistUnicode,
beatmap.SongTitle, beatmap.SongTitleUnicode, beatmap.Creator, beatmap.SongTitle, beatmap.SongTitleUnicode, beatmap.Creator,
@@ -187,16 +212,17 @@ func getBeatmapCount(db *sql.DB) int {
} }
func getRecent(db *sql.DB, limit, offset int) ([]Song, error) { func getRecent(db *sql.DB, limit, offset int) ([]Song, error) {
rows, err := db.Query("SELECT * FROM Songs ORDER BY LastPlayed DESC LIMIT ? OFFSET ?", limit, offset) rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ?", limit, offset)
if err != nil { if err != nil {
return nil, err return []Song{}, err
} }
defer rows.Close() defer rows.Close()
return scanSongs(rows) return scanSongs(rows)
} }
func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) { func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) {
rows, err := db.Query("SELECT * FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset) rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
if err != nil { if err != nil {
return ActiveSearch{}, err return ActiveSearch{}, err
} }
@@ -209,7 +235,7 @@ func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) {
} }
func getArtists(db *sql.DB, q string, limit, offset int) ([]string, error) { func getArtists(db *sql.DB, q string, limit, offset int) ([]string, error) {
rows, err := db.Query("SELECT * FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset) rows, err := db.Query("SELECT Artist FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }
@@ -245,18 +271,33 @@ func getCollections(db *sql.DB, q string, limit, offset int) ([]Collection, erro
} }
func getSong(db *sql.DB, hash string) (Song, error) { func getSong(db *sql.DB, hash string) (Song, error) {
row := db.QueryRow("SELECT * FROM Songs WHERE MD5Hash = ?", hash)
return scanSong(row) 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) { func scanSongs(rows *sql.Rows) ([]Song, error) {
songs := []Song{}
var songs []Song
for rows.Next() { for rows.Next() {
var s Song var s Song
if err := rows.Scan(&s); 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 []Song{}, err
} }
s.Url = fmt.Sprintf("%s/%s", s.Folder, s.Audio)
bm, err := parser.ParseOsuFile(fmt.Sprintf("%sSongs/%s/%s", fileName, s.Folder, s.File))
if err != nil {
fmt.Println(err)
s.Image = fmt.Sprintf("404.png")
} else {
if len(bm.Events) > 1 && len(bm.Events[0].EventParams) > 1 {
s.Image = fmt.Sprintf("%s/%s", s.Folder, bm.Events[0].EventParams[0])
}
}
songs = append(songs, s) songs = append(songs, s)
} }
return songs, nil return songs, nil
@@ -264,10 +305,23 @@ func scanSongs(rows *sql.Rows) ([]Song, error) {
func scanSong(row *sql.Row) (Song, error) { func scanSong(row *sql.Row) (Song, error) {
var s Song s := Song{}
if err := row.Scan(&s); err != nil { 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 return Song{}, err
} }
s.Url = fmt.Sprintf("%s/%s", s.Folder, s.Audio)
bm, err := parser.ParseOsuFile(fmt.Sprintf("%sSongs/%s/%s", fileName, s.Folder, s.File))
if err != nil {
fmt.Println(err)
s.Image = fmt.Sprintf("404.png")
return s, nil
}
if len(bm.Events) > 1 && len(bm.Events[0].EventParams) > 1 {
s.Image = fmt.Sprintf("%s/%s", s.Folder, bm.Events[0].EventParams[0])
}
return s, nil return s, nil
} }

View File

@@ -9,12 +9,26 @@ require (
) )
require ( require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.9.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/sys v0.22.0 // indirect github.com/swaggo/files v1.0.1 // indirect
github.com/swaggo/http-swagger v1.3.4 // indirect
github.com/swaggo/swag v1.16.4 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/tools v0.29.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.55.3 // indirect modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect modernc.org/memory v1.8.0 // indirect

View File

@@ -1,26 +1,88 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 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/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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/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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/juli0n21/go-osu-parser v0.0.6 h1:r5BTNrEDUHsF0ZFCvx0vfRcjU2IRvT3va4O1r3dm7og= github.com/juli0n21/go-osu-parser v0.0.6 h1:r5BTNrEDUHsF0ZFCvx0vfRcjU2IRvT3va4O1r3dm7og=
github.com/juli0n21/go-osu-parser v0.0.6/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0= github.com/juli0n21/go-osu-parser v0.0.6/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
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=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
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=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=

View File

@@ -2,186 +2,249 @@ package main
import ( import (
"database/sql" "database/sql"
"encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os"
"strconv" "strconv"
httpSwagger "github.com/swaggo/http-swagger"
"github.com/juli0n21/go-osu-parser/parser" "github.com/juli0n21/go-osu-parser/parser"
) )
func run(db *sql.DB, osuDb *parser.OsuDB) { var ErrRequiredParameterNotPresent = errors.New("required parameter missing")
var ErrFailedToParseEncoded = errors.New("invalid encoded value")
var ErrFileNotFound = errors.New("file not found")
type Server struct {
Port string
OsuDir string
Db *sql.DB
OsuDb *parser.OsuDB
}
func run(s *Server) {
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "pong") fmt.Fprintln(w, "pong")
}) })
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Login endpoint") http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect)
}) })
http.HandleFunc("/api/v1/songs/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/songs/{hash}/", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil { hash := r.PathValue("hash")
fmt.Fprintln(w, err) if hash == "" {
} http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
offset, err := strconv.Atoi(r.URL.Query().Get("offset")) return
if err != nil {
fmt.Fprintln(w, err)
} }
recent, err := getRecent(db, limit, offset) song, err := getSong(s.Db, hash)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusNotFound)
return
} }
fmt.Fprintln(w, recent)
writeJSON(w, song, http.StatusOK)
}) })
http.HandleFunc("/api/v1/songs/recent", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/songs/recent", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
fmt.Fprintln(w, err)
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
}
recent, err := getRecent(db, limit, offset) limit, offset := pagination(r)
recent, err := getRecent(s.Db, limit, offset)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, recent, http.StatusOK)
fmt.Fprintln(w, "Recent songs endpoint")
}) })
http.HandleFunc("/api/v1/songs/favorite", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/songs/favorite", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit")) query := r.URL.Query().Get("query")
if err != nil { if query == "" {
fmt.Fprintln(w, err) http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
} return
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
} }
recent, err := getRecent(db, limit, offset) limit, offset := pagination(r)
favorites, err := getFavorites(s.Db, query, limit, offset)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, favorites, http.StatusOK)
fmt.Fprintln(w, "Favorite songs endpoint")
}) })
http.HandleFunc("/api/v1/collections/{index}", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/collections", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit")) index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil { if err != nil {
fmt.Fprintln(w, err) http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
} return
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
} }
recent, err := getRecent(db, limit, offset) recent, err := getCollection(s.Db, index)
if err != nil { if err != nil {
fmt.Fprintln(w, err) http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, recent, http.StatusOK)
fmt.Fprintln(w, "Collections endpoint")
}) })
http.HandleFunc("/api/v1/collections/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/collections/", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit")) query := r.URL.Query().Get("query")
if err != nil { if query == "" {
fmt.Fprintln(w, err) http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
} return
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
} }
recent, err := getRecent(db, limit, offset) limit, offset := pagination(r)
recent, err := getCollections(s.Db, query, limit, offset)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, recent, http.StatusOK)
fmt.Fprintln(w, "Collection with index endpoint")
}) })
http.HandleFunc("/api/v1/audio/{hash}", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/audio/{filepath}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Audio endpoint") f := r.PathValue("filepath")
if f == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
filename, err := base64.RawStdEncoding.DecodeString(f)
if err != nil {
fmt.Println(err)
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
file, err := os.Open(s.OsuDir + "Songs/" + string(filename))
if err != nil {
fmt.Println(err)
http.Error(w, ErrFileNotFound.Error(), http.StatusNotFound)
return
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
fmt.Println(err)
http.Error(w, ErrFileNotFound.Error(), http.StatusNotFound)
return
}
http.ServeContent(w, r, stat.Name(), stat.ModTime(), file)
}) })
http.HandleFunc("/api/v1/search/active", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/search/active", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
fmt.Fprintln(w, err)
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
}
q := r.URL.Query().Get("query") q := r.URL.Query().Get("query")
if err != nil || q == "" { if q == "" {
fmt.Fprintln(w, errors.New("'query' is required for search")) http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
} }
recent, err := getSearch(db, q, limit, offset) limit, offset := pagination(r)
recent, err := getSearch(s.Db, q, limit, offset)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, recent, http.StatusOK)
fmt.Fprintln(w, "Active search endpoint")
}) })
http.HandleFunc("/api/v1/search/artist", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/search/artist", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
fmt.Fprintln(w, err)
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
}
q := r.URL.Query().Get("query") q := r.URL.Query().Get("query")
if err != nil { if q == "" {
fmt.Fprintln(w, err) http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
} }
recent, err := getArtists(db, q, limit, offset) limit, offset := pagination(r)
recent, err := getArtists(s.Db, q, limit, offset)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, recent, http.StatusOK)
fmt.Fprintln(w, "Artist search endpoint")
}) })
http.HandleFunc("/api/v1/search/collections", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/search/collections", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
fmt.Fprintln(w, err)
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
}
q := r.URL.Query().Get("query") q := r.URL.Query().Get("query")
if err != nil { if q == "" {
fmt.Fprintln(w, err) http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
} }
recent, err := getCollections(db, q, limit, offset) limit, offset := pagination(r)
recent, err := getCollections(s.Db, q, limit, offset)
if err != nil { if err != nil {
fmt.Fprintln(w, err) fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
fmt.Fprintln(w, recent) writeJSON(w, recent, http.StatusOK)
}) })
http.HandleFunc("/api/v1/images/{filename}", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/v1/images/{filename}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Images endpoint") f := r.PathValue("filename")
if f == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
filename, err := base64.RawStdEncoding.DecodeString(f)
if err != nil {
fmt.Println(err)
http.Error(w, ErrFailedToParseEncoded.Error(), http.StatusBadRequest)
return
}
http.ServeFile(w, r, s.OsuDir+"Songs/"+string(filename))
}) })
fmt.Println("starting server on http://localhost:8080") http.Handle("/swagger/", httpSwagger.WrapHandler)
http.ListenAndServe(":8080", nil)
fmt.Println("starting server on http://localhost" + s.Port)
log.Fatal(http.ListenAndServe(s.Port, nil))
}
func writeJSON(w http.ResponseWriter, v any, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
fmt.Println(err)
http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError)
}
}
func pagination(r *http.Request) (int, int) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil || limit <= 0 || limit > 100 {
limit = 100
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil || offset < 0 {
offset = 0
}
return limit, offset
} }

View File

@@ -8,9 +8,16 @@ import (
"github.com/juli0n21/go-osu-parser/parser" "github.com/juli0n21/go-osu-parser/parser"
) )
// @title go-osu-music-hoster
// @version 1.0
// @description Server Hosting ur own osu files over a simple Api
// @host localhost:8080
// @BasePath /api/v1/
func main() { func main() {
filename := "/mnt/g/Anwendungen/osu!/osu!.db" filename := "/mnt/g/Anwendungen/osu!/osu!.db"
osuRoot := "/mnt/g/Anwendungen/osu!/"
if err := godotenv.Load(); err != nil { if err := godotenv.Load(); err != nil {
fmt.Println("Error loading .env file") fmt.Println("Error loading .env file")
@@ -21,10 +28,17 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
db, err := initDB("./data/music.db", osuDb) db, err := initDB("./data/music.db", osuDb, osuRoot)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
run(db, osuDb) s := &Server{
Port: ":8080",
Db: db,
OsuDb: osuDb,
OsuDir: osuRoot,
}
run(s)
} }

View File

@@ -2,34 +2,42 @@ package main
import "time" import "time"
// Song represents a song entity
// @Description Song represents a song with metadata
type Song struct { type Song struct {
MD5Hash string BeatmapID int `json:"beatmap_id" example:"123456"`
Title string MD5Hash string `json:"md5_hash" example:"abcd1234efgh5678"`
Artist string Title string `json:"title" example:"Shape of You"`
Creator string Artist string `json:"artist" example:"Ed Sheeran"`
Folder string Creator string `json:"creator" example:"JohnDoe"`
File string Folder string `json:"folder" example:"osu/Songs/123456"`
Audio string File string `json:"file" example:"beatmap.osu"`
Mapper string Audio string `json:"audio" example:"audio.mp3"`
TotalTime time.Duration TotalTime time.Duration `json:"total_time" example:"240"`
Url string Url string `json:"url" example:"https://osu.ppy.sh/beatmaps/123456"`
Image string Image string `json:"image" example:"cover.jpg"`
} }
// CollectionPreview represents a preview of a song collection
// @Description CollectionPreview contains summary data of a song collection
type CollectionPreview struct { type CollectionPreview struct {
name string Name string `json:"name" example:"Favorite Songs"`
image string Image string `json:"image" example:"cover.jpg"`
index int Index int `json:"index" example:"1"`
items int Items int `json:"items" example:"10"`
} }
// Collection represents a full song collection
// @Description Collection holds a list of songs
type Collection struct { type Collection struct {
name string Name string `json:"name" example:"Best of 2023"`
items int Items int `json:"items" example:"15"`
Songs []Song Songs []Song `json:"songs"`
} }
// ActiveSearch represents an active song search query
// @Description ActiveSearch holds search results for a given artist
type ActiveSearch struct { type ActiveSearch struct {
artist string Artist string `json:"artist" example:"Ed Sheeran"`
Songs []Song Songs []Song `json:"songs"`
} }