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")
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)
@@ -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)
}
_, err = db.Exec("PRAGMA temp_store = MEMORY;")
if err != nil {
log.Fatal(err)
}
if err = createDB(db); err != nil {
return nil, err
}
if err = checkhealth(db, osuDb); err != nil {
if err = rebuildDb(db, osuDb); err != nil {
if err = checkhealth(db, osuDB); err != nil {
if err = rebuildDb(db, osuDB); err != nil {
return nil, err
}
}
@@ -53,9 +64,9 @@ func createDB(db *sql.DB) error {
TitleUnicode TEXT DEFAULT '???????',
Creator TEXT DEFAULT '?????',
Difficulty TEXT DEFAULT '1',
AudioFileName TEXT DEFAULT 'unknown.mp3',
Audio TEXT DEFAULT 'unknown.mp3',
MD5Hash TEXT DEFAULT '00000000000000000000000000000000',
FileName TEXT DEFAULT 'unknown.osu',
File TEXT DEFAULT 'unknown.osu',
RankedStatus TEXT DEFAULT Unknown,
LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00',
TotalTime INTEGER DEFAULT 0,
@@ -64,11 +75,25 @@ func createDB(db *sql.DB) error {
Source TEXT DEFAULT '',
Tags TEXT DEFAULT '',
LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00',
FolderName TEXT DEFAULT 'Unknown Folder',
Folder TEXT DEFAULT 'Unknown Folder',
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 {
return err
}
@@ -123,9 +148,9 @@ func rebuildDb(db *sql.DB, osuDb *parser.OsuDB) error {
stmt, err := db.Prepare(`
INSERT INTO Beatmap (
BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator,
Difficulty, AudioFileName, MD5Hash, FileName, RankedStatus,
Difficulty, Audio, MD5Hash, File, RankedStatus,
LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId,
Source, Tags, LastPlayed, FolderName
Source, Tags, LastPlayed, Folder
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
// ON CONFLICT (Artist, Title, MD5Hash) DO NOTHING
@@ -143,7 +168,7 @@ func rebuildDb(db *sql.DB, osuDb *parser.OsuDB) error {
stmt = tx.Stmt(stmt)
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(
beatmap.DifficultyID, beatmap.Artist, beatmap.ArtistUnicode,
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) {
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 {
return nil, err
return []Song{}, err
}
defer rows.Close()
return scanSongs(rows)
}
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 {
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) {
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 {
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) {
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) {
var songs []Song
songs := []Song{}
for rows.Next() {
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
}
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)
}
return songs, nil
@@ -264,10 +305,23 @@ func scanSongs(rows *sql.Rows) ([]Song, error) {
func scanSong(row *sql.Row) (Song, error) {
var s Song
if err := row.Scan(&s); err != nil {
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.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
}

View File

@@ -9,12 +9,26 @@ require (
)
require (
github.com/KyleBanks/depth v1.2.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/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/ncruces/go-strftime v0.1.9 // 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/mathutil v1.6.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/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/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
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/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/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/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/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/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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
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/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/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=

View File

@@ -2,186 +2,249 @@ package main
import (
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"strconv"
httpSwagger "github.com/swaggo/http-swagger"
"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) {
fmt.Fprintln(w, "pong")
})
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) {
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)
http.HandleFunc("/api/v1/songs/{hash}/", func(w http.ResponseWriter, r *http.Request) {
hash := r.PathValue("hash")
if hash == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
recent, err := getRecent(db, limit, offset)
song, err := getSong(s.Db, hash)
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) {
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 {
fmt.Fprintln(w, err)
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, recent)
fmt.Fprintln(w, "Recent songs endpoint")
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/songs/favorite", 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)
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
recent, err := getRecent(db, limit, offset)
limit, offset := pagination(r)
favorites, err := getFavorites(s.Db, query, limit, offset)
if err != nil {
fmt.Fprintln(w, err)
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, recent)
fmt.Fprintln(w, "Favorite songs endpoint")
writeJSON(w, favorites, http.StatusOK)
})
http.HandleFunc("/api/v1/collections/{index}", func(w http.ResponseWriter, r *http.Request) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
http.HandleFunc("/api/v1/collections", func(w http.ResponseWriter, r *http.Request) {
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
fmt.Fprintln(w, err)
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil {
fmt.Fprintln(w, err)
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
recent, err := getRecent(db, limit, offset)
recent, err := getCollection(s.Db, index)
if err != nil {
fmt.Fprintln(w, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, recent)
fmt.Fprintln(w, "Collections endpoint")
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/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)
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
recent, err := getRecent(db, limit, offset)
limit, offset := pagination(r)
recent, err := getCollections(s.Db, query, limit, offset)
if err != nil {
fmt.Fprintln(w, err)
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, recent)
fmt.Fprintln(w, "Collection with index endpoint")
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/audio/{hash}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Audio endpoint")
http.HandleFunc("/api/v1/audio/{filepath}", func(w http.ResponseWriter, r *http.Request) {
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) {
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")
if err != nil || q == "" {
fmt.Fprintln(w, errors.New("'query' is required for search"))
if q == "" {
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 {
fmt.Fprintln(w, err)
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, recent)
fmt.Fprintln(w, "Active search endpoint")
writeJSON(w, recent, http.StatusOK)
})
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")
if err != nil {
fmt.Fprintln(w, err)
if q == "" {
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 {
fmt.Fprintln(w, err)
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, recent)
fmt.Fprintln(w, "Artist search endpoint")
writeJSON(w, recent, http.StatusOK)
})
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")
if err != nil {
fmt.Fprintln(w, err)
if q == "" {
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 {
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) {
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.ListenAndServe(":8080", nil)
http.Handle("/swagger/", httpSwagger.WrapHandler)
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"
)
// @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() {
filename := "/mnt/g/Anwendungen/osu!/osu!.db"
osuRoot := "/mnt/g/Anwendungen/osu!/"
if err := godotenv.Load(); err != nil {
fmt.Println("Error loading .env file")
@@ -21,10 +28,17 @@ func main() {
log.Fatal(err)
}
db, err := initDB("./data/music.db", osuDb)
db, err := initDB("./data/music.db", osuDb, osuRoot)
if err != nil {
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"
// Song represents a song entity
// @Description Song represents a song with metadata
type Song struct {
MD5Hash string
Title string
Artist string
Creator string
Folder string
File string
Audio string
Mapper string
TotalTime time.Duration
Url string
Image string
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 time.Duration `json:"total_time" example:"240"`
Url string `json:"url" example:"https://osu.ppy.sh/beatmaps/123456"`
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 {
name string
image string
index int
items int
Name string `json:"name" example:"Favorite Songs"`
Image string `json:"image" example:"cover.jpg"`
Index int `json:"index" example:"1"`
Items int `json:"items" example:"10"`
}
// Collection represents a full song collection
// @Description Collection holds a list of songs
type Collection struct {
name string
items int
Songs []Song
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
Songs []Song
Artist string `json:"artist" example:"Ed Sheeran"`
Songs []Song `json:"songs"`
}