Browse Source

macaron transition progress

Torkel Ödegaard 11 years ago
parent
commit
222319d924

BIN
data/sessions/5/e/5e40ff05d87ac75cba0634e7350c263c4c45f202


BIN
grafana-pro


+ 28 - 22
pkg/cmd/web.go

@@ -10,11 +10,14 @@ import (
 
 
 	"github.com/Unknwon/macaron"
 	"github.com/Unknwon/macaron"
 	"github.com/codegangsta/cli"
 	"github.com/codegangsta/cli"
+	"github.com/macaron-contrib/session"
 
 
 	"github.com/torkelo/grafana-pro/pkg/log"
 	"github.com/torkelo/grafana-pro/pkg/log"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/routes"
 	"github.com/torkelo/grafana-pro/pkg/routes"
+	"github.com/torkelo/grafana-pro/pkg/routes/login"
 	"github.com/torkelo/grafana-pro/pkg/setting"
 	"github.com/torkelo/grafana-pro/pkg/setting"
+	"github.com/torkelo/grafana-pro/pkg/stores/rethink"
 )
 )
 
 
 var CmdWeb = cli.Command{
 var CmdWeb = cli.Command{
@@ -29,27 +32,15 @@ func newMacaron() *macaron.Macaron {
 	m := macaron.New()
 	m := macaron.New()
 	m.Use(middleware.Logger())
 	m.Use(middleware.Logger())
 	m.Use(macaron.Recovery())
 	m.Use(macaron.Recovery())
-	m.Use(macaron.Static(
-		path.Join(setting.StaticRootPath, "public"),
-		macaron.StaticOptions{
-			SkipLogging: true,
-			Prefix:      "public",
-		},
-	))
-	m.Use(macaron.Static(
-		path.Join(setting.StaticRootPath, "public/app"),
-		macaron.StaticOptions{
-			SkipLogging: true,
-			Prefix:      "app",
-		},
-	))
-	m.Use(macaron.Static(
-		path.Join(setting.StaticRootPath, "public/img"),
-		macaron.StaticOptions{
-			SkipLogging: true,
-			Prefix:      "img",
-		},
-	))
+
+	mapStatic(m, "public", "public")
+	mapStatic(m, "public/app", "app")
+	mapStatic(m, "public/img", "img")
+
+	m.Use(session.Sessioner(session.Options{
+		Provider: setting.SessionProvider,
+		Config:   *setting.SessionConfig,
+	}))
 
 
 	m.Use(macaron.Renderer(macaron.RenderOptions{
 	m.Use(macaron.Renderer(macaron.RenderOptions{
 		Directory:  path.Join(setting.StaticRootPath, "views"),
 		Directory:  path.Join(setting.StaticRootPath, "views"),
@@ -61,16 +52,31 @@ func newMacaron() *macaron.Macaron {
 	return m
 	return m
 }
 }
 
 
+func mapStatic(m *macaron.Macaron, dir string, prefix string) {
+	m.Use(macaron.Static(
+		path.Join(setting.StaticRootPath, dir),
+		macaron.StaticOptions{
+			SkipLogging: true,
+			Prefix:      prefix,
+		},
+	))
+}
+
 func runWeb(*cli.Context) {
 func runWeb(*cli.Context) {
 	setting.NewConfigContext()
 	setting.NewConfigContext()
 	setting.InitServices()
 	setting.InitServices()
+	rethink.Init()
 
 
 	log.Info("Starting Grafana-Pro v.1-alpha")
 	log.Info("Starting Grafana-Pro v.1-alpha")
 
 
 	m := newMacaron()
 	m := newMacaron()
 
 
+	auth := middleware.Auth()
+
 	// index
 	// index
-	m.Get("/", routes.Index)
+	m.Get("/", auth, routes.Index)
+	m.Get("/login", routes.Index)
+	m.Post("/login", login.LoginPost)
 
 
 	var err error
 	var err error
 	listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
 	listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)

+ 57 - 0
pkg/middleware/auth.go

@@ -0,0 +1,57 @@
+package middleware
+
+import (
+	"errors"
+	"strconv"
+
+	"github.com/Unknwon/macaron"
+	"github.com/macaron-contrib/session"
+	"github.com/torkelo/grafana-pro/pkg/models"
+)
+
+func authGetRequestAccountId(c *Context, sess session.Store) (int, error) {
+	accountId := sess.Get("accountId")
+
+	urlQuery := c.Req.URL.Query()
+	if len(urlQuery["render"]) > 0 {
+		accId, _ := strconv.Atoi(urlQuery["accountId"][0])
+		sess.Set("accountId", accId)
+		accountId = accId
+	}
+
+	if accountId == nil {
+		return -1, errors.New("Auth: session account id not found")
+	}
+
+	return accountId.(int), nil
+}
+
+func authDenied(c *Context) {
+	c.Redirect("/login")
+}
+
+func Auth() macaron.Handler {
+	return func(c *Context, sess session.Store) {
+		accountId, err := authGetRequestAccountId(c, sess)
+
+		if err != nil && c.Req.URL.Path != "/login" {
+			authDenied(c)
+			return
+		}
+
+		account, err := models.GetAccount(accountId)
+		if err != nil {
+			authDenied(c)
+			return
+		}
+
+		usingAccount, err := models.GetAccount(account.UsingAccountId)
+		if err != nil {
+			authDenied(c)
+			return
+		}
+
+		c.UserAccount = account
+		c.Account = usingAccount
+	}
+}

+ 10 - 4
pkg/middleware/middleware.go

@@ -1,7 +1,8 @@
 package middleware
 package middleware
 
 
 import (
 import (
-	"time"
+	"encoding/json"
+	"io/ioutil"
 
 
 	"github.com/Unknwon/macaron"
 	"github.com/Unknwon/macaron"
 	"github.com/macaron-contrib/session"
 	"github.com/macaron-contrib/session"
@@ -21,13 +22,12 @@ type Context struct {
 }
 }
 
 
 func GetContextHandler() macaron.Handler {
 func GetContextHandler() macaron.Handler {
-	return func(c *macaron.Context) {
+	return func(c *macaron.Context, sess session.Store) {
 		ctx := &Context{
 		ctx := &Context{
 			Context: c,
 			Context: c,
+			Session: sess,
 		}
 		}
 
 
-		ctx.Data["PageStartTime"] = time.Now()
-
 		c.Map(ctx)
 		c.Map(ctx)
 	}
 	}
 }
 }
@@ -50,3 +50,9 @@ func (ctx *Context) Handle(status int, title string, err error) {
 
 
 	ctx.HTML(status, "index")
 	ctx.HTML(status, "index")
 }
 }
+
+func (ctx *Context) JsonBody(model interface{}) bool {
+	b, _ := ioutil.ReadAll(ctx.Req.Body)
+	err := json.Unmarshal(b, &model)
+	return err == nil
+}

+ 13 - 0
pkg/models/account.go

@@ -5,6 +5,19 @@ import (
 	"time"
 	"time"
 )
 )
 
 
+var (
+	CreateAccount       func(acccount *Account) error
+	UpdateAccount       func(acccount *Account) error
+	GetAccountByLogin   func(emailOrName string) (*Account, error)
+	GetAccount          func(accountId int) (*Account, error)
+	GetOtherAccountsFor func(accountId int) ([]*OtherAccount, error)
+)
+
+// Typed errors
+var (
+	ErrAccountNotFound = errors.New("Account not found")
+)
+
 type CollaboratorLink struct {
 type CollaboratorLink struct {
 	AccountId  int
 	AccountId  int
 	Role       string
 	Role       string

+ 36 - 0
pkg/routes/apimodel/models.go

@@ -0,0 +1,36 @@
+package apimodel
+
+import (
+	"crypto/md5"
+	"fmt"
+	"strings"
+
+	"github.com/torkelo/grafana-pro/pkg/models"
+)
+
+type LoginResultDto struct {
+	Status string         `json:"status"`
+	User   CurrentUserDto `json:"user"`
+}
+
+type CurrentUserDto struct {
+	Login       string `json:"login"`
+	Email       string `json:"email"`
+	GravatarUrl string `json:"gravatarUrl"`
+}
+
+func NewCurrentUserDto(account *models.Account) *CurrentUserDto {
+	model := &CurrentUserDto{}
+	if account != nil {
+		model.Login = account.Login
+		model.Email = account.Email
+		model.GravatarUrl = getGravatarUrl(account.Email)
+	}
+	return model
+}
+
+func getGravatarUrl(text string) string {
+	hasher := md5.New()
+	hasher.Write([]byte(strings.ToLower(text)))
+	return fmt.Sprintf("https://secure.gravatar.com/avatar/%x?s=90&default=mm", hasher.Sum(nil))
+}

+ 5 - 2
pkg/routes/index.go

@@ -1,12 +1,15 @@
 package routes
 package routes
 
 
-import "github.com/torkelo/grafana-pro/pkg/middleware"
+import (
+	"github.com/torkelo/grafana-pro/pkg/middleware"
+	"github.com/torkelo/grafana-pro/pkg/routes/apimodel"
+)
 
 
 func Index(ctx *middleware.Context) {
 func Index(ctx *middleware.Context) {
+	ctx.Data["User"] = apimodel.NewCurrentUserDto(ctx.UserAccount)
 	ctx.HTML(200, "index")
 	ctx.HTML(200, "index")
 }
 }
 
 
 func NotFound(ctx *middleware.Context) {
 func NotFound(ctx *middleware.Context) {
-	ctx.Data["Title"] = "Page Not Found"
 	ctx.Handle(404, "index", nil)
 	ctx.Handle(404, "index", nil)
 }
 }

+ 56 - 0
pkg/routes/login/login.go

@@ -0,0 +1,56 @@
+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"
+)
+
+type loginJsonModel struct {
+	Email    string `json:"email" binding:"required"`
+	Password string `json:"password" binding:"required"`
+	Remember bool   `json:"remember"`
+}
+
+func LoginPost(c *middleware.Context) {
+	var loginModel loginJsonModel
+
+	if !c.JsonBody(&loginModel) {
+		c.JSON(400, gin.H{"status": "bad request"})
+		return
+	}
+
+	account, err := models.GetAccountByLogin(loginModel.Email)
+	if err != nil {
+		c.JSON(401, gin.H{"status": "unauthorized"})
+		return
+	}
+
+	if loginModel.Password != account.Password {
+		c.JSON(401, gin.H{"status": "unauthorized"})
+		return
+	}
+
+	loginUserWithAccount(account, c)
+
+	var resp = &apimodel.LoginResultDto{}
+	resp.Status = "Logged in"
+	resp.User.Login = account.Login
+
+	c.JSON(200, resp)
+}
+
+func loginUserWithAccount(account *models.Account, c *middleware.Context) {
+	if account == nil {
+		log.Error(3, "Account login with nil account")
+	}
+
+	c.Session.Set("accountId", account.Id)
+}
+
+func LogoutPost(c *middleware.Context) {
+	c.Session.Delete("accountId")
+	c.JSON(200, gin.H{"status": "logged out"})
+}

+ 197 - 0
pkg/stores/rethink/rethink.go

@@ -0,0 +1,197 @@
+package rethink
+
+import (
+	"errors"
+	"time"
+
+	r "github.com/dancannon/gorethink"
+
+	"github.com/torkelo/grafana-pro/pkg/log"
+	"github.com/torkelo/grafana-pro/pkg/models"
+)
+
+var (
+	session *r.Session
+	dbName  string = "grafana"
+)
+
+func Init() {
+	log.Info("Initializing rethink storage")
+
+	var err error
+	session, err = r.Connect(r.ConnectOpts{
+		Address:     "localhost:28015",
+		Database:    dbName,
+		MaxIdle:     10,
+		IdleTimeout: time.Second * 10,
+	})
+
+	if err != nil {
+		log.Error(3, "Failed to connect to rethink database %v", err)
+	}
+
+	createRethinkDBTablesAndIndices()
+
+	models.GetAccount = GetAccount
+	models.GetAccountByLogin = GetAccountByLogin
+}
+
+func createRethinkDBTablesAndIndices() {
+
+	r.DbCreate(dbName).Exec(session)
+
+	// create tables
+	r.Db(dbName).TableCreate("dashboards").Exec(session)
+	r.Db(dbName).TableCreate("accounts").Exec(session)
+	r.Db(dbName).TableCreate("master").Exec(session)
+
+	// create dashboard  accountId + slug index
+	r.Db(dbName).Table("dashboards").IndexCreateFunc("AccountIdSlug", func(row r.Term) interface{} {
+		return []interface{}{row.Field("AccountId"), row.Field("Slug")}
+	}).Exec(session)
+
+	r.Db(dbName).Table("dashboards").IndexCreate("AccountId").Exec(session)
+	r.Db(dbName).Table("accounts").IndexCreate("Login").Exec(session)
+
+	// create account collaborator index
+	r.Db(dbName).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(3, "Failed to insert master ids row", err)
+	}
+}
+
+func getNextAccountId() (int, error) {
+	resp, err := r.Table("master").Get("ids").Update(map[string]interface{}{
+		"NextAccountId": r.Row.Field("NextAccountId").Add(1),
+	}, r.UpdateOpts{ReturnChanges: true}).RunWrite(session)
+
+	if err != nil {
+		return 0, err
+	}
+
+	change := resp.Changes[0]
+
+	if change.NewValue == nil {
+		return 0, errors.New("Failed to get new value after incrementing account id")
+	}
+
+	return int(change.NewValue.(map[string]interface{})["NextAccountId"].(float64)), nil
+}
+
+func CreateAccount(account *models.Account) error {
+	accountId, err := getNextAccountId()
+	if err != nil {
+		return err
+	}
+
+	account.Id = accountId
+	account.UsingAccountId = accountId
+
+	resp, err := r.Table("accounts").Insert(account).RunWrite(session)
+	if err != nil {
+		return err
+	}
+
+	if resp.Inserted == 0 {
+		return errors.New("Failed to insert acccount")
+	}
+
+	return nil
+}
+
+func GetAccountByLogin(emailOrName string) (*models.Account, error) {
+	resp, err := r.Table("accounts").GetAllByIndex("Login", emailOrName).Run(session)
+
+	if err != nil {
+		return nil, err
+	}
+
+	var account models.Account
+	err = resp.One(&account)
+	if err != nil {
+		return nil, models.ErrAccountNotFound
+	}
+
+	return &account, nil
+}
+
+func GetAccount(id int) (*models.Account, error) {
+	resp, err := r.Table("accounts").Get(id).Run(session)
+
+	if err != nil {
+		return nil, err
+	}
+
+	var account models.Account
+	err = resp.One(&account)
+	if err != nil {
+		return nil, errors.New("Not found")
+	}
+
+	return &account, nil
+}
+
+func UpdateAccount(account *models.Account) error {
+	resp, err := r.Table("accounts").Update(account).RunWrite(session)
+	if err != nil {
+		return err
+	}
+
+	if resp.Replaced == 0 && resp.Unchanged == 0 {
+		return errors.New("Could not find account to update")
+	}
+
+	return nil
+}
+
+func 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{ReturnChanges: true}).RunWrite(session)
+
+	if err != nil {
+		return 0, err
+	}
+
+	change := resp.Changes[0]
+
+	if change.NewValue == nil {
+		return 0, errors.New("Failed to get next dashboard id, no new value after update")
+	}
+
+	return int(change.NewValue.(map[string]interface{})["NextDashboardId"].(float64)), nil
+}
+
+func 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(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
+}

+ 1 - 10
pkg/stores/store.go

@@ -1,10 +1,6 @@
 package stores
 package stores
 
 
-import (
-	"errors"
-
-	"github.com/torkelo/grafana-pro/pkg/models"
-)
+import "github.com/torkelo/grafana-pro/pkg/models"
 
 
 type Store interface {
 type Store interface {
 	GetDashboard(slug string, accountId int) (*models.Dashboard, error)
 	GetDashboard(slug string, accountId int) (*models.Dashboard, error)
@@ -19,11 +15,6 @@ type Store interface {
 	Close()
 	Close()
 }
 }
 
 
-// Typed errors
-var (
-	ErrAccountNotFound = errors.New("Account not found")
-)
-
 func New() Store {
 func New() Store {
 	return NewRethinkStore(&RethinkCfg{DatabaseName: "grafana"})
 	return NewRethinkStore(&RethinkCfg{DatabaseName: "grafana"})
 }
 }