mirror of
https://github.com/navidrome/navidrome.git
synced 2026-01-01 11:28:04 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9f5625abf | ||
|
|
22d57a7c26 | ||
|
|
0c5bf18d80 | ||
|
|
9b7d1757e7 | ||
|
|
c34a5dcb07 | ||
|
|
90a1e6d213 | ||
|
|
482350c076 | ||
|
|
64388b2d4a | ||
|
|
3007ca68d5 | ||
|
|
d4edff3aaa | ||
|
|
99b1dc1421 | ||
|
|
37dfe4c092 |
@@ -19,7 +19,7 @@ builds:
|
||||
flags:
|
||||
- -tags=embed
|
||||
ldflags:
|
||||
- -X main.gitSha={{.ShortCommit}} -X main.gitTag={{.Tag}}
|
||||
- -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Tag}}
|
||||
|
||||
- id: navidrome_linux
|
||||
env:
|
||||
@@ -32,7 +32,7 @@ builds:
|
||||
- -tags=embed
|
||||
ldflags:
|
||||
- "-extldflags '-static'"
|
||||
- -X main.gitSha={{.ShortCommit}} -X main.gitTag={{.Tag}}
|
||||
- -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Tag}}
|
||||
|
||||
- id: navidrome_windows_i686
|
||||
env:
|
||||
@@ -47,7 +47,7 @@ builds:
|
||||
- -tags=embed
|
||||
ldflags:
|
||||
- "-extldflags '-static'"
|
||||
- -X main.gitSha={{.ShortCommit}} -X main.gitTag={{.Tag}}
|
||||
- -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Tag}}
|
||||
|
||||
- id: navidrome_windows_x64
|
||||
env:
|
||||
@@ -62,7 +62,7 @@ builds:
|
||||
- -tags=embed
|
||||
ldflags:
|
||||
- "-extldflags '-static'"
|
||||
- -X main.gitSha={{.ShortCommit}} -X main.gitTag={{.Tag}}
|
||||
- -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Tag}}
|
||||
|
||||
archives:
|
||||
-
|
||||
@@ -84,4 +84,5 @@ changelog:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^chore:'
|
||||
- '^ci:'
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@@ -18,8 +18,7 @@ RUN apk add -U --no-cache build-base git
|
||||
RUN go get -u github.com/go-bindata/go-bindata/...
|
||||
|
||||
# Download and unpack static ffmpeg
|
||||
ARG FFMPEG_VERSION=4.1.4
|
||||
ARG FFMPEG_URL=https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-${FFMPEG_VERSION}-amd64-static.tar.xz
|
||||
ARG FFMPEG_URL=https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
|
||||
RUN wget -O /tmp/ffmpeg.tar.xz ${FFMPEG_URL}
|
||||
RUN cd /tmp && tar xJf ffmpeg.tar.xz && rm ffmpeg.tar.xz
|
||||
|
||||
@@ -28,30 +27,41 @@ WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy source and UI bundle, build executable
|
||||
# Copy source, test it
|
||||
COPY . .
|
||||
RUN go test ./...
|
||||
|
||||
# Copy UI bundle, build executable
|
||||
COPY --from=jsbuilder /src/build/* /src/ui/build/
|
||||
COPY --from=jsbuilder /src/build/static/css/* /src/ui/build/static/css/
|
||||
COPY --from=jsbuilder /src/build/static/js/* /src/ui/build/static/js/
|
||||
RUN rm -rf /src/build/css /src/build/js
|
||||
RUN GIT_SHA=$(git rev-parse --short HEAD) && \
|
||||
GIT_TAG=$(git describe --tags --abbrev=0 2> /dev/null) && \
|
||||
RUN GIT_TAG=$(git name-rev --name-only HEAD) && \
|
||||
GIT_TAG=${GIT_TAG#"tags/"} && \
|
||||
GIT_SHA=$(git rev-parse --short HEAD) && \
|
||||
echo "Building version: ${GIT_TAG} (${GIT_SHA})" && \
|
||||
go-bindata -fs -prefix ui/build -tags embed -nocompress -pkg assets -o assets/embedded_gen.go ui/build/... && \
|
||||
go build -ldflags="-X main.gitSha=${GIT_SHA} -X main.gitTag=${GIT_TAG}" -tags=embed
|
||||
go build -ldflags="-X github.com/deluan/navidrome/consts.gitSha=${GIT_SHA} -X github.com/deluan/navidrome/consts.gitTag=${GIT_TAG}" -tags=embed
|
||||
|
||||
#####################################################
|
||||
### Build Final Image
|
||||
FROM alpine
|
||||
FROM alpine as release
|
||||
MAINTAINER Deluan Quintao <navidrome@deluan.com>
|
||||
|
||||
COPY --from=gobuilder /src/navidrome /app/
|
||||
COPY --from=gobuilder /tmp/ffmpeg*/ffmpeg /usr/bin/
|
||||
|
||||
# Check if ffmpeg runs properly
|
||||
RUN ffmpeg -buildconf
|
||||
|
||||
VOLUME ["/data", "/music"]
|
||||
ENV ND_MUSICFOLDER /music
|
||||
ENV ND_DATAFOLDER /data
|
||||
ENV ND_SCANINTERVAL 1m
|
||||
ENV ND_LOGLEVEL info
|
||||
ENV ND_PORT 4533
|
||||
EXPOSE 4533
|
||||
|
||||
EXPOSE 4533
|
||||
WORKDIR /app
|
||||
CMD "/app/navidrome"
|
||||
|
||||
ENTRYPOINT "/app/navidrome"
|
||||
|
||||
32
Makefile
32
Makefile
@@ -4,11 +4,11 @@ NODE_VERSION=v13.7.0
|
||||
GIT_SHA=$(shell git rev-parse --short HEAD)
|
||||
|
||||
.PHONY: dev
|
||||
dev: check_env data
|
||||
dev: check_env
|
||||
@goreman -f Procfile.dev -b 4533 start
|
||||
|
||||
.PHONY: server
|
||||
server: check_go_env data
|
||||
server: check_go_env
|
||||
@reflex -d none -c reflex.conf
|
||||
|
||||
.PHONY: watch
|
||||
@@ -26,12 +26,13 @@ testall: check_go_env test
|
||||
|
||||
.PHONY: setup
|
||||
setup: Jamstash-master
|
||||
@which reflex || (echo "Installing Reflex" && GO111MODULE=off go get -u github.com/cespare/reflex)
|
||||
@which goconvey || (echo "Installing GoConvey" && GO111MODULE=off go get -u github.com/smartystreets/goconvey)
|
||||
@which wire || (echo "Installing Wire" && GO111MODULE=off go get -u github.com/google/wire/cmd/wire)
|
||||
@which go-bindata || (echo "Installing BinData" && GO111MODULE=off go get -u github.com/go-bindata/go-bindata/...)
|
||||
@which reflex || (echo "Installing Reflex" && GO111MODULE=off go get -u github.com/cespare/reflex)
|
||||
@which goreman || (echo "Installing Goreman" && GO111MODULE=off go get -u github.com/mattn/goreman)
|
||||
@which ginkgo || (echo "Installing Ginkgo" && GO111MODULE=off go get -u github.com/onsi/ginkgo/ginkgo)
|
||||
@which go-bindata || (echo "Installing BinData" && GO111MODULE=off go get -u github.com/go-bindata/go-bindata/...)
|
||||
@which goose || (echo "Installing Goose" && GO111MODULE=off go get -u github.com/pressly/goose/cmd/goose)
|
||||
go mod download
|
||||
@(cd ./ui && npm ci)
|
||||
|
||||
@@ -57,9 +58,6 @@ check_node_env:
|
||||
@(hash node) || (echo "\nERROR: Node environment not setup properly!\n"; exit 1)
|
||||
@node --version | grep -q $(NODE_VERSION) || (echo "\nERROR: Please check your Node version. Should be $(NODE_VERSION)\n"; exit 1)
|
||||
|
||||
data:
|
||||
mkdir data
|
||||
|
||||
UI_SRC = $(shell find ui/src ui/public -name "*.js")
|
||||
ui/build: $(UI_SRC) $(UI_PUBLIC) ui/package-lock.json
|
||||
@(cd ./ui && npm run build)
|
||||
@@ -69,8 +67,22 @@ assets/embedded_gen.go: ui/build
|
||||
|
||||
.PHONY: build
|
||||
build: check_go_env
|
||||
go build -ldflags="-X main.gitSha=$(GIT_SHA) -X main.gitTag=master"
|
||||
go build -ldflags="-X github.com/deluan/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/deluan/navidrome/consts.gitTag=master"
|
||||
|
||||
.PHONY: buildall
|
||||
buildall: check_go_env assets/embedded_gen.go
|
||||
go build -ldflags="-X main.gitSha=$(GIT_SHA) -X main.gitTag=master" -tags=embed
|
||||
buildall: check_env assets/embedded_gen.go
|
||||
go build -ldflags="-X github.com/deluan/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/deluan/navidrome/consts.gitTag=master" -tags=embed
|
||||
|
||||
.PHONY: release
|
||||
release:
|
||||
@if [[ ! "${V}" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$$ ]]; then echo "Usage: make release V=X.X.X"; exit 1; fi
|
||||
go mod tidy
|
||||
make test
|
||||
@if [ -n "`git status -s`" ]; then echo "\n\nThere are pending changes. Please commit or stash first"; exit 1; fi
|
||||
git tag v${V}
|
||||
git push origin v${V}
|
||||
git push origin master
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
docker run -it -v $(PWD):/github/workspace -w /github/workspace bepsays/ci-goreleaser:1.13-4 goreleaser release --rm-dist --skip-publish --snapshot
|
||||
|
||||
17
README.md
17
README.md
@@ -80,16 +80,19 @@ services:
|
||||
- "/Users/deluan/Music/iTunes/iTunes Media/Music:/music"
|
||||
```
|
||||
|
||||
### Build it yourself
|
||||
### Build from source
|
||||
|
||||
You will need to install [Go 1.13](https://golang.org/dl/) and [Node 13.7](http://nodejs.org).
|
||||
You'll also need [ffmpeg](https://ffmpeg.org) installed in your system
|
||||
You will need to install [Go 1.13](https://golang.org/dl/) and [Node 13.7.0](http://nodejs.org).
|
||||
You'll also need [ffmpeg](https://ffmpeg.org) installed in your system. The setup is very strict, and
|
||||
the steps bellow only work with these specific versions (enforced in the Makefile)
|
||||
|
||||
After the prerequisites above are installed, build the application with:
|
||||
After the prerequisites above are installed, clone this repository and build the application with:
|
||||
|
||||
```
|
||||
$ make setup
|
||||
$ make buildall
|
||||
```shell script
|
||||
$ git clone https://github.com/deluan/navidrome
|
||||
$ cd navidrome
|
||||
$ make setup # Install tools required for Navidrome's development
|
||||
$ make buildall # Build UI and server, generates a single executable
|
||||
```
|
||||
|
||||
This will generate the `navidrome` binary executable in the project's root folder.
|
||||
|
||||
20
banner.go
20
banner.go
@@ -4,33 +4,17 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/deluan/navidrome/consts"
|
||||
"github.com/deluan/navidrome/static"
|
||||
)
|
||||
|
||||
var (
|
||||
// This will be set in build time. If not, version will be set to "dev"
|
||||
gitTag string
|
||||
gitSha string
|
||||
)
|
||||
|
||||
// Formats:
|
||||
// dev
|
||||
// v0.2.0 (5b84188)
|
||||
// master (9ed35cb)
|
||||
func getVersion() string {
|
||||
if gitSha == "" {
|
||||
return "dev"
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", gitTag, gitSha)
|
||||
}
|
||||
|
||||
func getBanner() string {
|
||||
data, _ := static.Asset("banner.txt")
|
||||
return strings.TrimSuffix(string(data), "\n")
|
||||
}
|
||||
|
||||
func ShowBanner() {
|
||||
version := "Version: " + getVersion()
|
||||
version := "Version: " + consts.Version()
|
||||
padding := strings.Repeat(" ", 52-len(version))
|
||||
fmt.Printf("%s%s%s\n\n", getBanner(), padding, version)
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ func newWithPath(path string, skipFlags ...bool) *multiconfig.DefaultLoader {
|
||||
if strings.HasSuffix(path, "yml") || strings.HasSuffix(path, "yaml") {
|
||||
loaders = append(loaders, &multiconfig.YAMLLoader{Path: path})
|
||||
}
|
||||
} else {
|
||||
println("Skipping config file not found: ", path)
|
||||
}
|
||||
|
||||
e := &multiconfig.EnvironmentLoader{}
|
||||
|
||||
@@ -3,6 +3,8 @@ package consts
|
||||
import "time"
|
||||
|
||||
const (
|
||||
AppName = "navidrome"
|
||||
|
||||
LocalConfigFile = "./navidrome.toml"
|
||||
InitialSetupFlagKey = "InitialSetup"
|
||||
|
||||
@@ -10,7 +12,5 @@ const (
|
||||
JWTIssuer = "ND"
|
||||
JWTTokenExpiration = 30 * time.Minute
|
||||
|
||||
InitialUserName = "admin"
|
||||
|
||||
UIAssetsLocalPath = "ui/build"
|
||||
)
|
||||
|
||||
20
consts/version.go
Normal file
20
consts/version.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package consts
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
// This will be set in build time. If not, version will be set to "dev"
|
||||
gitTag string
|
||||
gitSha string
|
||||
)
|
||||
|
||||
// Formats:
|
||||
// dev
|
||||
// v0.2.0 (5b84188)
|
||||
// master (9ed35cb)
|
||||
func Version() string {
|
||||
if gitSha == "" {
|
||||
return "dev"
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", gitTag, gitSha)
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package engine
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/deluan/navidrome/consts"
|
||||
"github.com/deluan/navidrome/model"
|
||||
"github.com/deluan/navidrome/utils"
|
||||
)
|
||||
@@ -52,12 +51,11 @@ func (p *playlists) Create(ctx context.Context, playlistId, name string, ids []s
|
||||
}
|
||||
|
||||
func (p *playlists) getUser(ctx context.Context) string {
|
||||
owner := consts.InitialUserName
|
||||
user, ok := ctx.Value("user").(*model.User)
|
||||
if ok {
|
||||
owner = user.UserName
|
||||
return user.UserName
|
||||
}
|
||||
return owner
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *playlists) Delete(ctx context.Context, playlistId string) error {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/deluan/navidrome/log"
|
||||
"github.com/deluan/navidrome/model"
|
||||
)
|
||||
|
||||
@@ -49,11 +48,12 @@ func (u *users) Authenticate(ctx context.Context, username, pass, token, salt st
|
||||
if !valid {
|
||||
return nil, model.ErrInvalidAuth
|
||||
}
|
||||
go func() {
|
||||
err := u.ds.User(ctx).UpdateLastAccessAt(user.ID)
|
||||
if err != nil {
|
||||
log.Error(ctx, "Could not update user's lastAccessAt", "user", user.UserName)
|
||||
}
|
||||
}()
|
||||
// TODO: Find a way to update LastAccessAt without causing too much retention in the DB
|
||||
//go func() {
|
||||
// err := u.ds.User(ctx).UpdateLastAccessAt(user.ID)
|
||||
// if err != nil {
|
||||
// log.Error(ctx, "Could not update user's lastAccessAt", "user", user.UserName)
|
||||
// }
|
||||
//}()
|
||||
return user, nil
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -21,7 +21,7 @@ require (
|
||||
github.com/koding/multiconfig v0.0.0-20170327155832-26b6dfd3a84a
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
|
||||
github.com/onsi/ginkgo v1.11.0
|
||||
github.com/onsi/gomega v1.8.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -85,8 +85,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
|
||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
||||
@@ -83,6 +83,7 @@ func probe(inputs []string) (map[string]*Metadata, error) {
|
||||
infos := parseOutput(string(output))
|
||||
for file, info := range infos {
|
||||
md, err := extractMetadata(file, info)
|
||||
// Skip files with errors
|
||||
if err == nil {
|
||||
mds[file] = md
|
||||
}
|
||||
@@ -117,9 +118,16 @@ func parseOutput(output string) map[string]string {
|
||||
func extractMetadata(filePath, info string) (*Metadata, error) {
|
||||
m := &Metadata{filePath: filePath, tags: map[string]string{}}
|
||||
m.suffix = strings.ToLower(strings.TrimPrefix(path.Ext(filePath), "."))
|
||||
var err error
|
||||
m.fileInfo, err = os.Stat(filePath)
|
||||
if err != nil {
|
||||
log.Warn("Error stating file. Skipping", "filePath", filePath, err)
|
||||
return nil, errors.New("error stating file")
|
||||
}
|
||||
|
||||
m.parseInfo(info)
|
||||
m.fileInfo, _ = os.Stat(filePath)
|
||||
if len(m.tags) == 0 {
|
||||
log.Trace("Not a media file. Skipping", "filePath", filePath)
|
||||
return nil, errors.New("not a media file")
|
||||
}
|
||||
return m, nil
|
||||
|
||||
@@ -109,7 +109,7 @@ func CreateAdmin(ds model.DataStore) func(w http.ResponseWriter, r *http.Request
|
||||
|
||||
func createDefaultUser(ctx context.Context, ds model.DataStore, username, password string) error {
|
||||
id, _ := uuid.NewRandom()
|
||||
log.Warn("Creating initial user", "user", consts.InitialUserName)
|
||||
log.Warn("Creating initial user", "user", username)
|
||||
initialUser := model.User{
|
||||
ID: id.String(),
|
||||
UserName: username,
|
||||
|
||||
@@ -151,11 +151,12 @@ func HGone(r chi.Router, path string) {
|
||||
}
|
||||
|
||||
func SendError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
response := &responses.Subsonic{Version: Version, Status: "fail"}
|
||||
response := NewResponse()
|
||||
code := responses.ErrorGeneric
|
||||
if e, ok := err.(SubsonicError); ok {
|
||||
code = e.code
|
||||
}
|
||||
response.Status = "fail"
|
||||
response.Error = &responses.Error{Code: code, Message: err.Error()}
|
||||
|
||||
SendResponse(w, r, response)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/deluan/navidrome/consts"
|
||||
"github.com/deluan/navidrome/engine"
|
||||
"github.com/deluan/navidrome/model"
|
||||
"github.com/deluan/navidrome/server/subsonic/responses"
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func NewResponse() *responses.Subsonic {
|
||||
return &responses.Subsonic{Status: "ok", Version: Version}
|
||||
return &responses.Subsonic{Status: "ok", Version: Version, Type: consts.AppName, ServerVersion: consts.Version()}
|
||||
}
|
||||
|
||||
func RequiredParamString(r *http.Request, param string, msg string) (string, error) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","albumList":{"album":[{"id":"1","isDir":false,"title":"title"}]}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","albumList":{"album":[{"id":"1","isDir":false,"title":"title"}]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><albumList><album id="1" isDir="false" title="title"></album></albumList></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><albumList><album id="1" isDir="false" title="title"></album></albumList></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","albumList":{}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","albumList":{}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><albumList></albumList></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><albumList></albumList></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","directory":{"child":[{"id":"1","isDir":true,"title":"title","album":"album","artist":"artist","track":1,"year":1985,"genre":"Rock","coverArt":"1","size":"8421341","contentType":"audio/flac","suffix":"flac","starred":"2016-03-02T20:30:00Z","transcodedContentType":"audio/mpeg","transcodedSuffix":"mp3","duration":146,"bitRate":320}],"id":"1","name":"N"}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","directory":{"child":[{"id":"1","isDir":true,"title":"title","album":"album","artist":"artist","track":1,"year":1985,"genre":"Rock","coverArt":"1","size":"8421341","contentType":"audio/flac","suffix":"flac","starred":"2016-03-02T20:30:00Z","transcodedContentType":"audio/mpeg","transcodedSuffix":"mp3","duration":146,"bitRate":320}],"id":"1","name":"N"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><directory id="1" name="N"><child id="1" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320"></child></directory></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><directory id="1" name="N"><child id="1" isDir="true" title="title" album="album" artist="artist" track="1" year="1985" genre="Rock" coverArt="1" size="8421341" contentType="audio/flac" suffix="flac" starred="2016-03-02T20:30:00Z" transcodedContentType="audio/mpeg" transcodedSuffix="mp3" duration="146" bitRate="320"></child></directory></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","directory":{"child":[{"id":"1","isDir":false,"title":"title"}],"id":"1","name":"N"}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","directory":{"child":[{"id":"1","isDir":false,"title":"title"}],"id":"1","name":"N"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><directory id="1" name="N"><child id="1" isDir="false" title="title"></child></directory></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><directory id="1" name="N"><child id="1" isDir="false" title="title"></child></directory></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","directory":{"id":"1","name":"N"}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","directory":{"id":"1","name":"N"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><directory id="1" name="N"></directory></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><directory id="1" name="N"></directory></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0"}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","genres":{"genre":[{"value":"Rock","songCount":1000,"albumCount":100},{"value":"Reggae","songCount":500,"albumCount":50},{"value":"Pop","songCount":0,"albumCount":0}]}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","genres":{"genre":[{"value":"Rock","songCount":1000,"albumCount":100},{"value":"Reggae","songCount":500,"albumCount":50},{"value":"Pop","songCount":0,"albumCount":0}]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><genres><genre songCount="1000" albumCount="100">Rock</genre><genre songCount="500" albumCount="50">Reggae</genre><genre songCount="0" albumCount="0">Pop</genre></genres></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><genres><genre songCount="1000" albumCount="100">Rock</genre><genre songCount="500" albumCount="50">Reggae</genre><genre songCount="0" albumCount="0">Pop</genre></genres></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","genres":{}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","genres":{}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><genres></genres></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><genres></genres></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","indexes":{"index":[{"name":"A","artist":[{"id":"111","name":"aaa","starred":"2016-03-02T20:30:00Z"}]}],"lastModified":"1","ignoredArticles":"A"}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","indexes":{"index":[{"name":"A","artist":[{"id":"111","name":"aaa","starred":"2016-03-02T20:30:00Z"}]}],"lastModified":"1","ignoredArticles":"A"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><indexes lastModified="1" ignoredArticles="A"><index name="A"><artist id="111" name="aaa" starred="2016-03-02T20:30:00Z"></artist></index></indexes></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><indexes lastModified="1" ignoredArticles="A"><index name="A"><artist id="111" name="aaa" starred="2016-03-02T20:30:00Z"></artist></index></indexes></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","indexes":{"lastModified":"1","ignoredArticles":"A"}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","indexes":{"lastModified":"1","ignoredArticles":"A"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><indexes lastModified="1" ignoredArticles="A"></indexes></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><indexes lastModified="1" ignoredArticles="A"></indexes></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","license":{"valid":true}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","license":{"valid":true}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><license valid="true"></license></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><license valid="true"></license></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","musicFolders":{"musicFolder":[{"id":"111","name":"aaa"},{"id":"222","name":"bbb"}]}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","musicFolders":{"musicFolder":[{"id":"111","name":"aaa"},{"id":"222","name":"bbb"}]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><musicFolders><musicFolder id="111" name="aaa"></musicFolder><musicFolder id="222" name="bbb"></musicFolder></musicFolders></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><musicFolders><musicFolder id="111" name="aaa"></musicFolder><musicFolder id="222" name="bbb"></musicFolder></musicFolders></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","musicFolders":{}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","musicFolders":{}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><musicFolders></musicFolders></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><musicFolders></musicFolders></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","playlists":{"playlist":[{"id":"111","name":"aaa"},{"id":"222","name":"bbb"}]}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","playlists":{"playlist":[{"id":"111","name":"aaa"},{"id":"222","name":"bbb"}]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><playlists><playlist id="111" name="aaa"></playlist><playlist id="222" name="bbb"></playlist></playlists></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><playlists><playlist id="111" name="aaa"></playlist><playlist id="222" name="bbb"></playlist></playlists></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","playlists":{}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","playlists":{}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><playlists></playlists></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><playlists></playlists></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","user":{"username":"deluan","email":"navidrome@deluan.com","scrobblingEnabled":false,"adminRole":false,"settingsRole":false,"downloadRole":false,"uploadRole":false,"playlistRole":false,"coverArtRole":false,"commentRole":false,"podcastRole":false,"streamRole":false,"jukeboxRole":false,"shareRole":false,"videoConversionRole":false,"folder":[1]}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","user":{"username":"deluan","email":"navidrome@deluan.com","scrobblingEnabled":false,"adminRole":false,"settingsRole":false,"downloadRole":false,"uploadRole":false,"playlistRole":false,"coverArtRole":false,"commentRole":false,"podcastRole":false,"streamRole":false,"jukeboxRole":false,"shareRole":false,"videoConversionRole":false,"folder":[1]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><user username="deluan" email="navidrome@deluan.com" scrobblingEnabled="false" adminRole="false" settingsRole="false" downloadRole="false" uploadRole="false" playlistRole="false" coverArtRole="false" commentRole="false" podcastRole="false" streamRole="false" jukeboxRole="false" shareRole="false" videoConversionRole="false"><folder>1</folder></user></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><user username="deluan" email="navidrome@deluan.com" scrobblingEnabled="false" adminRole="false" settingsRole="false" downloadRole="false" uploadRole="false" playlistRole="false" coverArtRole="false" commentRole="false" podcastRole="false" streamRole="false" jukeboxRole="false" shareRole="false" videoConversionRole="false"><folder>1</folder></user></subsonic-response>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","user":{"username":"deluan","scrobblingEnabled":false,"adminRole":false,"settingsRole":false,"downloadRole":false,"uploadRole":false,"playlistRole":false,"coverArtRole":false,"commentRole":false,"podcastRole":false,"streamRole":false,"jukeboxRole":false,"shareRole":false,"videoConversionRole":false}}
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","user":{"username":"deluan","scrobblingEnabled":false,"adminRole":false,"settingsRole":false,"downloadRole":false,"uploadRole":false,"playlistRole":false,"coverArtRole":false,"commentRole":false,"podcastRole":false,"streamRole":false,"jukeboxRole":false,"shareRole":false,"videoConversionRole":false}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><user username="deluan" scrobblingEnabled="false" adminRole="false" settingsRole="false" downloadRole="false" uploadRole="false" playlistRole="false" coverArtRole="false" commentRole="false" podcastRole="false" streamRole="false" jukeboxRole="false" shareRole="false" videoConversionRole="false"></user></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><user username="deluan" scrobblingEnabled="false" adminRole="false" settingsRole="false" downloadRole="false" uploadRole="false" playlistRole="false" coverArtRole="false" commentRole="false" podcastRole="false" streamRole="false" jukeboxRole="false" shareRole="false" videoConversionRole="false"></user></subsonic-response>
|
||||
|
||||
@@ -9,6 +9,8 @@ type Subsonic struct {
|
||||
XMLName xml.Name `xml:"http://subsonic.org/restapi subsonic-response" json:"-"`
|
||||
Status string `xml:"status,attr" json:"status"`
|
||||
Version string `xml:"version,attr" json:"version"`
|
||||
Type string `xml:"type,attr" json:"type"`
|
||||
ServerVersion string `xml:"serverVersion,attr" json:"serverVersion"`
|
||||
Error *Error `xml:"error,omitempty" json:"error,omitempty"`
|
||||
License *License `xml:"license,omitempty" json:"license,omitempty"`
|
||||
MusicFolders *MusicFolders `xml:"musicFolders,omitempty" json:"musicFolders,omitempty"`
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
"github.com/deluan/navidrome/consts"
|
||||
. "github.com/deluan/navidrome/server/subsonic/responses"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
var _ = Describe("Responses", func() {
|
||||
var response *Subsonic
|
||||
BeforeEach(func() {
|
||||
response = &Subsonic{Status: "ok", Version: "1.8.0"}
|
||||
response = &Subsonic{Status: "ok", Version: "1.8.0", Type: consts.AppName, ServerVersion: "v0.0.0"}
|
||||
})
|
||||
|
||||
Describe("EmptyResponse", func() {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="Navidrome Music Server"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
|
||||
@@ -23,7 +23,7 @@ const UserEdit = (props) => (
|
||||
<PasswordInput source="password" validate={[required()]} />
|
||||
<BooleanInput source="isAdmin" initialValue={false} />
|
||||
<DateField source="lastLoginAt" showTime />
|
||||
<DateField source="lastAccessAt" showTime />
|
||||
{/*<DateField source="lastAccessAt" showTime />*/}
|
||||
<DateField source="updatedAt" showTime />
|
||||
<DateField source="createdAt" showTime />
|
||||
</SimpleForm>
|
||||
|
||||
@@ -33,8 +33,7 @@ const UserList = (props) => {
|
||||
<SimpleList
|
||||
primaryText={(record) => record.name}
|
||||
secondaryText={(record) =>
|
||||
record.lastAccessAt &&
|
||||
new Date(record.lastAccessAt).toLocaleString()
|
||||
record.lastLoginAt && new Date(record.lastLoginAt).toLocaleString()
|
||||
}
|
||||
tertiaryText={(record) => (record.isAdmin ? '[admin]️' : '')}
|
||||
/>
|
||||
@@ -42,7 +41,7 @@ const UserList = (props) => {
|
||||
<Datagrid rowClick="edit">
|
||||
<TextField source="userName" />
|
||||
<BooleanField source="isAdmin" />
|
||||
<DateField source="lastAccessAt" locales="pt-BR" />
|
||||
<DateField source="lastLoginAt" locales="pt-BR" />
|
||||
<DateField source="updatedAt" locales="pt-BR" />
|
||||
</Datagrid>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user