Commit f133a9de by Torkel Ödegaard

Merge branch 'master' of github.com:torkelo/grafana-pro

Conflicts:
	grafana
parents b876d193 31fe471d
Subproject commit 3944c37627326a14c8d39d8f2f25617681e3f074 Subproject commit 9b2476451ef341285e1387c6eefe97c7995e300a
go get code.google.com/p/goprotobuf/{proto,protoc-gen-go}
...@@ -51,8 +51,8 @@ func (self *HttpServer) ListenAndServe() { ...@@ -51,8 +51,8 @@ func (self *HttpServer) ListenAndServe() {
} }
// register default route // register default route
self.router.GET("/", self.authMiddleware(), self.index) self.router.GET("/", self.auth(), self.index)
self.router.GET("/dashboard/*_", self.authMiddleware(), self.index) self.router.GET("/dashboard/*_", self.auth(), self.index)
self.router.Run(":" + self.port) self.router.Run(":" + self.port)
} }
......
package api package api
import ( import (
log "github.com/alecthomas/log4go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/torkelo/grafana-pro/pkg/models" "github.com/torkelo/grafana-pro/pkg/models"
) )
func init() { func init() {
addRoutes(func(self *HttpServer) { addRoutes(func(self *HttpServer) {
self.router.GET("/api/dashboards/:id", self.getDashboard) self.router.GET("/api/dashboards/:id", self.auth(), self.getDashboard)
self.router.GET("/api/search/", self.search) self.router.GET("/api/search/", self.auth(), self.search)
self.router.POST("/api/dashboard", self.postDashboard) self.router.POST("/api/dashboard", self.auth(), self.postDashboard)
}) })
} }
func (self *HttpServer) getDashboard(c *gin.Context) { func (self *HttpServer) getDashboard(c *gin.Context) {
id := c.Params.ByName("id") id := c.Params.ByName("id")
accountId, err := c.Get("accountId")
dash, err := self.store.GetById(id) dash, err := self.store.GetDashboard(id, accountId.(int))
if err != nil { if err != nil {
c.JSON(404, newErrorResponse("Dashboard not found")) c.JSON(404, newErrorResponse("Dashboard not found"))
return return
...@@ -30,6 +32,7 @@ func (self *HttpServer) search(c *gin.Context) { ...@@ -30,6 +32,7 @@ func (self *HttpServer) search(c *gin.Context) {
results, err := self.store.Query(query) results, err := self.store.Query(query)
if err != nil { if err != nil {
log.Error("Store query error: %v", err)
c.JSON(500, newErrorResponse("Failed")) c.JSON(500, newErrorResponse("Failed"))
return return
} }
...@@ -41,9 +44,19 @@ func (self *HttpServer) postDashboard(c *gin.Context) { ...@@ -41,9 +44,19 @@ func (self *HttpServer) postDashboard(c *gin.Context) {
var command saveDashboardCommand var command saveDashboardCommand
if c.EnsureBody(&command) { if c.EnsureBody(&command) {
err := self.store.Save(&models.Dashboard{Data: command.Dashboard}) dashboard := models.NewDashboard("test")
dashboard.Data = command.Dashboard
dashboard.Title = dashboard.Data["title"].(string)
dashboard.AccountId = 1
dashboard.UpdateSlug()
if dashboard.Data["id"] != nil {
dashboard.Id = dashboard.Data["id"].(string)
}
err := self.store.SaveDashboard(dashboard)
if err == nil { if err == nil {
c.JSON(200, gin.H{"status": "saved"}) c.JSON(200, gin.H{"status": "success", "slug": dashboard.Slug})
return return
} }
} }
......
...@@ -19,18 +19,28 @@ type loginJsonModel struct { ...@@ -19,18 +19,28 @@ type loginJsonModel struct {
func (self *HttpServer) loginPost(c *gin.Context) { func (self *HttpServer) loginPost(c *gin.Context) {
var loginModel loginJsonModel var loginModel loginJsonModel
if c.EnsureBody(&loginModel) { if !c.EnsureBody(&loginModel) {
if loginModel.Email == "manu" && loginModel.Password == "123" { c.JSON(400, gin.H{"status": "bad request"})
return
}
account, err := self.store.GetUserAccountLogin(loginModel.Email)
if err != nil {
c.JSON(400, gin.H{"status": "some error"})
}
if loginModel.Password != account.Password {
c.JSON(401, gin.H{"status": "unauthorized"})
return
}
session, _ := sessionStore.Get(c.Request, "grafana-session") session, _ := sessionStore.Get(c.Request, "grafana-session")
session.Values["login"] = true session.Values["login"] = true
session.Values["accountId"] = account.DatabaseId
session.Save(c.Request, c.Writer) session.Save(c.Request, c.Writer)
c.JSON(200, gin.H{"status": "you are logged in"}) c.JSON(200, gin.H{"status": "you are logged in"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
}
} }
func (self *HttpServer) logoutPost(c *gin.Context) { func (self *HttpServer) logoutPost(c *gin.Context) {
...@@ -41,15 +51,18 @@ func (self *HttpServer) logoutPost(c *gin.Context) { ...@@ -41,15 +51,18 @@ func (self *HttpServer) logoutPost(c *gin.Context) {
c.JSON(200, gin.H{"status": "logged out"}) c.JSON(200, gin.H{"status": "logged out"})
} }
func (self *HttpServer) authMiddleware() gin.HandlerFunc { func (self *HttpServer) auth() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
session, _ := sessionStore.Get(c.Request, "grafana-session") session, _ := sessionStore.Get(c.Request, "grafana-session")
if c.Request.URL.Path != "/login" && session.Values["login"] == nil { if c.Request.URL.Path != "/login" && session.Values["login"] == nil {
c.Writer.Header().Set("Location", "/login") c.Writer.Header().Set("Location", "/login")
c.Abort(302) c.Abort(302)
return
} }
c.Set("accountId", session.Values["accountId"])
session.Save(c.Request, c.Writer) session.Save(c.Request, c.Writer)
} }
} }
package api
import (
log "github.com/alecthomas/log4go"
"github.com/gin-gonic/gin"
"github.com/torkelo/grafana-pro/pkg/models"
)
func init() {
addRoutes(func(self *HttpServer) {
self.router.GET("/register/*_", self.index)
self.router.POST("/api/register/user", self.registerUserPost)
})
}
type registerAccountJsonModel struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
Password2 bool `json:"remember2"`
}
func (self *HttpServer) registerUserPost(c *gin.Context) {
var registerModel registerAccountJsonModel
if !c.EnsureBody(&registerModel) {
c.JSON(400, gin.H{"status": "bad request"})
return
}
account := models.UserAccount{
UserName: registerModel.Email,
Login: registerModel.Email,
Email: registerModel.Email,
Password: registerModel.Password,
}
err := self.store.SaveUserAccount(&account)
if err != nil {
log.Error("Failed to create user account, email: %v, error: %v", registerModel.Email, err)
c.JSON(500, gin.H{"status": "failed to create account"})
return
}
c.JSON(200, gin.H{"status": "ok"})
}
package models
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardModel(t *testing.T) {
Convey("When generating slug", t, func() {
dashboard := NewDashboard("Grafana Play Home")
dashboard.UpdateSlug()
So(dashboard.Slug, ShouldEqual, "grafana-play-home")
})
}
...@@ -3,22 +3,65 @@ package models ...@@ -3,22 +3,65 @@ package models
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"regexp"
"strings"
"time"
) )
type Dashboard struct { type Dashboard struct {
Id string `gorethink:"id,omitempty"`
Slug string
AccountId int
LastModifiedByUserId string
LastModifiedByDate time.Time
CreatedDate time.Time
Title string
Tags []string
Data map[string]interface{} Data map[string]interface{}
} }
type UserAccountLink struct {
UserId int
Role string
ModifiedOn time.Time
CreatedOn time.Time
}
type UserAccount struct {
DatabaseId int `gorethink:"id"`
UserName string
Login string
Email string
Password string
NextDashboardId int
UsingAccountId int
GrantedAccess []UserAccountLink
CreatedOn time.Time
ModifiedOn time.Time
}
type UserContext struct {
UserId string
AccountId string
}
type SearchResult struct { type SearchResult struct {
Type string `json:"title"`
Id string `json:"id"` Id string `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Slug string `json:"slug"`
} }
func NewDashboard(title string) *Dashboard { func NewDashboard(title string) *Dashboard {
dash := &Dashboard{} dash := &Dashboard{}
dash.Id = ""
dash.LastModifiedByDate = time.Now()
dash.CreatedDate = time.Now()
dash.LastModifiedByUserId = "123"
dash.Data = make(map[string]interface{}) dash.Data = make(map[string]interface{})
dash.Data["title"] = title dash.Data["title"] = title
dash.Title = title
dash.UpdateSlug()
return dash return dash
} }
...@@ -34,20 +77,13 @@ func NewFromJson(reader io.Reader) (*Dashboard, error) { ...@@ -34,20 +77,13 @@ func NewFromJson(reader io.Reader) (*Dashboard, error) {
return dash, nil return dash, nil
} }
/*type DashboardServices struct {
}
type DashboardServicesFilter struct {
}
type DashboardServicesFilterTime struct {
From string To string
}*/
func (dash *Dashboard) GetString(prop string) string { func (dash *Dashboard) GetString(prop string) string {
return dash.Data[prop].(string) return dash.Data[prop].(string)
} }
func (dash *Dashboard) Title() string { func (dash *Dashboard) UpdateSlug() {
return dash.GetString("title") title := strings.ToLower(dash.Data["title"].(string))
re := regexp.MustCompile("[^\\w ]+")
re2 := regexp.MustCompile("\\s")
dash.Slug = re2.ReplaceAllString(re.ReplaceAllString(title, ""), "-")
} }
package stores package stores
import ( //
"encoding/json" // import (
"io" // "encoding/json"
"os" // "io"
"path/filepath" // "os"
"strings" // "path/filepath"
// "strings"
log "github.com/alecthomas/log4go" //
"github.com/torkelo/grafana-pro/pkg/models" // log "github.com/alecthomas/log4go"
) // "github.com/torkelo/grafana-pro/pkg/models"
// )
type fileStore struct { //
dataDir string // type fileStore struct {
dashDir string // dataDir string
cache map[string]*models.Dashboard // dashDir string
} // cache map[string]*models.Dashboard
// }
func NewFileStore(dataDir string) *fileStore { //
// func NewFileStore(dataDir string) *fileStore {
if dirDoesNotExist(dataDir) { //
log.Crashf("FileStore failed to initialize, dataDir does not exist %v", dataDir) // if dirDoesNotExist(dataDir) {
} // log.Crashf("FileStore failed to initialize, dataDir does not exist %v", dataDir)
// }
dashDir := filepath.Join(dataDir, "dashboards") //
// dashDir := filepath.Join(dataDir, "dashboards")
if dirDoesNotExist(dashDir) { //
log.Debug("Did not find dashboard dir, creating...") // if dirDoesNotExist(dashDir) {
err := os.Mkdir(dashDir, 0777) // log.Debug("Did not find dashboard dir, creating...")
if err != nil { // err := os.Mkdir(dashDir, 0777)
log.Crashf("FileStore failed to initialize, could not create directory %v, error: %v", dashDir, err) // if err != nil {
} // log.Crashf("FileStore failed to initialize, could not create directory %v, error: %v", dashDir, err)
} // }
// }
store := &fileStore{} //
store.dataDir = dataDir // store := &fileStore{}
store.dashDir = dashDir // store.dataDir = dataDir
store.cache = make(map[string]*models.Dashboard) // store.dashDir = dashDir
go store.scanFiles() // store.cache = make(map[string]*models.Dashboard)
// store.scanFiles()
return store //
} // return store
// }
func (store *fileStore) scanFiles() { //
visitor := func(path string, f os.FileInfo, err error) error { // func (store *fileStore) scanFiles() {
if err != nil { // visitor := func(path string, f os.FileInfo, err error) error {
return err // if err != nil {
} // return err
if f.IsDir() { // }
return nil // if f.IsDir() {
} // return nil
if strings.HasSuffix(f.Name(), ".json") { // }
err = store.loadDashboardIntoCache(path) // if strings.HasSuffix(f.Name(), ".json") {
if err != nil { // err = store.loadDashboardIntoCache(path)
return err // if err != nil {
} // return err
} // }
return nil // }
} // return nil
// }
err := filepath.Walk(store.dashDir, visitor) //
if err != nil { // err := filepath.Walk(store.dashDir, visitor)
log.Error("FileStore::updateCache failed %v", err) // if err != nil {
} // log.Error("FileStore::updateCache failed %v", err)
} // }
// }
func (store fileStore) loadDashboardIntoCache(filename string) error { //
log.Info("Loading dashboard file %v into cache", filename) // func (store fileStore) loadDashboardIntoCache(filename string) error {
dash, err := loadDashboardFromFile(filename) // log.Info("Loading dashboard file %v into cache", filename)
if err != nil { // dash, err := loadDashboardFromFile(filename)
return err // if err != nil {
} // return err
// }
store.cache[dash.Title()] = dash //
// store.cache[dash.Title] = dash
return nil //
} // return nil
// }
func (store *fileStore) Close() { //
// func (store *fileStore) Close() {
} //
// }
func (store *fileStore) GetById(id string) (*models.Dashboard, error) { //
log.Debug("FileStore::GetById id = %v", id) // func (store *fileStore) GetById(id string) (*models.Dashboard, error) {
filename := store.getFilePathForDashboard(id) // log.Debug("FileStore::GetById id = %v", id)
// filename := store.getFilePathForDashboard(id)
return loadDashboardFromFile(filename) //
} // return loadDashboardFromFile(filename)
// }
func (store *fileStore) Save(dash *models.Dashboard) error { //
filename := store.getFilePathForDashboard(dash.Title()) // func (store *fileStore) Save(dash *models.Dashboard) error {
// filename := store.getFilePathForDashboard(dash.Title)
log.Debug("Saving dashboard %v to %v", dash.Title(), filename) //
// log.Debug("Saving dashboard %v to %v", dash.Title, filename)
var err error //
var data []byte // var err error
if data, err = json.Marshal(dash.Data); err != nil { // var data []byte
return err // if data, err = json.Marshal(dash.Data); err != nil {
} // return err
// }
return writeFile(filename, data) //
} // return writeFile(filename, data)
// }
func (store *fileStore) Query(query string) ([]*models.SearchResult, error) { //
results := make([]*models.SearchResult, 0, 50) // func (store *fileStore) Query(query string) ([]*models.SearchResult, error) {
// results := make([]*models.SearchResult, 0, 50)
for _, dash := range store.cache { //
item := &models.SearchResult{ // for _, dash := range store.cache {
Id: dash.Title(), // item := &models.SearchResult{
Type: "dashboard", // Id: dash.Title,
} // Type: "dashboard",
results = append(results, item) // }
} // results = append(results, item)
// }
return results, nil //
} // return results, nil
// }
func loadDashboardFromFile(filename string) (*models.Dashboard, error) { //
log.Debug("FileStore::loading dashboard from file %v", filename) // func loadDashboardFromFile(filename string) (*models.Dashboard, error) {
// log.Debug("FileStore::loading dashboard from file %v", filename)
configFile, err := os.Open(filename) //
if err != nil { // configFile, err := os.Open(filename)
return nil, err // if err != nil {
} // return nil, err
// }
return models.NewFromJson(configFile) //
} // return models.NewFromJson(configFile)
// }
func (store *fileStore) getFilePathForDashboard(id string) string { //
id = strings.ToLower(id) // func (store *fileStore) getFilePathForDashboard(id string) string {
id = strings.Replace(id, " ", "-", -1) // id = strings.ToLower(id)
return filepath.Join(store.dashDir, id) + ".json" // id = strings.Replace(id, " ", "-", -1)
} // return filepath.Join(store.dashDir, id) + ".json"
// }
func dirDoesNotExist(dir string) bool { //
_, err := os.Stat(dir) // func dirDoesNotExist(dir string) bool {
return os.IsNotExist(err) // _, err := os.Stat(dir)
} // return os.IsNotExist(err)
// }
func writeFile(filename string, data []byte) error { //
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) // func writeFile(filename string, data []byte) error {
if err != nil { // f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
return err // if err != nil {
} // return err
n, err := f.Write(data) // }
if err == nil && n < len(data) { // n, err := f.Write(data)
err = io.ErrShortWrite // if err == nil && n < len(data) {
} // err = io.ErrShortWrite
if err1 := f.Close(); err == nil { // }
err = err1 // if err1 := f.Close(); err == nil {
} // err = err1
// }
return err //
} // return err
// }
package stores package stores
import ( //
"fmt" // import (
"io" // "fmt"
"io/ioutil" // "io"
"os" // "io/ioutil"
"path/filepath" // "os"
"testing" // "path/filepath"
// "testing"
. "github.com/smartystreets/goconvey/convey" //
"github.com/torkelo/grafana-pro/pkg/models" // . "github.com/smartystreets/goconvey/convey"
) // "github.com/torkelo/grafana-pro/pkg/models"
// )
func TestFileStore(t *testing.T) { //
// func TestFileStore(t *testing.T) {
GivenFileStore("When saving a dashboard", t, func(store *fileStore) { //
dashboard := models.NewDashboard("hello") // GivenFileStore("When saving a dashboard", t, func(store *fileStore) {
// dashboard := models.NewDashboard("hello")
err := store.Save(dashboard) //
// err := store.Save(dashboard)
Convey("should be saved to disk", func() { //
So(err, ShouldBeNil) // Convey("should be saved to disk", func() {
// So(err, ShouldBeNil)
_, err = os.Stat(store.getFilePathForDashboard("hello")) //
So(err, ShouldBeNil) // _, err = os.Stat(store.getFilePathForDashboard("hello"))
}) // So(err, ShouldBeNil)
}) // })
// })
GivenFileStore("When getting a saved dashboard", t, func(store *fileStore) { //
copyDashboardToTempData("default.json", "", store.dashDir) // GivenFileStore("When getting a saved dashboard", t, func(store *fileStore) {
dash, err := store.GetById("default") // copyDashboardToTempData("default.json", "", store.dashDir)
// dash, err := store.GetById("default")
Convey("should be read from disk", func() { //
So(err, ShouldBeNil) // Convey("should be read from disk", func() {
So(dash, ShouldNotBeNil) // So(err, ShouldBeNil)
// So(dash, ShouldNotBeNil)
So(dash.Title(), ShouldEqual, "Grafana Play Home") //
}) // So(dash.Title, ShouldEqual, "Grafana Play Home")
}) // })
// })
GivenFileStore("when getting dashboard with capital letters", t, func(store *fileStore) { //
copyDashboardToTempData("annotations.json", "", store.dashDir) // GivenFileStore("when getting dashboard with capital letters", t, func(store *fileStore) {
dash, err := store.GetById("AnnoTations") // copyDashboardToTempData("annotations.json", "", store.dashDir)
// dash, err := store.GetById("AnnoTations")
Convey("should be read from disk", func() { //
So(err, ShouldBeNil) // Convey("should be read from disk", func() {
So(dash, ShouldNotBeNil) // So(err, ShouldBeNil)
// So(dash, ShouldNotBeNil)
So(dash.Title(), ShouldEqual, "Annotations") //
}) // So(dash.Title, ShouldEqual, "Annotations")
}) // })
// })
GivenFileStore("When copying dashboards into data dir", t, func(store *fileStore) { //
copyDashboardToTempData("annotations.json", "", store.dashDir) // GivenFileStore("When copying dashboards into data dir", t, func(store *fileStore) {
copyDashboardToTempData("default.json", "", store.dashDir) // copyDashboardToTempData("annotations.json", "", store.dashDir)
copyDashboardToTempData("graph-styles.json", "", store.dashDir) // copyDashboardToTempData("default.json", "", store.dashDir)
store.scanFiles() // copyDashboardToTempData("graph-styles.json", "", store.dashDir)
// store.scanFiles()
Convey("scan should generate index of all dashboards", func() { //
// Convey("scan should generate index of all dashboards", func() {
result, err := store.Query("*") //
So(err, ShouldBeNil) // result, err := store.Query("*")
So(len(result), ShouldEqual, 3) // So(err, ShouldBeNil)
}) // So(len(result), ShouldEqual, 3)
}) // })
} // })
// }
func copyDashboardToTempData(name string, destName string, dir string) { //
if destName == "" { // func copyDashboardToTempData(name string, destName string, dir string) {
destName = name // if destName == "" {
} // destName = name
source, _ := filepath.Abs("../../data/dashboards/" + name) // }
dest := filepath.Join(dir, destName) // source, _ := filepath.Abs("../../data/dashboards/" + name)
err := copyFile(dest, source) // dest := filepath.Join(dir, destName)
if err != nil { // err := copyFile(dest, source)
panic(fmt.Sprintf("failed to copy file %v", name)) // if err != nil {
} // panic(fmt.Sprintf("failed to copy file %v", name))
} // }
// }
func GivenFileStore(desc string, t *testing.T, f func(store *fileStore)) { //
Convey(desc, t, func() { // func GivenFileStore(desc string, t *testing.T, f func(store *fileStore)) {
tempDir, _ := ioutil.TempDir("", "store") // Convey(desc, t, func() {
// tempDir, _ := ioutil.TempDir("", "store")
store := NewFileStore(tempDir) //
// store := NewFileStore(tempDir)
f(store) //
// f(store)
Reset(func() { //
os.RemoveAll(tempDir) // Reset(func() {
}) // os.RemoveAll(tempDir)
}) // })
} // })
// }
func copyFile(dst, src string) error { //
in, err := os.Open(src) // func copyFile(dst, src string) error {
if err != nil { // in, err := os.Open(src)
return err // if err != nil {
} // return err
defer in.Close() // }
out, err := os.Create(dst) // defer in.Close()
if err != nil { // out, err := os.Create(dst)
return err // if err != nil {
} // return err
defer out.Close() // }
_, err = io.Copy(out, in) // defer out.Close()
cerr := out.Close() // _, err = io.Copy(out, in)
if err != nil { // cerr := out.Close()
return err // if err != nil {
} // return err
return cerr // }
} // return cerr
// }
package stores
import (
"time"
log "github.com/alecthomas/log4go"
r "github.com/dancannon/gorethink"
"github.com/torkelo/grafana-pro/pkg/models"
)
type rethinkStore struct {
session *r.Session
}
type RethinkCfg struct {
DatabaseName string
}
type Account struct {
Id int `gorethink:"id"`
NextDashboardId int
}
func NewRethinkStore(config *RethinkCfg) *rethinkStore {
log.Info("Initializing rethink storage")
session, err := r.Connect(r.ConnectOpts{
Address: "localhost:28015",
Database: config.DatabaseName,
MaxIdle: 10,
IdleTimeout: time.Second * 10,
})
if err != nil {
log.Crash("Failed to connect to rethink database %v", err)
}
r.DbCreate(config.DatabaseName).Exec(session)
r.Db(config.DatabaseName).TableCreate("dashboards").Exec(session)
r.Db(config.DatabaseName).TableCreate("accounts").Exec(session)
r.Db(config.DatabaseName).TableCreate("master").Exec(session)
r.Db(config.DatabaseName).Table("dashboards").IndexCreateFunc("AccountIdSlug", func(row r.Term) interface{} {
return []interface{}{row.Field("AccountId"), row.Field("Slug")}
}).Exec(session)
r.Db(config.DatabaseName).Table("accounts").IndexCreateFunc("AccountLogin", func(row r.Term) interface{} {
return []interface{}{row.Field("Login")}
}).Exec(session)
_, err = r.Table("master").Insert(map[string]interface{}{"id": "ids", "NextAccountId": 0}).RunWrite(session)
if err != nil {
log.Error("Failed to insert master ids row", err)
}
return &rethinkStore{
session: session,
}
}
func (self *rethinkStore) SaveDashboard(dash *models.Dashboard) error {
resp, err := r.Table("dashboards").Insert(dash, r.InsertOpts{Upsert: true}).RunWrite(self.session)
if err != nil {
return err
}
log.Info("Inserted: %v, Errors: %v, Updated: %v", resp.Inserted, resp.Errors, resp.Updated)
log.Info("First error:", resp.FirstError)
if len(resp.GeneratedKeys) > 0 {
dash.Id = resp.GeneratedKeys[0]
}
return nil
}
func (self *rethinkStore) GetDashboard(slug string, accountId int) (*models.Dashboard, error) {
resp, err := r.Table("dashboards").GetAllByIndex("AccountIdSlug", []interface{}{accountId, slug}).Run(self.session)
if err != nil {
return nil, err
}
var dashboard models.Dashboard
err = resp.One(&dashboard)
if err != nil {
return nil, err
}
return &dashboard, nil
}
func (self *rethinkStore) Query(query string) ([]*models.SearchResult, error) {
docs, err := r.Table("dashboards").Filter(r.Row.Field("Title").Match(".*")).Run(self.session)
if err != nil {
return nil, err
}
results := make([]*models.SearchResult, 0, 50)
var dashboard models.Dashboard
for docs.Next(&dashboard) {
results = append(results, &models.SearchResult{
Title: dashboard.Title,
Id: dashboard.Slug,
})
}
return results, nil
}
func (self *rethinkStore) Close() {}
package stores
import (
"errors"
r "github.com/dancannon/gorethink"
"github.com/torkelo/grafana-pro/pkg/models"
)
func (self *rethinkStore) getNextAccountId() (int, error) {
resp, err := r.Table("master").Get("ids").Update(map[string]interface{}{
"NextAccountId": r.Row.Field("NextAccountId").Add(1),
}, r.UpdateOpts{ReturnVals: true}).RunWrite(self.session)
if err != nil {
return 0, err
}
if resp.NewValue == nil {
return 0, errors.New("Failed to get new value after incrementing account id")
}
return int(resp.NewValue.(map[string]interface{})["NextAccountId"].(float64)), nil
}
func (self *rethinkStore) SaveUserAccount(account *models.UserAccount) error {
accountId, err := self.getNextAccountId()
if err != nil {
return err
}
account.DatabaseId = accountId
resp, err := r.Table("accounts").Insert(account).RunWrite(self.session)
if err != nil {
return err
}
if resp.Inserted == 0 {
return errors.New("Failed to insert acccount")
}
return nil
}
func (self *rethinkStore) GetUserAccountLogin(emailOrName string) (*models.UserAccount, error) {
resp, err := r.Table("accounts").GetAllByIndex("AccountLogin", []interface{}{emailOrName}).Run(self.session)
if err != nil {
return nil, err
}
var account models.UserAccount
err = resp.One(&account)
if err != nil {
return nil, errors.New("Not found")
}
return &account, nil
}
func (self *rethinkStore) getNextDashboardNumber(accountId int) (int, error) {
resp, err := r.Table("accounts").Get(accountId).Update(map[string]interface{}{
"NextDashboardId": r.Row.Field("NextDashboardId").Add(1),
}, r.UpdateOpts{ReturnVals: true}).RunWrite(self.session)
if err != nil {
return 0, err
}
if resp.NewValue == nil {
return 0, errors.New("Failed to get next dashboard id, no new value after update")
}
return int(resp.NewValue.(map[string]interface{})["NextDashboardId"].(float64)), nil
}
package stores
import (
"testing"
"github.com/dancannon/gorethink"
. "github.com/smartystreets/goconvey/convey"
"github.com/torkelo/grafana-pro/pkg/models"
)
func TestRethinkStore(t *testing.T) {
store := NewRethinkStore(&RethinkCfg{DatabaseName: "tests"})
defer gorethink.DbDrop("tests").Exec(store.session)
Convey("Insert dashboard", t, func() {
dashboard := models.NewDashboard("test")
dashboard.AccountId = 1
err := store.SaveDashboard(dashboard)
So(err, ShouldBeNil)
So(dashboard.Id, ShouldNotBeEmpty)
read, err := store.GetDashboard("test", 1)
So(err, ShouldBeNil)
So(read, ShouldNotBeNil)
})
Convey("can get next account id", t, func() {
id, err := store.getNextAccountId()
So(err, ShouldBeNil)
So(id, ShouldNotEqual, 0)
id2, err := store.getNextAccountId()
So(id2, ShouldEqual, id+1)
})
Convey("can create account", t, func() {
account := &models.UserAccount{UserName: "torkelo", Email: "mupp", Login: "test@test.com"}
err := store.SaveUserAccount(account)
So(err, ShouldBeNil)
So(account.DatabaseId, ShouldNotEqual, 0)
read, err := store.GetUserAccountLogin("test@test.com")
So(err, ShouldBeNil)
So(read.DatabaseId, ShouldEqual, account.DatabaseId)
})
Convey("can get next dashboard id", t, func() {
account := &models.UserAccount{UserName: "torkelo", Email: "mupp"}
err := store.SaveUserAccount(account)
dashId, err := store.getNextDashboardNumber(account.DatabaseId)
So(err, ShouldBeNil)
So(dashId, ShouldEqual, 1)
})
}
...@@ -5,12 +5,14 @@ import ( ...@@ -5,12 +5,14 @@ import (
) )
type Store interface { type Store interface {
GetById(id string) (*models.Dashboard, error) GetDashboard(title string, accountId int) (*models.Dashboard, error)
Save(dash *models.Dashboard) error SaveDashboard(dash *models.Dashboard) error
Query(query string) ([]*models.SearchResult, error) Query(query string) ([]*models.SearchResult, error)
SaveUserAccount(acccount *models.UserAccount) error
GetUserAccountLogin(emailOrName string) (*models.UserAccount, error)
Close() Close()
} }
func New() Store { func New() Store {
return NewFileStore("data") return NewRethinkStore(&RethinkCfg{DatabaseName: "grafana"})
} }
docker kill gfdev
docker rm gfdev
docker run -d -p 8180:8080 -p 28015:28015 -p 29015:29015 \
--name rethinkdb \
-v /var/docker/grafana-pro-rethinkdb:/data \
dockerfile/rethinkdb
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment