add swagger docs

This commit is contained in:
2025-02-03 03:06:35 +01:00
parent 0479db520b
commit 1c266a5d2e
7 changed files with 2155 additions and 212 deletions

View File

@@ -11,6 +11,8 @@ import (
"os"
"strconv"
_ "backend/docs"
httpSwagger "github.com/swaggo/http-swagger"
"github.com/juli0n21/go-osu-parser/parser"
@@ -27,206 +29,350 @@ type Server struct {
OsuDb *parser.OsuDB
}
func (s *Server) registerRoutes() {
http.HandleFunc("/api/v1/ping", s.ping)
http.HandleFunc("/api/v1/login", s.login)
http.HandleFunc("/api/v1/song/{hash}/", s.song)
http.HandleFunc("/api/v1/songs/recents", s.recents)
http.HandleFunc("/api/v1/songs/favorites", s.favorites)
http.HandleFunc("/api/v1/collection", s.collection)
http.HandleFunc("/api/v1/collections", s.collections)
http.HandleFunc("/api/v1/search/collections", s.collectionSearch)
http.HandleFunc("/api/v1/search/active", s.activeSearch)
http.HandleFunc("/api/v1/search/artist", s.artistSearch)
http.HandleFunc("/api/v1/audio/{filepath}", s.songFile)
http.HandleFunc("/api/v1/image/{filepath}", s.imageFile)
http.Handle("/swagger-ui", httpSwagger.WrapHandler)
}
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) {
http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect)
})
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
}
song, err := getSong(s.Db, hash)
if err != nil {
fmt.Println(err)
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusNotFound)
return
}
writeJSON(w, song, http.StatusOK)
})
http.HandleFunc("/api/v1/songs/recent", func(w http.ResponseWriter, r *http.Request) {
limit, offset := pagination(r)
recent, err := getRecent(s.Db, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/songs/favorite", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
favorites, err := getFavorites(s.Db, query, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, favorites, http.StatusOK)
})
http.HandleFunc("/api/v1/collections", func(w http.ResponseWriter, r *http.Request) {
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
recent, err := getCollection(s.Db, index)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/collections/", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getCollections(s.Db, query, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
})
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) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getSearch(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/search/artist", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getArtists(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/search/collections", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getCollections(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
})
http.HandleFunc("/api/v1/images/{filename}", func(w http.ResponseWriter, r *http.Request) {
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))
})
http.Handle("/swagger/", httpSwagger.WrapHandler)
s.registerRoutes()
fmt.Println("starting server on http://localhost" + s.Port)
log.Fatal(http.ListenAndServe(s.Port, nil))
}
// ping godoc
// @Summary Check server health
// @Description Returns a pong response if the server is running
// @Tags health
// @Success 200 {string} string "pong"
// @Router /ping [get]
func (s *Server) ping(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "pong")
}
// login godoc
// @Summary Redirect to login page
// @Description Redirects users to an external authentication page
// @Tags auth
// @Success 307 {string} string "Temporary Redirect"
// @Router /login [get]
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect)
}
// song godoc
// @Summary Get a song by its hash
// @Description Retrieves a song using its unique hash identifier.
// @Tags songs
// @Accept json
// @Produce json
// @Param hash path string true "Song hash"
// @Success 200 {object} Song
// @Failure 400 {string} string "Invalid parameter"
// @Failure 404 {string} string "Song not found"
// @Router /song/{hash} [get]
func (s *Server) song(w http.ResponseWriter, r *http.Request) {
hash := r.PathValue("hash")
if hash == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
song, err := getSong(s.Db, hash)
if err != nil {
fmt.Println(err)
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusNotFound)
return
}
writeJSON(w, song, http.StatusOK)
}
// recents godoc
// @Summary Get a list of recent songs
// @Description Retrieves recent songs with pagination support.
// @Tags songs
// @Accept json
// @Produce json
// @Param limit query int false "Limit" default(10)
// @Param offset query int false "Offset" default(0)
// @Success 200 {array} Song
// @Failure 500 {string} string "Internal server error"
// @Router /songs/recents [get]
func (s *Server) recents(w http.ResponseWriter, r *http.Request) {
limit, offset := pagination(r)
recent, err := getRecent(s.Db, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// favorites godoc
// @Summary Get a list of favorite songs based on a query
// @Description Retrieves favorite songs filtered by a query with pagination support.
// @Tags songs
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit" default(10)
// @Param offset query int false "Offset" default(0)
// @Success 200 {array} Song
// @Failure 400 {string} string "Invalid parameter"
// @Failure 500 {string} string "Internal server error"
// @Router /songs/favorites [get]
func (s *Server) favorites(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
favorites, err := getFavorites(s.Db, query, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, favorites, http.StatusOK)
}
// collection godoc
// @Summary Get a collection of songs by index
// @Description Retrieves a collection of songs using the provided index.
// @Tags songs
// @Accept json
// @Produce json
// @Param index query int true "Index"
// @Success 200 {array} Song
// @Failure 400 {string} string "Invalid parameter"
// @Failure 500 {string} string "Internal server error"
// @Router /collection [get]
func (s *Server) collection(w http.ResponseWriter, r *http.Request) {
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
recent, err := getCollection(s.Db, index)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// @Summary Retrieves collections based on a query
// @Description Retrieves collections from the database with pagination based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string false "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {array} Collection "List of collections"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /collections [get]
func (s *Server) collections(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getCollections(s.Db, query, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// @Summary Searches collections based on a query
// @Description Searches collections in the database based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {array} Collection "List of collections"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /search/collections [get]
func (s *Server) collectionSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getCollections(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// @Summary Searches active records based on a query
// @Description Searches active records in the database based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {object} ActiveSearch "Active search result"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /search/active [get]
func (s *Server) activeSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getSearch(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// @Summary Searches for artists based on a query
// @Description Searches for artists in the database based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {array} string "List of artists"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /search/artist [get]
func (s *Server) artistSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
recent, err := getArtists(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// @Summary Retrieves a song file by its encoded path
// @Description Retrieves a song file from the server based on the provided encoded filepath
// @Tags files
// @Accept json
// @Produce json
// @Param filepath path string true "Base64 encoded file path"
// @Success 200 {file} File "The requested song file"
// @Failure 400 {object} string "Bad Request"
// @Failure 404 {object} string "File Not Found"
// @Router /audio/{filepath} [get]
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)
}
// @Summary Retrieves an image file by its encoded path
// @Description Retrieves an image file from the server based on the provided encoded filepath
// @Tags files
// @Accept json
// @Produce json
// @Param filepath path string true "Base64 encoded file path"
// @Success 200 {file} File "The requested image file"
// @Failure 400 {object} string "Bad Request"
// @Failure 404 {object} string "File Not Found"
// @Router /image/{filepath} [get]
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 writeJSON(w http.ResponseWriter, v any, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)