Просмотр исходного кода

Lots of progress on account management

Torkel Ödegaard 11 лет назад
Родитель
Сommit
cd9306df45

+ 1 - 1
grafana

@@ -1 +1 @@
-Subproject commit 639a44d99629ba38e67eb04df8c8c9622093de70
+Subproject commit e0dc530e943d52453aa06fc47596d3f6f0261f2c

+ 90 - 8
pkg/api/api_account.go

@@ -1,11 +1,18 @@
 package api
 
-import "github.com/gin-gonic/gin"
+import (
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+)
 
 func init() {
 	addRoutes(func(self *HttpServer) {
 		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/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) {
 		model.Collaborators = append(model.Collaborators, &collaboratorInfoDto{
 			AccountId: collaborator.AccountId,
 			Role:      collaborator.Role,
+			Email:     collaborator.Email,
 		})
 	}
 
 	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) {
 	var model addCollaboratorDto
 
 	if !c.EnsureBody(&model) {
-		c.JSON(400, gin.H{"status": "Collaborator not found"})
+		c.JSON(400, gin.H{"message": "Invalid request"})
 		return
 	}
 
 	collaborator, err := self.store.GetAccountByLogin(model.Email)
 	if err != nil {
-		c.JSON(404, gin.H{"status": "Collaborator not found"})
+		c.JSON(404, gin.H{"message": "Collaborator not found"})
 		return
 	}
 
 	userAccount := auth.userAccount
 
 	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
 	}
 
-	err = userAccount.AddCollaborator(collaborator.Id)
+	err = userAccount.AddCollaborator(collaborator)
 	if err != nil {
-		c.JSON(400, gin.H{"status": err.Error()})
+		c.JSON(400, gin.H{"message": err.Error()})
 		return
 	}
 
 	err = self.store.UpdateAccount(userAccount)
 	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
 	}
 
-	c.JSON(200, gin.H{"status": "Collaborator added"})
+	c.Abort(204)
 }

+ 3 - 3
pkg/api/api_auth.go

@@ -23,18 +23,18 @@ func (self *HttpServer) auth() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		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)
 			return
 		}
 
-		account, err := self.store.GetAccount(session.Values["userAccountId"].(int))
+		account, err := self.store.GetAccount(session.Values["accountId"].(int))
 		if err != nil {
 			self.authDenied(c)
 			return
 		}
 
-		usingAccount, err := self.store.GetAccount(session.Values["usingAccountId"].(int))
+		usingAccount, err := self.store.GetAccount(account.UsingAccountId)
 		if err != nil {
 			self.authDenied(c)
 			return

+ 11 - 0
pkg/api/api_dtos.go

@@ -16,3 +16,14 @@ type collaboratorInfoDto struct {
 type addCollaboratorDto struct {
 	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"`
+}

+ 3 - 3
pkg/api/api_login.go

@@ -26,7 +26,8 @@ func (self *HttpServer) loginPost(c *gin.Context) {
 
 	account, err := self.store.GetAccountByLogin(loginModel.Email)
 	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 {
@@ -35,8 +36,7 @@ func (self *HttpServer) loginPost(c *gin.Context) {
 	}
 
 	session, _ := sessionStore.Get(c.Request, "grafana-session")
-	session.Values["userAccountId"] = account.Id
-	session.Values["usingAccountId"] = account.UsingAccountId
+	session.Values["accountId"] = account.Id
 	session.Save(c.Request, c.Writer)
 
 	var resp = &LoginResultDto{}

+ 30 - 3
pkg/models/account.go

@@ -8,10 +8,17 @@ import (
 type CollaboratorLink struct {
 	AccountId  int
 	Role       string
+	Email      string
 	ModifiedOn time.Time
 	CreatedOn  time.Time
 }
 
+type OtherAccount struct {
+	Id   int `gorethink:"id"`
+	Name string
+	Role string
+}
+
 type Account struct {
 	Id              int `gorethink:"id"`
 	Version         int
@@ -27,15 +34,16 @@ type Account struct {
 	LastLoginOn     time.Time
 }
 
-func (account *Account) AddCollaborator(accountId int) error {
+func (account *Account) AddCollaborator(newCollaborator *Account) error {
 	for _, collaborator := range account.Collaborators {
-		if collaborator.AccountId == accountId {
+		if collaborator.AccountId == newCollaborator.Id {
 			return errors.New("Collaborator already exists")
 		}
 	}
 
 	account.Collaborators = append(account.Collaborators, CollaboratorLink{
-		AccountId:  accountId,
+		AccountId:  newCollaborator.Id,
+		Email:      newCollaborator.Email,
 		Role:       "admin",
 		CreatedOn:  time.Now(),
 		ModifiedOn: time.Now(),
@@ -43,3 +51,22 @@ func (account *Account) AddCollaborator(accountId int) error {
 
 	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
+}

+ 1 - 88
pkg/stores/rethinkdb.go

@@ -1,12 +1,10 @@
 package stores
 
 import (
-	"errors"
 	"time"
 
 	log "github.com/alecthomas/log4go"
 	r "github.com/dancannon/gorethink"
-	"github.com/torkelo/grafana-pro/pkg/models"
 )
 
 type rethinkStore struct {
@@ -36,96 +34,11 @@ func NewRethinkStore(config *RethinkCfg) *rethinkStore {
 		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("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)
-	}
+	createRethinkDBTablesAndIndices(config, session)
 
 	return &rethinkStore{
 		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() {}

+ 29 - 3
pkg/stores/rethinkdb_accounts.go

@@ -47,7 +47,7 @@ func (self *rethinkStore) CreateAccount(account *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 {
 		return nil, err
@@ -84,8 +84,8 @@ func (self *rethinkStore) UpdateAccount(account *models.Account) error {
 		return err
 	}
 
-	if resp.Replaced != 1 {
-		return errors.New("Could not fund account to uodate")
+	if resp.Replaced == 0 && resp.Unchanged == 0 {
+		return errors.New("Could not find account to update")
 	}
 
 	return nil
@@ -108,3 +108,29 @@ func (self *rethinkStore) getNextDashboardNumber(accountId int) (int, error) {
 
 	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
+}

+ 79 - 0
pkg/stores/rethinkdb_dashboards.go

@@ -0,0 +1,79 @@
+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
+}

+ 39 - 0
pkg/stores/rethinkdb_setup.go

@@ -0,0 +1,39 @@
+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)
+	}
+
+}

+ 2 - 1
pkg/stores/store.go

@@ -12,7 +12,8 @@ type Store interface {
 	CreateAccount(acccount *models.Account) error
 	UpdateAccount(acccount *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()
 }
 

+ 29 - 0
̈́

@@ -0,0 +1,29 @@
+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
+}