mirror of
https://github.com/JuLi0n21/pwa-player.git
synced 2026-04-19 15:30:05 +00:00
302 lines
7.6 KiB
Go
302 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
|
|
v1 "backend/gen"
|
|
"backend/internal/db"
|
|
sqlcdb "backend/internal/db"
|
|
|
|
"github.com/joho/godotenv"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/juli0n21/go-osu-parser/parser"
|
|
)
|
|
|
|
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
|
|
Env map[string]string
|
|
Sqlc *db.Queries
|
|
|
|
v1.UnimplementedMusicBackendServer
|
|
}
|
|
|
|
func (s *Server) registerRoutes() *http.ServeMux {
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/api/v1/audio/{filepath}", s.songFile)
|
|
mux.HandleFunc("/api/v1/image/{filepath}", s.imageFile)
|
|
|
|
mux.HandleFunc("/api/v1/callback", s.callback)
|
|
|
|
return mux
|
|
}
|
|
|
|
func logRequests(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL.Path)
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func corsMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (s *Server) Ping(ctx context.Context, req *v1.PingRequest) (*v1.PingResponse, error) {
|
|
return &v1.PingResponse{Pong: "pong"}, nil
|
|
}
|
|
|
|
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) Song(ctx context.Context, req *v1.SongRequest) (*v1.SongResponse, error) {
|
|
|
|
hash := req.Hash
|
|
if hash == "" {
|
|
return nil, status.Errorf(codes.InvalidArgument, "hash is required and cant be empty")
|
|
}
|
|
|
|
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: 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)
|
|
|
|
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: 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))
|
|
offset := int(req.Offset)
|
|
favorites, err := getFavorites(s.Db, req.Query, limit, offset)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil, status.Errorf(codes.Internal, "failed to get favorites")
|
|
}
|
|
|
|
return &v1.FavoriteResponse{
|
|
Songs: toProtoSongs(favorites),
|
|
}, nil
|
|
*/
|
|
}
|
|
|
|
func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v1.CollectionResponse, error) {
|
|
|
|
limit := defaultLimit(int(req.Limit))
|
|
offset := int(req.Offset)
|
|
|
|
name := req.Name
|
|
if 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 toProtoCollectionSqlc(c), nil
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
return toProtoCollectionoffsetSqlc(c), nil
|
|
|
|
}
|
|
|
|
func (s *Server) Search(ctx context.Context, req *v1.SearchSharedRequest) (*v1.SearchSharedResponse, error) {
|
|
q := req.Query
|
|
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 := 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: "",
|
|
Songs: toProtoSongsSqlC(search),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) SearchCollections(ctx context.Context, req *v1.SearchCollectionRequest) (*v1.SearchCollectionResponse, error) {
|
|
q := req.Query
|
|
q = "%" + q + "%"
|
|
//limit := defaultLimit(int(req.Limit))
|
|
limit := 10000
|
|
offset := int(req.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 toProtoCollectionsSearchPreview(preview), nil
|
|
}
|
|
|
|
func (s *Server) SearchArtists(ctx context.Context, req *v1.SearchArtistRequest) (*v1.SearchArtistResponse, error) {
|
|
q := req.Query
|
|
if q == "" {
|
|
return nil, status.Error(codes.InvalidArgument, "query is required")
|
|
}
|
|
|
|
limit := defaultLimit(int(req.Limit))
|
|
offset := int(req.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: nil,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) songFile(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)
|
|
}
|
|
|
|
func (s *Server) imageFile(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, ErrFailedToParseEncoded.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
http.ServeFile(w, r, s.OsuDir+"Songs/"+string(filename))
|
|
}
|
|
|
|
func (s *Server) callback(w http.ResponseWriter, r *http.Request) {
|
|
cookie := r.URL.Query().Get("COOKIE")
|
|
|
|
if cookie != "" {
|
|
s.Env["COOKIE"] = cookie
|
|
godotenv.Write(s.Env, ".env")
|
|
}
|
|
}
|
|
|
|
func defaultLimit(limit int) int {
|
|
if limit <= 0 || limit > 100 {
|
|
return 100
|
|
}
|
|
return limit
|
|
}
|