mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-04 13:07:36 +03:00
Create initial user if User table is empty. Also make model.QueryOptions compatible with rest.QueryOptions
This commit is contained in:
parent
2ab0cecd48
commit
491bfb1f69
11 changed files with 130 additions and 38 deletions
|
@ -38,8 +38,8 @@ $ make
|
||||||
The server should start listening for requests. The default configuration is:
|
The server should start listening for requests. The default configuration is:
|
||||||
|
|
||||||
- Port: `4533`
|
- Port: `4533`
|
||||||
- User: `anyone`
|
- User: `admin`
|
||||||
- Password: `wordpass`
|
- Password: `admin`
|
||||||
|
|
||||||
To override this or any other configuration, create a file named `sonic.toml` in the project folder.
|
To override this or any other configuration, create a file named `sonic.toml` in the project folder.
|
||||||
For all options see the [configuration.go](conf/configuration.go) file
|
For all options see the [configuration.go](conf/configuration.go) file
|
||||||
|
|
|
@ -15,8 +15,8 @@ type sonic struct {
|
||||||
IgnoredArticles string `default:"The El La Los Las Le Les Os As O A"`
|
IgnoredArticles string `default:"The El La Los Las Le Les Os As O A"`
|
||||||
IndexGroups string `default:"A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ) [Unknown]([)"`
|
IndexGroups string `default:"A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ) [Unknown]([)"`
|
||||||
|
|
||||||
User string `default:"anyone"`
|
User string `default:"admin"`
|
||||||
Password string `default:"wordpass"`
|
Password string `default:"admin"`
|
||||||
|
|
||||||
DisableDownsampling bool `default:"false"`
|
DisableDownsampling bool `default:"false"`
|
||||||
DownsampleCommand string `default:"ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -f mp3 -"`
|
DownsampleCommand string `default:"ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -f mp3 -"`
|
||||||
|
|
|
@ -31,42 +31,42 @@ type listGenerator struct {
|
||||||
npRepo NowPlayingRepository
|
npRepo NowPlayingRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Only return albums that have the SortBy field != empty
|
// TODO: Only return albums that have the Sort field != empty
|
||||||
func (g *listGenerator) query(qo model.QueryOptions, offset int, size int) (Entries, error) {
|
func (g *listGenerator) query(qo model.QueryOptions, offset int, size int) (Entries, error) {
|
||||||
qo.Offset = offset
|
qo.Offset = offset
|
||||||
qo.Size = size
|
qo.Max = size
|
||||||
albums, err := g.ds.Album().GetAll(qo)
|
albums, err := g.ds.Album().GetAll(qo)
|
||||||
|
|
||||||
return FromAlbums(albums), err
|
return FromAlbums(albums), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetNewest(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetNewest(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{SortBy: "CreatedAt", Desc: true}
|
qo := model.QueryOptions{Sort: "CreatedAt", Order: "desc"}
|
||||||
return g.query(qo, offset, size)
|
return g.query(qo, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetRecent(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetRecent(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{SortBy: "PlayDate", Desc: true}
|
qo := model.QueryOptions{Sort: "PlayDate", Order: "desc"}
|
||||||
return g.query(qo, offset, size)
|
return g.query(qo, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetFrequent(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetFrequent(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{SortBy: "PlayCount", Desc: true}
|
qo := model.QueryOptions{Sort: "PlayCount", Order: "desc"}
|
||||||
return g.query(qo, offset, size)
|
return g.query(qo, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetHighest(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetHighest(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{SortBy: "Rating", Desc: true}
|
qo := model.QueryOptions{Sort: "Rating", Order: "desc"}
|
||||||
return g.query(qo, offset, size)
|
return g.query(qo, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetByName(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetByName(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{SortBy: "Name"}
|
qo := model.QueryOptions{Sort: "Name"}
|
||||||
return g.query(qo, offset, size)
|
return g.query(qo, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetByArtist(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetByArtist(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{SortBy: "Artist"}
|
qo := model.QueryOptions{Sort: "Artist"}
|
||||||
return g.query(qo, offset, size)
|
return g.query(qo, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ func (g *listGenerator) GetRandomSongs(size int) (Entries, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
|
||||||
qo := model.QueryOptions{Offset: offset, Size: size, SortBy: "starred_at", Desc: true}
|
qo := model.QueryOptions{Offset: offset, Max: size, Sort: "starred_at", Order: "desc"}
|
||||||
albums, err := g.ds.Album().GetStarred(qo)
|
albums, err := g.ds.Album().GetStarred(qo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -122,7 +122,7 @@ func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
|
||||||
|
|
||||||
// TODO Return is confusing
|
// TODO Return is confusing
|
||||||
func (g *listGenerator) GetAllStarred() (Entries, Entries, Entries, error) {
|
func (g *listGenerator) GetAllStarred() (Entries, Entries, Entries, error) {
|
||||||
artists, err := g.ds.Artist().GetStarred(model.QueryOptions{SortBy: "starred_at", Desc: true})
|
artists, err := g.ds.Artist().GetStarred(model.QueryOptions{Sort: "starred_at", Order: "desc"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (g *listGenerator) GetAllStarred() (Entries, Entries, Entries, error) {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaFiles, err := g.ds.MediaFile().GetStarred(model.QueryOptions{SortBy: "starred_at", Desc: true})
|
mediaFiles, err := g.ds.MediaFile().GetStarred(model.QueryOptions{Sort: "starred_at", Order: "desc"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,12 @@ var (
|
||||||
// Ex: var q = QueryOptions{Filters: Filters{"name__istartswith": "Deluan","age__gt": 25}}
|
// Ex: var q = QueryOptions{Filters: Filters{"name__istartswith": "Deluan","age__gt": 25}}
|
||||||
// All conditions will be ANDed together
|
// All conditions will be ANDed together
|
||||||
// TODO Implement filter in repositories' methods
|
// TODO Implement filter in repositories' methods
|
||||||
type Filters map[string]interface{}
|
|
||||||
|
|
||||||
type QueryOptions struct {
|
type QueryOptions struct {
|
||||||
SortBy string
|
Sort string
|
||||||
Desc bool
|
Order string
|
||||||
|
Max int
|
||||||
Offset int
|
Offset int
|
||||||
Size int
|
Filters map[string]interface{}
|
||||||
Filters Filters
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceRepository interface {
|
type ResourceRepository interface {
|
||||||
|
@ -37,6 +35,7 @@ type DataStore interface {
|
||||||
Genre() GenreRepository
|
Genre() GenreRepository
|
||||||
Playlist() PlaylistRepository
|
Playlist() PlaylistRepository
|
||||||
Property() PropertyRepository
|
Property() PropertyRepository
|
||||||
|
User() UserRepository
|
||||||
|
|
||||||
Resource(model interface{}) ResourceRepository
|
Resource(model interface{}) ResourceRepository
|
||||||
|
|
||||||
|
|
|
@ -12,3 +12,9 @@ type User struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserRepository interface {
|
||||||
|
CountAll(...QueryOptions) (int64, error)
|
||||||
|
Get(id string) (*User, error)
|
||||||
|
Put(*User) error
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ var _ = Describe("AlbumRepository", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns all records sorted", func() {
|
It("returns all records sorted", func() {
|
||||||
Expect(repo.GetAll(model.QueryOptions{SortBy: "Name"})).To(Equal(model.Albums{
|
Expect(repo.GetAll(model.QueryOptions{Sort: "Name"})).To(Equal(model.Albums{
|
||||||
albumAbbeyRoad,
|
albumAbbeyRoad,
|
||||||
albumRadioactivity,
|
albumRadioactivity,
|
||||||
albumSgtPeppers,
|
albumSgtPeppers,
|
||||||
|
@ -28,7 +28,7 @@ var _ = Describe("AlbumRepository", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns all records sorted desc", func() {
|
It("returns all records sorted desc", func() {
|
||||||
Expect(repo.GetAll(model.QueryOptions{SortBy: "Name", Desc: true})).To(Equal(model.Albums{
|
Expect(repo.GetAll(model.QueryOptions{Sort: "Name", Order: "desc"})).To(Equal(model.Albums{
|
||||||
albumSgtPeppers,
|
albumSgtPeppers,
|
||||||
albumRadioactivity,
|
albumRadioactivity,
|
||||||
albumAbbeyRoad,
|
albumAbbeyRoad,
|
||||||
|
@ -36,7 +36,7 @@ var _ = Describe("AlbumRepository", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("paginates the result", func() {
|
It("paginates the result", func() {
|
||||||
Expect(repo.GetAll(model.QueryOptions{Offset: 1, Size: 1})).To(Equal(model.Albums{
|
Expect(repo.GetAll(model.QueryOptions{Offset: 1, Max: 1})).To(Equal(model.Albums{
|
||||||
albumAbbeyRoad,
|
albumAbbeyRoad,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
|
@ -49,6 +49,10 @@ func (db *MockDataStore) Property() model.PropertyRepository {
|
||||||
return struct{ model.PropertyRepository }{}
|
return struct{ model.PropertyRepository }{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *MockDataStore) User() model.UserRepository {
|
||||||
|
return struct{ model.UserRepository }{}
|
||||||
|
}
|
||||||
|
|
||||||
func (db *MockDataStore) WithTx(block func(db model.DataStore) error) error {
|
func (db *MockDataStore) WithTx(block func(db model.DataStore) error) error {
|
||||||
return block(db)
|
return block(db)
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,8 +69,12 @@ func (db *SQLStore) Property() model.PropertyRepository {
|
||||||
return NewPropertyRepository(db.getOrmer())
|
return NewPropertyRepository(db.getOrmer())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *SQLStore) User() model.UserRepository {
|
||||||
|
return NewUserRepository(db.getOrmer())
|
||||||
|
}
|
||||||
|
|
||||||
func (db *SQLStore) Resource(model interface{}) model.ResourceRepository {
|
func (db *SQLStore) Resource(model interface{}) model.ResourceRepository {
|
||||||
return NewResource(db.getOrmer(), model, mappedModels[model])
|
return NewResource(db.getOrmer(), model, getMappedModel(model))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *SQLStore) WithTx(block func(tx model.DataStore) error) error {
|
func (db *SQLStore) WithTx(block func(tx model.DataStore) error) error {
|
||||||
|
@ -129,19 +133,31 @@ func collectField(collection interface{}, getValue func(item interface{}) string
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getType(myvar interface{}) string {
|
||||||
|
if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr {
|
||||||
|
return t.Elem().Name()
|
||||||
|
} else {
|
||||||
|
return t.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func registerModel(model interface{}, mappedModel interface{}) {
|
func registerModel(model interface{}, mappedModel interface{}) {
|
||||||
mappedModels[model] = mappedModel
|
mappedModels[getType(model)] = mappedModel
|
||||||
orm.RegisterModel(mappedModel)
|
orm.RegisterModel(mappedModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMappedModel(model interface{}) interface{} {
|
||||||
|
return mappedModels[getType(model)]
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mappedModels = map[interface{}]interface{}{}
|
mappedModels = map[interface{}]interface{}{}
|
||||||
|
|
||||||
registerModel(new(model.Artist), new(artist))
|
registerModel(model.Artist{}, new(artist))
|
||||||
registerModel(new(model.Album), new(album))
|
registerModel(model.Album{}, new(album))
|
||||||
registerModel(new(model.MediaFile), new(mediaFile))
|
registerModel(model.MediaFile{}, new(mediaFile))
|
||||||
registerModel(new(model.Property), new(property))
|
registerModel(model.Property{}, new(property))
|
||||||
registerModel(new(model.Playlist), new(playlist))
|
registerModel(model.Playlist{}, new(playlist))
|
||||||
registerModel(model.User{}, new(user))
|
registerModel(model.User{}, new(user))
|
||||||
|
|
||||||
orm.RegisterModel(new(checksum))
|
orm.RegisterModel(new(checksum))
|
||||||
|
|
|
@ -16,14 +16,14 @@ func (r *sqlRepository) newQuery(options ...model.QueryOptions) orm.QuerySeter {
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
opts := options[0]
|
opts := options[0]
|
||||||
q = q.Offset(opts.Offset)
|
q = q.Offset(opts.Offset)
|
||||||
if opts.Size > 0 {
|
if opts.Max > 0 {
|
||||||
q = q.Limit(opts.Size)
|
q = q.Limit(opts.Max)
|
||||||
}
|
}
|
||||||
if opts.SortBy != "" {
|
if opts.Sort != "" {
|
||||||
if opts.Desc {
|
if opts.Order == "desc" {
|
||||||
q = q.OrderBy("-" + opts.SortBy)
|
q = q.OrderBy("-" + opts.Sort)
|
||||||
} else {
|
} else {
|
||||||
q = q.OrderBy(opts.SortBy)
|
q = q.OrderBy(opts.Sort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,15 @@ package persistence
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/cloudsonic/sonic-server/model"
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
|
"github.com/deluan/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type user struct {
|
type user struct {
|
||||||
ID string `json:"id" orm:"pk;column(id)"`
|
ID string `json:"id" orm:"pk;column(id)"`
|
||||||
Name string `json:"name" orm:"index"`
|
Name string `json:"name" orm:"index"`
|
||||||
Password string `json:"-"`
|
Password string `json:"password"`
|
||||||
IsAdmin bool `json:"isAdmin"`
|
IsAdmin bool `json:"isAdmin"`
|
||||||
LastLoginAt *time.Time `json:"lastLoginAt" orm:"null"`
|
LastLoginAt *time.Time `json:"lastLoginAt" orm:"null"`
|
||||||
LastAccessAt *time.Time `json:"lastAccessAt" orm:"null"`
|
LastAccessAt *time.Time `json:"lastAccessAt" orm:"null"`
|
||||||
|
@ -17,4 +19,45 @@ type user struct {
|
||||||
UpdatedAt time.Time `json:"updatedAt" orm:"auto_now;type(datetime)"`
|
UpdatedAt time.Time `json:"updatedAt" orm:"auto_now;type(datetime)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userRepository struct {
|
||||||
|
ormer orm.Ormer
|
||||||
|
userResource model.ResourceRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userRepository) CountAll(qo ...model.QueryOptions) (int64, error) {
|
||||||
|
if len(qo) > 0 {
|
||||||
|
return r.userResource.Count(rest.QueryOptions(qo[0]))
|
||||||
|
}
|
||||||
|
return r.userResource.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userRepository) Get(id string) (*model.User, error) {
|
||||||
|
u, err := r.userResource.Read(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := model.User(u.(user))
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userRepository) Put(u *model.User) error {
|
||||||
|
c, err := r.CountAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c == 0 {
|
||||||
|
mappedUser := user(*u)
|
||||||
|
_, err = r.userResource.Save(&mappedUser)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.userResource.Update(u, "name", "is_admin", "password")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRepository(o orm.Ormer) model.UserRepository {
|
||||||
|
r := &userRepository{ormer: o}
|
||||||
|
r.userResource = NewResource(o, model.User{}, new(user))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
var _ = model.User(user{})
|
var _ = model.User(user{})
|
||||||
|
var _ model.UserRepository = (*userRepository)(nil)
|
||||||
|
|
|
@ -2,16 +2,21 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cloudsonic/sonic-server/log"
|
||||||
"github.com/cloudsonic/sonic-server/model"
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
"github.com/cloudsonic/sonic-server/server"
|
"github.com/cloudsonic/sonic-server/server"
|
||||||
"github.com/deluan/rest"
|
"github.com/deluan/rest"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const initialUser = "admin"
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
ds model.DataStore
|
ds model.DataStore
|
||||||
mux http.Handler
|
mux http.Handler
|
||||||
|
@ -21,6 +26,7 @@ type Router struct {
|
||||||
func New(ds model.DataStore, path string) *Router {
|
func New(ds model.DataStore, path string) *Router {
|
||||||
r := &Router{ds: ds, path: path}
|
r := &Router{ds: ds, path: path}
|
||||||
r.mux = r.routes()
|
r.mux = r.routes()
|
||||||
|
r.createDefaultUser()
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +52,24 @@ func (app *Router) routes() http.Handler {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *Router) createDefaultUser() {
|
||||||
|
c, err := app.ds.User().CountAll()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not access User table: %s", err))
|
||||||
|
}
|
||||||
|
if c == 0 {
|
||||||
|
id, _ := uuid.NewRandom()
|
||||||
|
initialPassword, _ := uuid.NewRandom()
|
||||||
|
log.Warn("Creating initial user. Please change the password!", "user", initialUser, "password", initialPassword)
|
||||||
|
app.ds.User().Put(&model.User{
|
||||||
|
ID: id.String(),
|
||||||
|
Name: initialUser,
|
||||||
|
Password: initialPassword.String(),
|
||||||
|
IsAdmin: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func R(r chi.Router, pathPrefix string, newRepository rest.RepositoryConstructor) {
|
func R(r chi.Router, pathPrefix string, newRepository rest.RepositoryConstructor) {
|
||||||
r.Route(pathPrefix, func(r chi.Router) {
|
r.Route(pathPrefix, func(r chi.Router) {
|
||||||
r.Get("/", rest.GetAll(newRepository))
|
r.Get("/", rest.GetAll(newRepository))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue