Commit cd9306df by Torkel Ödegaard

Lots of progress on account management

parent 19a35673
Subproject commit 639a44d99629ba38e67eb04df8c8c9622093de70 Subproject commit e0dc530e943d52453aa06fc47596d3f6f0261f2c
package api package api
import "github.com/gin-gonic/gin" import (
"strconv"
"github.com/gin-gonic/gin"
)
func init() { func init() {
addRoutes(func(self *HttpServer) { addRoutes(func(self *HttpServer) {
self.addRoute("POST", "/api/account/collaborators/add", self.addCollaborator) self.addRoute("POST", "/api/account/collaborators/add", self.addCollaborator)
self.addRoute("POST", "/api/account/collaborators/remove", self.removeCollaborator)
self.addRoute("GET", "/api/account/", self.getAccount) self.addRoute("GET", "/api/account/", self.getAccount)
self.addRoute("GET", "/api/account/others", self.getOtherAccounts)
self.addRoute("POST", "/api/account/using/:id", self.setUsingAccount)
}) })
} }
...@@ -22,44 +29,119 @@ func (self *HttpServer) getAccount(c *gin.Context, auth *authContext) { ...@@ -22,44 +29,119 @@ func (self *HttpServer) getAccount(c *gin.Context, auth *authContext) {
model.Collaborators = append(model.Collaborators, &collaboratorInfoDto{ model.Collaborators = append(model.Collaborators, &collaboratorInfoDto{
AccountId: collaborator.AccountId, AccountId: collaborator.AccountId,
Role: collaborator.Role, Role: collaborator.Role,
Email: collaborator.Email,
}) })
} }
c.JSON(200, model) c.JSON(200, model)
} }
func (self *HttpServer) getOtherAccounts(c *gin.Context, auth *authContext) {
var account = auth.userAccount
otherAccounts, err := self.store.GetOtherAccountsFor(account.Id)
if err != nil {
c.JSON(500, gin.H{"message": err.Error()})
return
}
var result []*otherAccountDto
result = append(result, &otherAccountDto{
Id: account.Id,
Role: "owner",
IsUsing: account.Id == account.UsingAccountId,
Name: account.Email,
})
for _, other := range otherAccounts {
result = append(result, &otherAccountDto{
Id: other.Id,
Role: other.Role,
Name: other.Name,
IsUsing: other.Id == account.UsingAccountId,
})
}
c.JSON(200, result)
}
func (self *HttpServer) addCollaborator(c *gin.Context, auth *authContext) { func (self *HttpServer) addCollaborator(c *gin.Context, auth *authContext) {
var model addCollaboratorDto var model addCollaboratorDto
if !c.EnsureBody(&model) { if !c.EnsureBody(&model) {
c.JSON(400, gin.H{"status": "Collaborator not found"}) c.JSON(400, gin.H{"message": "Invalid request"})
return return
} }
collaborator, err := self.store.GetAccountByLogin(model.Email) collaborator, err := self.store.GetAccountByLogin(model.Email)
if err != nil { if err != nil {
c.JSON(404, gin.H{"status": "Collaborator not found"}) c.JSON(404, gin.H{"message": "Collaborator not found"})
return return
} }
userAccount := auth.userAccount userAccount := auth.userAccount
if collaborator.Id == userAccount.Id { if collaborator.Id == userAccount.Id {
c.JSON(400, gin.H{"status": "Cannot add yourself as collaborator"}) c.JSON(400, gin.H{"message": "Cannot add yourself as collaborator"})
return return
} }
err = userAccount.AddCollaborator(collaborator.Id) err = userAccount.AddCollaborator(collaborator)
if err != nil { if err != nil {
c.JSON(400, gin.H{"status": err.Error()}) c.JSON(400, gin.H{"message": err.Error()})
return return
} }
err = self.store.UpdateAccount(userAccount) err = self.store.UpdateAccount(userAccount)
if err != nil { if err != nil {
c.JSON(500, gin.H{"status": err.Error()}) c.JSON(500, gin.H{"message": err.Error()})
return
}
c.Abort(204)
}
func (self *HttpServer) removeCollaborator(c *gin.Context, auth *authContext) {
var model removeCollaboratorDto
if !c.EnsureBody(&model) {
c.JSON(400, gin.H{"message": "Invalid request"})
return
}
account := auth.userAccount
account.RemoveCollaborator(model.AccountId)
err := self.store.UpdateAccount(account)
if err != nil {
c.JSON(500, gin.H{"message": err.Error()})
return
}
c.Abort(204)
}
func (self *HttpServer) setUsingAccount(c *gin.Context, auth *authContext) {
idString := c.Params.ByName("id")
id, _ := strconv.Atoi(idString)
account := auth.userAccount
otherAccount, err := self.store.GetAccount(id)
if err != nil {
c.JSON(500, gin.H{"message": err.Error()})
return
}
if otherAccount.Id != account.Id && !otherAccount.HasCollaborator(account.Id) {
c.Abort(401)
return
}
account.UsingAccountId = otherAccount.Id
err = self.store.UpdateAccount(account)
if err != nil {
c.JSON(500, gin.H{"message": err.Error()})
return return
} }
c.JSON(200, gin.H{"status": "Collaborator added"}) c.Abort(204)
} }
...@@ -23,18 +23,18 @@ func (self *HttpServer) auth() gin.HandlerFunc { ...@@ -23,18 +23,18 @@ 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["userAccountId"] == nil { if c.Request.URL.Path != "/login" && session.Values["accountId"] == nil {
self.authDenied(c) self.authDenied(c)
return return
} }
account, err := self.store.GetAccount(session.Values["userAccountId"].(int)) account, err := self.store.GetAccount(session.Values["accountId"].(int))
if err != nil { if err != nil {
self.authDenied(c) self.authDenied(c)
return return
} }
usingAccount, err := self.store.GetAccount(session.Values["usingAccountId"].(int)) usingAccount, err := self.store.GetAccount(account.UsingAccountId)
if err != nil { if err != nil {
self.authDenied(c) self.authDenied(c)
return return
......
...@@ -16,3 +16,14 @@ type collaboratorInfoDto struct { ...@@ -16,3 +16,14 @@ type collaboratorInfoDto struct {
type addCollaboratorDto struct { type addCollaboratorDto struct {
Email string `json:"email" binding:"required"` Email string `json:"email" binding:"required"`
} }
type removeCollaboratorDto struct {
AccountId int `json:"accountId" binding:"required"`
}
type otherAccountDto struct {
Id int `json:"id"`
Name string `json:"name"`
Role string `json:"role"`
IsUsing bool `json:"isUsing"`
}
...@@ -26,7 +26,8 @@ func (self *HttpServer) loginPost(c *gin.Context) { ...@@ -26,7 +26,8 @@ func (self *HttpServer) loginPost(c *gin.Context) {
account, err := self.store.GetAccountByLogin(loginModel.Email) account, err := self.store.GetAccountByLogin(loginModel.Email)
if err != nil { if err != nil {
c.JSON(400, gin.H{"status": "some error"}) c.JSON(400, gin.H{"status": err.Error()})
return
} }
if loginModel.Password != account.Password { if loginModel.Password != account.Password {
...@@ -35,8 +36,7 @@ func (self *HttpServer) loginPost(c *gin.Context) { ...@@ -35,8 +36,7 @@ func (self *HttpServer) loginPost(c *gin.Context) {
} }
session, _ := sessionStore.Get(c.Request, "grafana-session") session, _ := sessionStore.Get(c.Request, "grafana-session")
session.Values["userAccountId"] = account.Id session.Values["accountId"] = account.Id
session.Values["usingAccountId"] = account.UsingAccountId
session.Save(c.Request, c.Writer) session.Save(c.Request, c.Writer)
var resp = &LoginResultDto{} var resp = &LoginResultDto{}
......
...@@ -8,10 +8,17 @@ import ( ...@@ -8,10 +8,17 @@ import (
type CollaboratorLink struct { type CollaboratorLink struct {
AccountId int AccountId int
Role string Role string
Email string
ModifiedOn time.Time ModifiedOn time.Time
CreatedOn time.Time CreatedOn time.Time
} }
type OtherAccount struct {
Id int `gorethink:"id"`
Name string
Role string
}
type Account struct { type Account struct {
Id int `gorethink:"id"` Id int `gorethink:"id"`
Version int Version int
...@@ -27,15 +34,16 @@ type Account struct { ...@@ -27,15 +34,16 @@ type Account struct {
LastLoginOn time.Time LastLoginOn time.Time
} }
func (account *Account) AddCollaborator(accountId int) error { func (account *Account) AddCollaborator(newCollaborator *Account) error {
for _, collaborator := range account.Collaborators { for _, collaborator := range account.Collaborators {
if collaborator.AccountId == accountId { if collaborator.AccountId == newCollaborator.Id {
return errors.New("Collaborator already exists") return errors.New("Collaborator already exists")
} }
} }
account.Collaborators = append(account.Collaborators, CollaboratorLink{ account.Collaborators = append(account.Collaborators, CollaboratorLink{
AccountId: accountId, AccountId: newCollaborator.Id,
Email: newCollaborator.Email,
Role: "admin", Role: "admin",
CreatedOn: time.Now(), CreatedOn: time.Now(),
ModifiedOn: time.Now(), ModifiedOn: time.Now(),
...@@ -43,3 +51,22 @@ func (account *Account) AddCollaborator(accountId int) error { ...@@ -43,3 +51,22 @@ func (account *Account) AddCollaborator(accountId int) error {
return nil return nil
} }
func (account *Account) RemoveCollaborator(accountId int) {
list := account.Collaborators
for i, collaborator := range list {
if collaborator.AccountId == accountId {
account.Collaborators = append(list[:i], list[i+1:]...)
break
}
}
}
func (account *Account) HasCollaborator(accountId int) bool {
for _, collaborator := range account.Collaborators {
if collaborator.AccountId == accountId {
return true
}
}
return false
}
package stores package stores
import ( import (
"errors"
"time" "time"
log "github.com/alecthomas/log4go" log "github.com/alecthomas/log4go"
r "github.com/dancannon/gorethink" r "github.com/dancannon/gorethink"
"github.com/torkelo/grafana-pro/pkg/models"
) )
type rethinkStore struct { type rethinkStore struct {
...@@ -36,96 +34,11 @@ func NewRethinkStore(config *RethinkCfg) *rethinkStore { ...@@ -36,96 +34,11 @@ func NewRethinkStore(config *RethinkCfg) *rethinkStore {
log.Crash("Failed to connect to rethink database %v", err) log.Crash("Failed to connect to rethink database %v", err)
} }
r.DbCreate(config.DatabaseName).Exec(session) createRethinkDBTablesAndIndices(config, 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("dashboards").IndexCreateFunc("AccountId", func(row r.Term) interface{} {
return []interface{}{row.Field("AccountId")}
}).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{ return &rethinkStore{
session: session, session: session,
} }
} }
func (self *rethinkStore) SaveDashboard(dash *models.Dashboard) error {
resp, err := r.Table("dashboards").Insert(dash, r.InsertOpts{Conflict: "update"}).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) DeleteDashboard(slug string, accountId int) error {
resp, err := r.Table("dashboards").
GetAllByIndex("AccountIdSlug", []interface{}{accountId, slug}).
Delete().RunWrite(self.session)
if err != nil {
return err
}
if resp.Deleted != 1 {
return errors.New("Did not find dashboard to delete")
}
return nil
}
func (self *rethinkStore) Query(query string, accountId int) ([]*models.SearchResult, error) {
docs, err := r.Table("dashboards").GetAllByIndex("AccountId", []interface{}{accountId}).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() {} func (self *rethinkStore) Close() {}
...@@ -47,7 +47,7 @@ func (self *rethinkStore) CreateAccount(account *models.Account) error { ...@@ -47,7 +47,7 @@ func (self *rethinkStore) CreateAccount(account *models.Account) error {
} }
func (self *rethinkStore) GetAccountByLogin(emailOrName string) (*models.Account, error) { func (self *rethinkStore) GetAccountByLogin(emailOrName string) (*models.Account, error) {
resp, err := r.Table("accounts").GetAllByIndex("AccountLogin", []interface{}{emailOrName}).Run(self.session) resp, err := r.Table("accounts").GetAllByIndex("Login", emailOrName).Run(self.session)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -84,8 +84,8 @@ func (self *rethinkStore) UpdateAccount(account *models.Account) error { ...@@ -84,8 +84,8 @@ func (self *rethinkStore) UpdateAccount(account *models.Account) error {
return err return err
} }
if resp.Replaced != 1 { if resp.Replaced == 0 && resp.Unchanged == 0 {
return errors.New("Could not fund account to uodate") return errors.New("Could not find account to update")
} }
return nil return nil
...@@ -108,3 +108,29 @@ func (self *rethinkStore) getNextDashboardNumber(accountId int) (int, error) { ...@@ -108,3 +108,29 @@ func (self *rethinkStore) getNextDashboardNumber(accountId int) (int, error) {
return int(change.NewValue.(map[string]interface{})["NextDashboardId"].(float64)), nil return int(change.NewValue.(map[string]interface{})["NextDashboardId"].(float64)), nil
} }
func (self *rethinkStore) GetOtherAccountsFor(accountId int) ([]*models.OtherAccount, error) {
resp, err := r.Table("accounts").
GetAllByIndex("CollaboratorAccountId", accountId).
Map(func(row r.Term) interface{} {
return map[string]interface{}{
"id": row.Field("id"),
"Name": row.Field("Email"),
"Role": row.Field("Collaborators").Filter(map[string]interface{}{
"AccountId": accountId,
}).Nth(0).Field("Role"),
}
}).Run(self.session)
if err != nil {
return nil, err
}
var list []*models.OtherAccount
err = resp.All(&list)
if err != nil {
return nil, errors.New("Failed to read available accounts")
}
return list, nil
}
package stores
import (
"errors"
log "github.com/alecthomas/log4go"
r "github.com/dancannon/gorethink"
"github.com/torkelo/grafana-pro/pkg/models"
)
func (self *rethinkStore) SaveDashboard(dash *models.Dashboard) error {
resp, err := r.Table("dashboards").Insert(dash, r.InsertOpts{Conflict: "update"}).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) DeleteDashboard(slug string, accountId int) error {
resp, err := r.Table("dashboards").
GetAllByIndex("AccountIdSlug", []interface{}{accountId, slug}).
Delete().RunWrite(self.session)
if err != nil {
return err
}
if resp.Deleted != 1 {
return errors.New("Did not find dashboard to delete")
}
return nil
}
func (self *rethinkStore) Query(query string, accountId int) ([]*models.SearchResult, error) {
docs, err := r.Table("dashboards").
GetAllByIndex("AccountId", []interface{}{accountId}).
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
}
package stores
import (
log "github.com/alecthomas/log4go"
r "github.com/dancannon/gorethink"
)
func createRethinkDBTablesAndIndices(config *RethinkCfg, session *r.Session) {
r.DbCreate(config.DatabaseName).Exec(session)
// create tables
r.Db(config.DatabaseName).TableCreate("dashboards").Exec(session)
r.Db(config.DatabaseName).TableCreate("accounts").Exec(session)
r.Db(config.DatabaseName).TableCreate("master").Exec(session)
// create dashboard accountId + slug index
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("dashboards").IndexCreate("AccountId").Exec(session)
r.Db(config.DatabaseName).Table("accounts").IndexCreate("Login").Exec(session)
// create account collaborator index
r.Db(config.DatabaseName).Table("accounts").
IndexCreateFunc("CollaboratorAccountId", func(row r.Term) interface{} {
return row.Field("Collaborators").Map(func(row r.Term) interface{} {
return row.Field("AccountId")
})
}, r.IndexCreateOpts{Multi: true}).Exec(session)
// make sure master ids row exists
_, 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)
}
}
...@@ -12,7 +12,8 @@ type Store interface { ...@@ -12,7 +12,8 @@ type Store interface {
CreateAccount(acccount *models.Account) error CreateAccount(acccount *models.Account) error
UpdateAccount(acccount *models.Account) error UpdateAccount(acccount *models.Account) error
GetAccountByLogin(emailOrName string) (*models.Account, error) GetAccountByLogin(emailOrName string) (*models.Account, error)
GetAccount(id int) (*models.Account, error) GetAccount(accountId int) (*models.Account, error)
GetOtherAccountsFor(accountId int) ([]*models.OtherAccount, error)
Close() Close()
} }
......
package api
package api
type accountInfoDto struct {
Login string `json:"login"`
Email string `json:"email"`
AccountName string `json:"accountName"`
Collaborators []*collaboratorInfoDto `json:"collaborators"`
}
type collaboratorInfoDto struct {
AccountId int `json:"accountId"`
Email string `json:"email"`
Role string `json:"role"`
}
type addCollaboratorDto struct {
Email string `json:"email" binding:"required"`
}
type removeCollaboratorDto struct {
AccountId int `json:"accountId" binding:"required"`
}
type usingAccountDto struct {
AccountId int `json:"accountId"`
Email string `json:"email"`
Role string `json:"role"`
IsUsing bool
}
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