Created
April 9, 2014 15:14
-
-
Save vastbinderj/10281831 to your computer and use it in GitHub Desktop.
ottemo-create-user-func
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"github.com/codegangsta/martini" | |
"github.com/martini-contrib/binding" | |
"github.com/martini-contrib/render" | |
"github.com/martini-contrib/secure" | |
"github.com/martini-contrib/sessions" | |
"github.com/ottemo/ottemo-go/auth" | |
"github.com/ottemo/ottemo-go/config" | |
"github.com/ottemo/ottemo-go/product" | |
"github.com/ottemo/ottemo-go/visitor" | |
"labix.org/v2/mgo" | |
"labix.org/v2/mgo/bson" | |
"log" | |
"net/http" | |
) | |
var conf *config.Config | |
// init - initialization and configuration | |
func init() { | |
// read in config file | |
filename, err := config.DiscoverConfig("config.json", "local.json") | |
if err != nil { | |
log.Fatal("No configuration file found") | |
} | |
conf, err = config.NewConfig(filename) | |
if err != nil { | |
log.Fatalf("Error parsing config.json: %s", err.Error()) | |
} | |
} | |
func main() { | |
m := App() | |
listen := fmt.Sprintf("%s:%d", conf.Host, conf.Port) | |
fmt.Printf("Ottemo listening on %s\n", listen) | |
log.Fatal(http.ListenAndServe(listen, m)) | |
} | |
// App returns a new martini app. | |
func App() *martini.ClassicMartini { | |
m := martini.Classic() | |
// TODO: Cookie secret needs to be random. | |
store := sessions.NewCookieStore([]byte("secret")) | |
m.Use(sessions.Sessions("ottemo", store)) | |
// Set security values in production. | |
if martini.Env == martini.Prod { | |
m.Use(secure.Secure(secure.Options{ | |
SSLRedirect: true, | |
})) | |
} | |
// Initialize Middleware. | |
m.Use(Db(conf.DbURL, conf.DbName)) | |
m.Use(render.Renderer(render.Options{Extensions: []string{".html"}})) | |
m.Use(visitor.SessionVisitor) | |
// Application Routes. | |
m.Get("/", func(r render.Render) { | |
r.HTML(http.StatusOK, "index", nil) | |
}) | |
m.Get("/login", func(r render.Render) { | |
r.HTML(http.StatusOK, "login", nil) | |
}) | |
m.Post("/login", binding.Bind(auth.LocalLoginRequest{}), auth.LocalLoginHandler) | |
m.Get("/register", func(r render.Render) { | |
r.HTML(http.StatusOK, "register", nil) | |
}) | |
m.Post("/register", binding.Bind(visitor.RegisterLocalRequest{}), visitor.RegisterLocalHandler) | |
// API Routes | |
// TODO better error handling when resources not found | |
m.Get("/api/v1/visitor", func(r render.Render) { | |
// r.JSON(200, visitor.GetVisitors) | |
}) | |
m.Get("/api/v1/visitor/:id", func(r render.Render, db *mgo.Database, params martini.Params) { | |
v, err := visitor.GetVisitorByID(db, bson.ObjectIdHex(params["id"])) | |
if err != nil { | |
r.Error(500) | |
} | |
r.JSON(200, v) | |
}) | |
// Create a visitor | |
m.Post("/api/v1/visitor", binding.Bind(visitor.RegisterLocalRequest{}), visitor.CreateLocalVisitor) | |
m.Get("/api/v1/visitor/email/:id", func(r render.Render, db *mgo.Database, params martini.Params) { | |
v, err := visitor.GetVisitorByEmail(db, params["id"]) | |
if err != nil { | |
r.Error(500) | |
} | |
r.JSON(200, v) | |
}) | |
// Admin Tool Routes | |
m.Get("/admintool", func(r render.Render) { | |
r.HTML(http.StatusOK, "admintool/index", nil) | |
}) | |
m.Get("/admintool/product", func(r render.Render) { | |
r.HTML(http.StatusOK, "admintool/catalog/product/index", nil) | |
}) | |
m.Get("/admintool/product/new", func(r render.Render) { | |
r.HTML(http.StatusOK, "admintool/catalog/product/index", nil) | |
}) | |
m.Post("/admintool/product/new", binding.Bind(product.ProductRequest{}), product.NewProductHandler) | |
m.Get("/admintool/product/update", func(r render.Render) { | |
r.HTML(http.StatusOK, "admintool/catalog/product/index", nil) | |
}) | |
// m.Post("/admintool/product/update", binding.Bind(product.ProductRequest{}), product.UpdateProductHandler) | |
return m | |
} | |
// Db maps a MongoDb instance to the current request context. | |
func Db(url, name string) martini.Handler { | |
s, err := mgo.Dial(url) | |
if err != nil { | |
log.Fatal(err) | |
} | |
return func(c martini.Context) { | |
clone := s.Clone() | |
c.Map(clone.DB(name)) | |
defer clone.Close() | |
c.Next() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*Package visitor contains data types and supporting functions | |
for visitory creation, login, etc. | |
*/ | |
package visitor | |
import ( | |
"code.google.com/p/go-uuid/uuid" | |
"code.google.com/p/go.crypto/bcrypt" | |
"github.com/codegangsta/martini" | |
"github.com/martini-contrib/render" | |
"github.com/martini-contrib/sessions" | |
"labix.org/v2/mgo" | |
"labix.org/v2/mgo/bson" | |
"net/http" | |
) | |
const ( | |
// COLLECTION is the name of the MongoDB collection | |
COLLECTION = "visitors" | |
// VISITOR is the string representing a visitor role. | |
VISITOR = "visitor" | |
// MEMBER is the string representing a visitor role. | |
MEMBER = "member" | |
// ADMIN is the string representing a visitor role. | |
ADMIN = "admin" | |
// RETIRED is the string representing a visitor role. | |
RETIRED = "retired" | |
) | |
var roles = []string{VISITOR, MEMBER, ADMIN, RETIRED} | |
// Visitor is a user of the site. | |
type Visitor struct { | |
ID bson.ObjectId `bson:"_id" json:"id"` | |
Email string `bson:"email" json:"email"` | |
Hash string `bson:"hash" json:"hash"` | |
Fname string `bson:"fname" json:"fname"` | |
Lname string `bson:"lname" json:"lname"` | |
PhoneNumbers []PhoneNumber `bson:"phone_numbers" json:"phone_numbers"` | |
Roles []string `bson:"roles" json:"roles"` | |
Addresses []Address `bson:"addresses" json:"addresses"` | |
SessionToken string `bson:"session_token" json:"session_token"` | |
IsEnabled bool `bson:"is_enabled" json:"is_enabled"` | |
IsActivated bool `bson:"is_activated" json:"is_activated"` | |
ActivationCode string `bson:"activiation_code" json:"activation_code"` | |
} | |
// Address holds the visitors billing/shipping address. A visitory can have | |
// multiple addresses but should only have one primary billing and shipping. | |
type Address struct { | |
PrimaryBilling bool `bson:"primary_billing" json:"primary_billing"` | |
PrimaryShipping bool `bson:"primary_shipping" json:"primary_shipping"` | |
StreetLine1 string `bson:"street_line1" json:"street_line1"` | |
StreetLine2 string `bson:"street_line2" json:"street_line2"` | |
City string `bson:"city" json:"city"` | |
State string `bson:"state" json:"state"` | |
Country string `bson:"country" json:"country"` | |
} | |
// PhoneNumber holds contact information for the visitor. Primarily | |
// used for shipping and billing. | |
type PhoneNumber struct { | |
Role string `bson:"role" json:"role"` | |
CountryCode int `bson:"country_code" json:"country_code"` | |
AreaCode int `bson:"area_code" json:"area_code"` | |
Digits int `bson:"digits" json:"digits"` | |
} | |
// HasRole returns true if the user has the visitor role provided. | |
// Will return false if the role is invalid. | |
func (v *Visitor) HasRole(role string) bool { | |
if !IsValidRole(role) { | |
return false | |
} | |
for _, r := range roles { | |
if r == role { | |
return true | |
} | |
} | |
return false | |
} | |
// LocalLogin compares the provided password and returns true if once hashes it matches. | |
func (v *Visitor) LocalLogin(password string) bool { | |
err := bcrypt.CompareHashAndPassword([]byte(v.Hash), []byte(password)) | |
return err == nil | |
} | |
// RegisterLocalRequest is used to bind form attributes when register a new user | |
// using local authentication. | |
type RegisterLocalRequest struct { | |
Email string `form:"email" binding:"required"` | |
Password string `form:"password" binding:"required"` | |
Fname string `form:"fname" binding:"required"` | |
Lname string `form:"lname" binding:"required"` | |
} | |
// RegisterLocalHandler handles a Post request to register a new member using local authentication. | |
func RegisterLocalHandler(db *mgo.Database, nv RegisterLocalRequest, r render.Render, s sessions.Session) { | |
v, err := NewVisitorFromEmail(nv.Email, nv.Password, nv.Fname, nv.Lname) | |
if err != nil { | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
// First, we must verify that this email is not registered. | |
_, err = GetVisitorByEmail(db, v.Email) | |
if err != nil { | |
switch err.Error() { | |
// If the existing email was not found, pass. If there was another error, | |
// consider it a 500 and stop. | |
case "not found": | |
default: | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
} else { | |
// TODO: If the user was found, send an email asking if the user | |
// forgot they had an existing account, but don't log them in. | |
r.HTML(http.StatusOK, "index", nil) | |
return | |
} | |
if err := InsertVisitor(db, v); err != nil { | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
s.Set("visitorID", v.ID.Hex()) | |
s.Set("token", v.SessionToken) | |
// r.HTML(http.StatusOK, "index", nil) | |
r.JSON(200, v) | |
} | |
// CreateLocalVisitor handles a post request to create a new local visitor | |
func CreateLocalVisitor(db *mgo.Database, nv RegisterLocalRequest, r render.Render) { | |
v, err := NewVisitorFromEmail(nv.Email, nv.Password, nv.Fname, nv.Lname) | |
if err != nil { | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
// First, we must verify that this email is not registered. | |
_, err = GetVisitorByEmail(db, v.Email) | |
if err != nil { | |
switch err.Error() { | |
// If the existing email was not found, pass. If there was another error, | |
// consider it a 500 and stop. | |
case "not found": | |
default: | |
r.Error(500) | |
return | |
} | |
} else { | |
// TODO If the user was found, send an email asking if the user | |
// forgot they had an existing account, but don't log them in. | |
r.Error(500) | |
return | |
} | |
if err := InsertVisitor(db, v); err != nil { | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
r.JSON(200, v) | |
} | |
// SessionVisitor is a martini middleware that looks for a visitorID in a session and maps either | |
// an existing or null Visitor to the current context. No authentication or authorization is performed. | |
// TODO: This could also map the visitors cart. | |
// TODO: Render custom error pages rather than returning 500. | |
func SessionVisitor(db *mgo.Database, s sessions.Session, c martini.Context, r render.Render) { | |
v := &Visitor{} | |
vid := s.Get("visitorID") | |
token := s.Get("token") | |
if vid != nil && token != nil { | |
// vid should never not be a string. So this is ok. | |
switch vid.(type) { | |
case string: | |
default: | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
if !bson.IsObjectIdHex(vid.(string)) { | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
switch token.(type) { | |
case string: | |
default: | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
var err error | |
v, err = GetVisitorByID(db, bson.ObjectIdHex(vid.(string))) | |
if err != nil { | |
switch err.Error() { | |
// Visitor was not found. So it could've been deleted. | |
// Clear the session. | |
case "not found": | |
s.Clear() | |
v = &Visitor{} | |
default: | |
s.Clear() | |
r.HTML(http.StatusInternalServerError, "internal_error", nil) | |
return | |
} | |
} else { | |
if token.(string) != v.SessionToken { | |
s.Clear() | |
v = &Visitor{} | |
} | |
} | |
} | |
c.Map(v) | |
} | |
// GetVisitorByID returns a pointer to a visitor with the provided id | |
// from the provided database. | |
func GetVisitorByID(db *mgo.Database, id bson.ObjectId) (*Visitor, error) { | |
v := &Visitor{} | |
err := db.C(COLLECTION).FindId(id).One(&v) | |
return v, err | |
} | |
// GetVisitorByEmail returns a pointer to a visitor with the provided email | |
// from the provided database. | |
func GetVisitorByEmail(db *mgo.Database, email string) (*Visitor, error) { | |
v := &Visitor{} | |
err := db.C(COLLECTION).Find(bson.M{"email": email}).One(&v) | |
return v, err | |
} | |
// NewVisitorFromEmail initializes a pointer to a new Visitor with the role of | |
// visitor. | |
func NewVisitorFromEmail(email, password, fname, lname string) (*Visitor, error) { | |
v := &Visitor{Email: email, Fname: fname, Lname: lname} | |
hash, err := hashPassword(password) | |
if err != nil { | |
return v, err | |
} | |
v.Hash = hash | |
v.Roles = append(v.Roles, VISITOR) | |
v.SessionToken = bson.Now().String() | |
v.ID = bson.NewObjectId() | |
v.IsEnabled = true | |
v.ActivationCode = uuid.New() | |
return v, nil | |
} | |
// InsertVisitor inserts a Visitor into the Mongodb instance. | |
func InsertVisitor(db *mgo.Database, v *Visitor) error { | |
return db.C(COLLECTION).Insert(v) | |
} | |
func hashPassword(password string) (string, error) { | |
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10) | |
if err != nil { | |
return "", err | |
} | |
return string(hash), nil | |
} | |
// IsValidRole returns true if the provided role is in roles. | |
func IsValidRole(role string) bool { | |
for _, r := range roles { | |
if role == r { | |
return true | |
} | |
} | |
return false | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment