diff --git a/pkg/models/account.go b/pkg/models/account.go index dde55b3459d..4c7d0f840f3 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -6,11 +6,13 @@ import ( ) var ( - CreateAccount func(acccount *Account) error - UpdateAccount func(acccount *Account) error - GetAccountByLogin func(emailOrName string) (*Account, error) - GetAccount func(accountId int64) (*Account, error) - GetOtherAccountsFor func(accountId int64) ([]*OtherAccount, error) + CreateAccount func(acccount *Account) error + UpdateAccount func(acccount *Account) error + GetAccountByLogin func(emailOrName string) (*Account, error) + GetAccount func(accountId int64) (*Account, error) + GetOtherAccountsFor func(accountId int64) ([]*OtherAccount, error) + GetCollaboratorsForAccount func(accountId int64) ([]*CollaboratorInfo, error) + AddCollaborator func(collaborator *Collaborator) error ) // Typed errors diff --git a/pkg/models/collaborator.go b/pkg/models/collaborator.go new file mode 100644 index 00000000000..d7f57df7fc4 --- /dev/null +++ b/pkg/models/collaborator.go @@ -0,0 +1,38 @@ +package models + +import ( + "time" +) + +const ( + ROLE_READ_WRITE = "ReadWrite" + ROLE_READ = "Read" +) + +type RoleType string + +type Collaborator struct { + Id int64 + AccountId int64 `xorm:"not null unique(uix_account_id_for_account_id)"` // The account that can use another account + Role RoleType `xorm:"not null"` // Permission type + ForAccountId int64 `xorm:"not null unique(uix_account_id_for_account_id)"` // The account being given access to + Created time.Time + Updated time.Time +} + +// read only projection +type CollaboratorInfo struct { + AccountId int64 + Role string + Email string +} + +func NewCollaborator(accountId int64, forAccountId int64) *Collaborator { + return &Collaborator{ + AccountId: accountId, + ForAccountId: forAccountId, + Role: ROLE_READ, + Created: time.Now(), + Updated: time.Now(), + } +} diff --git a/pkg/routes/api/api_account.go b/pkg/routes/api/api_account.go new file mode 100644 index 00000000000..1f47d96e9d0 --- /dev/null +++ b/pkg/routes/api/api_account.go @@ -0,0 +1,62 @@ +package api + +import ( + "github.com/torkelo/grafana-pro/pkg/middleware" + "github.com/torkelo/grafana-pro/pkg/models" + "github.com/torkelo/grafana-pro/pkg/routes/dtos" + "github.com/torkelo/grafana-pro/pkg/utils" +) + +func GetAccount(c *middleware.Context) { + model := dtos.AccountInfo{ + Name: c.UserAccount.Name, + Email: c.UserAccount.Email, + } + + collaborators, err := models.GetCollaboratorsForAccount(c.UserAccount.Id) + if err != nil { + c.JsonApiErr(500, "Failed to fetch collaboratos", err) + return + } + + for _, collaborator := range collaborators { + model.Collaborators = append(model.Collaborators, &dtos.Collaborator{ + AccountId: collaborator.AccountId, + Role: collaborator.Role, + Email: collaborator.Email, + }) + } + + c.JSON(200, model) +} + +func AddCollaborator(c *middleware.Context) { + var model dtos.AddCollaboratorCommand + + if !c.JsonBody(&model) { + c.JSON(400, utils.DynMap{"message": "Invalid request"}) + return + } + + accountToAdd, err := models.GetAccountByLogin(model.Email) + if err != nil { + c.JSON(404, utils.DynMap{"message": "Collaborator not found"}) + return + } + + if accountToAdd.Id == c.UserAccount.Id { + c.JSON(400, utils.DynMap{"message": "Cannot add yourself as collaborator"}) + return + } + + var collaborator = models.NewCollaborator(accountToAdd.Id, c.UserAccount.Id) + collaborator.Role = models.ROLE_READ_WRITE + + err = models.AddCollaborator(collaborator) + if err != nil { + c.JSON(400, utils.DynMap{"message": err.Error()}) + return + } + + c.Status(204) +} diff --git a/pkg/routes/api/api_dashboard.go b/pkg/routes/api/api_dashboard.go index 1762539d84e..8362c97ef5e 100644 --- a/pkg/routes/api/api_dashboard.go +++ b/pkg/routes/api/api_dashboard.go @@ -3,7 +3,7 @@ package api import ( "github.com/torkelo/grafana-pro/pkg/middleware" "github.com/torkelo/grafana-pro/pkg/models" - "github.com/torkelo/grafana-pro/pkg/routes/apimodel" + "github.com/torkelo/grafana-pro/pkg/routes/dtos" "github.com/torkelo/grafana-pro/pkg/utils" ) @@ -63,7 +63,7 @@ func convertToStringArray(arr []interface{}) []string { } func PostDashboard(c *middleware.Context) { - var command apimodel.SaveDashboardCommand + var command dtos.SaveDashboardCommand if !c.JsonBody(&command) { c.JsonApiErr(400, "bad request", nil) diff --git a/pkg/routes/apimodel/models.go b/pkg/routes/dtos/models.go similarity index 55% rename from pkg/routes/apimodel/models.go rename to pkg/routes/dtos/models.go index 211bcb1646b..99d40d3f585 100644 --- a/pkg/routes/apimodel/models.go +++ b/pkg/routes/dtos/models.go @@ -1,4 +1,4 @@ -package apimodel +package dtos import ( "crypto/md5" @@ -8,19 +8,35 @@ import ( "github.com/torkelo/grafana-pro/pkg/models" ) -type LoginResultDto struct { - Status string `json:"status"` - User CurrentUserDto `json:"user"` +type LoginResult struct { + Status string `json:"status"` + User CurrentUser `json:"user"` } -type CurrentUserDto struct { +type CurrentUser struct { Login string `json:"login"` Email string `json:"email"` GravatarUrl string `json:"gravatarUrl"` } -func NewCurrentUserDto(account *models.Account) *CurrentUserDto { - model := &CurrentUserDto{} +type AccountInfo struct { + Email string `json:"email"` + Name string `json:"name"` + Collaborators []*Collaborator `json:"collaborators"` +} + +type Collaborator struct { + AccountId int64 `json:"accountId"` + Email string `json:"email"` + Role string `json:"role"` +} + +type AddCollaboratorCommand struct { + Email string `json:"email" binding:"required"` +} + +func NewCurrentUser(account *models.Account) *CurrentUser { + model := &CurrentUser{} if account != nil { model.Login = account.Login model.Email = account.Email diff --git a/pkg/routes/index.go b/pkg/routes/index.go index 78aebf80698..44ed96f75ac 100644 --- a/pkg/routes/index.go +++ b/pkg/routes/index.go @@ -4,7 +4,7 @@ import ( "github.com/Unknwon/macaron" "github.com/torkelo/grafana-pro/pkg/middleware" "github.com/torkelo/grafana-pro/pkg/routes/api" - "github.com/torkelo/grafana-pro/pkg/routes/apimodel" + "github.com/torkelo/grafana-pro/pkg/routes/dtos" "github.com/torkelo/grafana-pro/pkg/routes/login" ) @@ -20,6 +20,11 @@ func Register(m *macaron.Macaron) { m.Get("/login", Index) m.Get("/login/:name", login.OAuthLogin) + // account + m.Get("/account/", auth, Index) + m.Get("/api/account/", auth, api.GetAccount) + m.Post("/api/account/collaborators/add", auth, api.AddCollaborator) + // user register m.Get("/register/*_", Index) m.Post("/api/account", api.CreateAccount) @@ -36,7 +41,7 @@ func Register(m *macaron.Macaron) { } func Index(ctx *middleware.Context) { - ctx.Data["User"] = apimodel.NewCurrentUserDto(ctx.UserAccount) + ctx.Data["User"] = dtos.NewCurrentUser(ctx.UserAccount) ctx.HTML(200, "index") } diff --git a/pkg/routes/login/login.go b/pkg/routes/login/login.go index c8b0a9cde2e..93c9c41322f 100644 --- a/pkg/routes/login/login.go +++ b/pkg/routes/login/login.go @@ -1,11 +1,11 @@ package login import ( - "github.com/gin-gonic/gin" "github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/middleware" "github.com/torkelo/grafana-pro/pkg/models" - "github.com/torkelo/grafana-pro/pkg/routes/apimodel" + "github.com/torkelo/grafana-pro/pkg/routes/dtos" + "github.com/torkelo/grafana-pro/pkg/utils" ) type loginJsonModel struct { @@ -18,24 +18,24 @@ func LoginPost(c *middleware.Context) { var loginModel loginJsonModel if !c.JsonBody(&loginModel) { - c.JSON(400, gin.H{"status": "bad request"}) + c.JSON(400, utils.DynMap{"status": "bad request"}) return } account, err := models.GetAccountByLogin(loginModel.Email) if err != nil { - c.JSON(401, gin.H{"status": "unauthorized"}) + c.JSON(401, utils.DynMap{"status": "unauthorized"}) return } if loginModel.Password != account.Password { - c.JSON(401, gin.H{"status": "unauthorized"}) + c.JSON(401, utils.DynMap{"status": "unauthorized"}) return } loginUserWithAccount(account, c) - var resp = &apimodel.LoginResultDto{} + var resp = &dtos.LoginResult{} resp.Status = "Logged in" resp.User.Login = account.Login @@ -52,5 +52,5 @@ func loginUserWithAccount(account *models.Account, c *middleware.Context) { func LogoutPost(c *middleware.Context) { c.Session.Delete("accountId") - c.JSON(200, gin.H{"status": "logged out"}) + c.JSON(200, utils.DynMap{"status": "logged out"}) } diff --git a/pkg/stores/sqlstore/sqlstore.go b/pkg/stores/sqlstore/sqlstore.go index a6448f2d9ef..f9a8244a380 100644 --- a/pkg/stores/sqlstore/sqlstore.go +++ b/pkg/stores/sqlstore/sqlstore.go @@ -27,7 +27,7 @@ var ( ) func Init() { - tables = append(tables, new(models.Account), new(models.Dashboard)) + tables = append(tables, new(models.Account), new(models.Dashboard), new(models.Collaborator)) models.CreateAccount = CreateAccount models.GetAccount = GetAccount @@ -36,6 +36,8 @@ func Init() { models.SaveDashboard = SaveDashboard models.SearchQuery = SearchQuery models.DeleteDashboard = DeleteDashboard + models.GetCollaboratorsForAccount = GetCollaboratorsForAccount + models.AddCollaborator = AddCollaborator } func LoadModelsConfig() { diff --git a/pkg/stores/sqlstore/sqlstore_accounts.go b/pkg/stores/sqlstore/sqlstore_accounts.go index 15a39bd15c1..2332e6752b9 100644 --- a/pkg/stores/sqlstore/sqlstore_accounts.go +++ b/pkg/stores/sqlstore/sqlstore_accounts.go @@ -57,3 +57,31 @@ func GetAccountByLogin(emailOrLogin string) (*models.Account, error) { return account, nil } + +func GetCollaboratorsForAccount(accountId int64) ([]*models.CollaboratorInfo, error) { + collaborators := make([]*models.CollaboratorInfo, 0) + sess := x.Table("Collaborator") + sess.Join("INNER", "Account", "Account.id=Collaborator.account_Id") + err := sess.Find(&collaborators) + return collaborators, err +} + +func AddCollaborator(collaborator *models.Collaborator) error { + var err error + + sess := x.NewSession() + defer sess.Close() + + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.Insert(collaborator); err != nil { + sess.Rollback() + return err + } else if err = sess.Commit(); err != nil { + return err + } + + return nil +}