mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-04 21:17:37 +03:00
193 lines
5.6 KiB
Go
193 lines
5.6 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Masterminds/squirrel"
|
|
"github.com/navidrome/navidrome/model"
|
|
)
|
|
|
|
func toAPITrack(mf model.MediaFile) Track {
|
|
return Track{
|
|
Type: "track",
|
|
Id: mf.ID,
|
|
Attributes: &TrackAttributes{
|
|
Album: mf.Album,
|
|
//Albumartist: mf.AlbumArtist,
|
|
Artist: mf.Artist,
|
|
//Bitrate: mf.BitRate,
|
|
//Bpm: p(mf.Bpm),
|
|
//Channels: mf.Channels,
|
|
//Comments: p(mf.Comment),
|
|
//Disc: p(mf.DiscNumber),
|
|
Duration: mf.Duration,
|
|
//Genre: p(mf.Genre),
|
|
//Mimetype: mf.ContentType(),
|
|
//RecordingMbid: p(mf.MbzTrackID),
|
|
//Size: int(mf.Size),
|
|
Title: mf.Title,
|
|
//Track: mf.TrackNumber,
|
|
//TrackMbid: p(mf.MbzReleaseTrackID),
|
|
//Year: p(mf.Year),
|
|
},
|
|
}
|
|
}
|
|
|
|
func toAPITracks(mfs model.MediaFiles) []Track {
|
|
tracks := make([]Track, len(mfs))
|
|
for i := range mfs {
|
|
tracks[i] = toAPITrack(mfs[i])
|
|
}
|
|
return tracks
|
|
}
|
|
|
|
func p[T comparable](t T) *T {
|
|
var zero T
|
|
if t == zero {
|
|
return nil
|
|
}
|
|
return &t
|
|
}
|
|
|
|
func v[T comparable](p *T) T {
|
|
var zero T
|
|
if p == nil {
|
|
return zero
|
|
}
|
|
return *p
|
|
}
|
|
|
|
// toQueryOptions convert a params struct to a model.QueryOptions struct, to be used by the
|
|
// GetAll and CountAll functions. It assumes all GetXxxxParams functions have the exact same structure.
|
|
func toQueryOptions(params GetTracksParams) model.QueryOptions {
|
|
var filters squirrel.And
|
|
parseFilter := func(fs *[]string, op func(f, v string) squirrel.Sqlizer) {
|
|
if fs != nil {
|
|
for _, f := range *fs {
|
|
parts := strings.SplitN(f, ":", 2)
|
|
filters = append(filters, op(parts[0], parts[1]))
|
|
}
|
|
}
|
|
}
|
|
parseFilter(params.FilterEquals, func(f, v string) squirrel.Sqlizer { return squirrel.Eq{f: v} })
|
|
parseFilter(params.FilterContains, func(f, v string) squirrel.Sqlizer { return squirrel.Like{f: "%" + v + "%"} })
|
|
parseFilter(params.FilterStartsWith, func(f, v string) squirrel.Sqlizer { return squirrel.Like{f: v + "%"} })
|
|
parseFilter(params.FilterEndsWith, func(f, v string) squirrel.Sqlizer { return squirrel.Like{f: "%" + v} })
|
|
parseFilter(params.FilterGreaterThan, func(f, v string) squirrel.Sqlizer { return squirrel.Gt{f: v} })
|
|
parseFilter(params.FilterGreaterOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.GtOrEq{f: v} })
|
|
parseFilter(params.FilterLessThan, func(f, v string) squirrel.Sqlizer { return squirrel.Lt{f: v} })
|
|
parseFilter(params.FilterLessOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.LtOrEq{f: v} })
|
|
offset := v(params.PageOffset)
|
|
limit := v(params.PageLimit)
|
|
return model.QueryOptions{Max: int(limit), Offset: int(offset), Filters: filters}
|
|
}
|
|
|
|
func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
|
|
var res ErrorObject
|
|
switch err {
|
|
case model.ErrNotAuthorized:
|
|
res = ErrorObject{Status: p(strconv.Itoa(http.StatusForbidden)), Title: p(http.StatusText(http.StatusForbidden))}
|
|
case model.ErrNotFound:
|
|
res = ErrorObject{Status: p(strconv.Itoa(http.StatusNotFound)), Title: p(http.StatusText(http.StatusNotFound))}
|
|
default:
|
|
res = ErrorObject{Status: p(strconv.Itoa(http.StatusInternalServerError)), Title: p(http.StatusText(http.StatusInternalServerError))}
|
|
}
|
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
|
w.WriteHeader(403)
|
|
|
|
json.NewEncoder(w).Encode(ErrorList{[]ErrorObject{res}})
|
|
}
|
|
|
|
func validationErrorHandler(w http.ResponseWriter, message string, statusCode int) {
|
|
_ = GetTracks400JSONResponse{BadRequestJSONResponse{Errors: []ErrorObject{
|
|
{
|
|
Status: p(strconv.Itoa(statusCode)),
|
|
Title: p(http.StatusText(statusCode)),
|
|
Detail: p(message),
|
|
},
|
|
}}}.VisitGetTracksResponse(w)
|
|
}
|
|
|
|
func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resourceName string) (PaginationLinks, PaginationMeta) {
|
|
pageLimit := *params.PageLimit
|
|
pageOffset := *params.PageOffset
|
|
|
|
totalPages := (totalItems + pageLimit - 1) / pageLimit
|
|
currentPage := pageOffset/pageLimit + 1
|
|
|
|
meta := PaginationMeta{
|
|
CurrentPage: ¤tPage,
|
|
TotalItems: &totalItems,
|
|
TotalPages: &totalPages,
|
|
}
|
|
|
|
var first, last, next, prev *string
|
|
|
|
buildLink := func(page int32) *string {
|
|
query := url.Values{}
|
|
query.Add("page[offset]", strconv.Itoa(int(page*pageLimit)))
|
|
query.Add("page[limit]", strconv.Itoa(int(pageLimit)))
|
|
|
|
addFilterParams := func(paramName string, values *[]string) {
|
|
if values == nil {
|
|
return
|
|
}
|
|
for _, value := range *values {
|
|
query.Add(paramName, value)
|
|
}
|
|
}
|
|
|
|
addFilterParams("filter[equals]", params.FilterEquals)
|
|
addFilterParams("filter[contains]", params.FilterContains)
|
|
addFilterParams("filter[lessThan]", params.FilterLessThan)
|
|
addFilterParams("filter[lessOrEqual]", params.FilterLessOrEqual)
|
|
addFilterParams("filter[greaterThan]", params.FilterGreaterThan)
|
|
addFilterParams("filter[greaterOrEqual]", params.FilterGreaterOrEqual)
|
|
addFilterParams("filter[startsWith]", params.FilterStartsWith)
|
|
addFilterParams("filter[endsWith]", params.FilterEndsWith)
|
|
|
|
if params.Sort != nil {
|
|
query.Add("sort", string(*params.Sort))
|
|
}
|
|
if params.Include != nil {
|
|
query.Add("include", string(*params.Include))
|
|
}
|
|
|
|
link := resourceName
|
|
if len(query) > 0 {
|
|
link += "&" + query.Encode()
|
|
}
|
|
return &link
|
|
}
|
|
|
|
if totalPages > 0 {
|
|
firstLink := buildLink(0)
|
|
first = firstLink
|
|
|
|
lastLink := buildLink(totalPages - 1)
|
|
last = lastLink
|
|
}
|
|
|
|
if currentPage < totalPages {
|
|
nextLink := buildLink(currentPage)
|
|
next = nextLink
|
|
}
|
|
|
|
if currentPage > 1 {
|
|
prevLink := buildLink(currentPage - 2)
|
|
prev = prevLink
|
|
}
|
|
|
|
links := PaginationLinks{
|
|
First: first,
|
|
Last: last,
|
|
Next: next,
|
|
Prev: prev,
|
|
}
|
|
|
|
return links, meta
|
|
}
|