Create initial user if User table is empty. Also make model.QueryOptions compatible with rest.QueryOptions

This commit is contained in:
Deluan 2020-01-19 22:36:15 -05:00
parent 2ab0cecd48
commit 491bfb1f69
11 changed files with 130 additions and 38 deletions

View File

@ -38,8 +38,8 @@ $ make
The server should start listening for requests. The default configuration is:
- Port: `4533`
- User: `anyone`
- Password: `wordpass`
- User: `admin`
- Password: `admin`
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

View File

@ -15,8 +15,8 @@ type sonic struct {
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]([)"`
User string `default:"anyone"`
Password string `default:"wordpass"`
User string `default:"admin"`
Password string `default:"admin"`
DisableDownsampling bool `default:"false"`
DownsampleCommand string `default:"ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -f mp3 -"`

View File

@ -31,42 +31,42 @@ type listGenerator struct {
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) {
qo.Offset = offset
qo.Size = size
qo.Max = size
albums, err := g.ds.Album().GetAll(qo)
return FromAlbums(albums), err
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
@ -111,7 +111,7 @@ func (g *listGenerator) GetRandomSongs(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)
if err != nil {
return nil, err
@ -122,7 +122,7 @@ func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
// TODO Return is confusing
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 {
return nil, nil, nil, err
}
@ -132,7 +132,7 @@ func (g *listGenerator) GetAllStarred() (Entries, Entries, Entries, error) {
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 {
return nil, nil, nil, err
}

View File

@ -14,14 +14,12 @@ var (
// Ex: var q = QueryOptions{Filters: Filters{"name__istartswith": "Deluan","age__gt": 25}}
// All conditions will be ANDed together
// TODO Implement filter in repositories' methods
type Filters map[string]interface{}
type QueryOptions struct {
SortBy string
Desc bool
Sort string
Order string
Max int
Offset int
Size int
Filters Filters
Filters map[string]interface{}
}
type ResourceRepository interface {
@ -37,6 +35,7 @@ type DataStore interface {
Genre() GenreRepository
Playlist() PlaylistRepository
Property() PropertyRepository
User() UserRepository
Resource(model interface{}) ResourceRepository

View File

@ -12,3 +12,9 @@ type User struct {
CreatedAt time.Time
UpdatedAt time.Time
}
type UserRepository interface {
CountAll(...QueryOptions) (int64, error)
Get(id string) (*User, error)
Put(*User) error
}

View File

@ -20,7 +20,7 @@ var _ = Describe("AlbumRepository", 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,
albumRadioactivity,
albumSgtPeppers,
@ -28,7 +28,7 @@ var _ = Describe("AlbumRepository", 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,
albumRadioactivity,
albumAbbeyRoad,
@ -36,7 +36,7 @@ var _ = Describe("AlbumRepository", 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,
}))
})

View File

@ -49,6 +49,10 @@ func (db *MockDataStore) Property() 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 {
return block(db)
}

View File

@ -69,8 +69,12 @@ func (db *SQLStore) Property() model.PropertyRepository {
return NewPropertyRepository(db.getOrmer())
}
func (db *SQLStore) User() model.UserRepository {
return NewUserRepository(db.getOrmer())
}
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 {
@ -129,19 +133,31 @@ func collectField(collection interface{}, getValue func(item interface{}) string
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{}) {
mappedModels[model] = mappedModel
mappedModels[getType(model)] = mappedModel
orm.RegisterModel(mappedModel)
}
func getMappedModel(model interface{}) interface{} {
return mappedModels[getType(model)]
}
func init() {
mappedModels = map[interface{}]interface{}{}
registerModel(new(model.Artist), new(artist))
registerModel(new(model.Album), new(album))
registerModel(new(model.MediaFile), new(mediaFile))
registerModel(new(model.Property), new(property))
registerModel(new(model.Playlist), new(playlist))
registerModel(model.Artist{}, new(artist))
registerModel(model.Album{}, new(album))
registerModel(model.MediaFile{}, new(mediaFile))
registerModel(model.Property{}, new(property))
registerModel(model.Playlist{}, new(playlist))
registerModel(model.User{}, new(user))
orm.RegisterModel(new(checksum))

View File

@ -16,14 +16,14 @@ func (r *sqlRepository) newQuery(options ...model.QueryOptions) orm.QuerySeter {
if len(options) > 0 {
opts := options[0]
q = q.Offset(opts.Offset)
if opts.Size > 0 {
q = q.Limit(opts.Size)
if opts.Max > 0 {
q = q.Limit(opts.Max)
}
if opts.SortBy != "" {
if opts.Desc {
q = q.OrderBy("-" + opts.SortBy)
if opts.Sort != "" {
if opts.Order == "desc" {
q = q.OrderBy("-" + opts.Sort)
} else {
q = q.OrderBy(opts.SortBy)
q = q.OrderBy(opts.Sort)
}
}
}

View File

@ -3,13 +3,15 @@ package persistence
import (
"time"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/model"
"github.com/deluan/rest"
)
type user struct {
ID string `json:"id" orm:"pk;column(id)"`
Name string `json:"name" orm:"index"`
Password string `json:"-"`
Password string `json:"password"`
IsAdmin bool `json:"isAdmin"`
LastLoginAt *time.Time `json:"lastLoginAt" 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)"`
}
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.UserRepository = (*userRepository)(nil)

View File

@ -2,16 +2,21 @@ package app
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
"github.com/cloudsonic/sonic-server/server"
"github.com/deluan/rest"
"github.com/go-chi/chi"
"github.com/google/uuid"
)
const initialUser = "admin"
type Router struct {
ds model.DataStore
mux http.Handler
@ -21,6 +26,7 @@ type Router struct {
func New(ds model.DataStore, path string) *Router {
r := &Router{ds: ds, path: path}
r.mux = r.routes()
r.createDefaultUser()
return r
}
@ -46,6 +52,24 @@ func (app *Router) routes() http.Handler {
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) {
r.Route(pathPrefix, func(r chi.Router) {
r.Get("/", rest.GetAll(newRepository))