added blog example

This commit is contained in:
Pedro Nasser
2016-08-30 16:47:34 -03:00
parent b95e88bfdf
commit c9de0428aa
14 changed files with 597 additions and 0 deletions

0
examples/blog/.gitignore vendored Normal file
View File

8
examples/blog/Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM iron/go:dev
ADD . $GOPATH/src/github.com/iron-io/functions/examples/blog
WORKDIR $GOPATH/src/github.com/iron-io/functions/examples/blog
RUN go get .
ENTRYPOINT ["go", "run", "main.go"]

103
examples/blog/README.md Normal file
View File

@@ -0,0 +1,103 @@
# Blog API Example
## Requirements
- Remote MongoDB instance (for example heroku)
## Development
### Building image locally
```
# SET BELOW TO YOUR DOCKER HUB USERNAME
USERNAME=YOUR_DOCKER_HUB_USERNAME
# build it
docker build -t $USERNAME/functions-blog .
```
### Publishing it
```
# tagging
docker run --rm -v "$PWD":/app treeder/bump patch
docker tag $USERNAME/functions-blog:latest $USERNAME/functions-blog:`cat VERSION`
# pushing to docker hub
docker push $USERNAME/functions-blog
```
## Running it on IronFunctions
### First, let's define this environment variables
```
# Set your Function server address
# Eg. 127.0.0.1:8080
FUNCAPI=YOUR_FUNCTIONS_ADDRESS
# Set your mongoDB server address
# Eg. 127.0.0.1:27017
MONGODB=YOUR_MONGODB_ADDRESS
```
### Creating our blog application in your IronFunctions
With this command we are going to create an application with name `blog` and also defining the app configuration `DB`.
```
curl -X POST --data '{
"app": {
"name": "blog",
"config": { "DB": "'$MONGODB'" }
}
}' http://$FUNCAPI/v1/apps
```
Now, we can create our blog routes: `/posts` and `/posts/:id`
```
curl -X POST --data '{
"route": {
"image": "'$USERNAME'/functions-blog",
"path": "/posts"
}
}' http://$FUNCAPI/v1/apps/blog/routes
```
```
curl -X POST --data '{
"route": {
"image": "'$USERNAME'/functions-blog",
"path": "/posts/:id"
}
}' http://$FUNCAPI/v1/apps/blog/routes
```
### Testing our Blog via API
Now that we created our IronFunction route, lets test our routes
```
curl -X GET http://$FUNCAPI/r/blog/posts
curl -X GET http://$FUNCAPI/r/blog/posts/123456
```
These commands should return `{"error":"Invalid authentication"}` because we aren't sending any token.
## Authentication
### Creating a blog user
First let's create our blog user.
```
```
###
To get authorized to access our Blog API endpoints we must request a new token with a valid user.
```
```

1
examples/blog/VERSION Normal file
View File

@@ -0,0 +1 @@
0.0.1

View File

@@ -0,0 +1,20 @@
package database
import "gopkg.in/mgo.v2"
type Database struct {
Session *mgo.Session
}
func New(uri string) *Database {
session, err := mgo.Dial(uri)
if err != nil {
panic(err)
}
session.SetMode(mgo.Monotonic, true)
return &Database{
Session: session,
}
}

View File

@@ -0,0 +1,73 @@
package database
import (
"errors"
"github.com/iron-io/functions/examples/blog/models"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
var (
ErrNotObjectIdHex = errors.New("Invalid ID")
)
func (db *Database) SavePost(post *models.Post) (*models.Post, error) {
s := db.Session.Copy()
defer s.Close()
c := s.DB("").C("post")
if post.ID.Hex() == "" {
post.ID = bson.NewObjectId()
}
id := post.ID
change := mgo.Change{
Update: bson.M{"$set": post},
ReturnNew: true,
Upsert: true,
}
_, err := c.Find(bson.M{"_id": id}).Apply(change, &post)
if err != nil {
return nil, err
}
return post, nil
}
func (db *Database) GetPost(id string) (*models.Post, error) {
s := db.Session.Copy()
defer s.Close()
c := s.DB("").C("post")
var post models.Post
if !bson.IsObjectIdHex(id) {
return nil, ErrNotObjectIdHex
}
err := c.Find(bson.M{"_id": bson.ObjectIdHex(id)}).One(&post)
if err != nil {
return nil, err
}
return &post, nil
}
func (db *Database) GetPosts(query []bson.M) ([]*models.Post, error) {
s := db.Session.Copy()
defer s.Close()
c := s.DB("").C("post")
var posts []*models.Post
err := c.Pipe(query).All(&posts)
if err != nil {
return nil, err
}
return posts, nil
}

View File

@@ -0,0 +1,65 @@
package database
import (
"github.com/iron-io/functions/examples/blog/models"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
func (db *Database) SaveUser(user *models.User) (*models.User, error) {
s := db.Session.Copy()
defer s.Close()
c := s.DB("").C("user")
id := user.Username
user.Username = ""
if len(user.Password) > 0 {
user.Password = models.UserPasswordEncrypt(user.Password)
}
change := mgo.Change{
Update: bson.M{"$set": user},
ReturnNew: true,
Upsert: true,
}
_, err := c.Find(bson.M{"_id": id}).Apply(change, &user)
if err != nil {
return nil, err
}
return user, nil
}
func (db *Database) GetUser(id string) (*models.User, error) {
s := db.Session.Copy()
defer s.Close()
c := s.DB("").C("user")
var user models.User
err := c.Find(bson.M{"_id": id}).One(&user)
if err != nil {
return nil, err
}
return &user, nil
}
func (db *Database) GetUsers(query []bson.M) ([]*models.User, error) {
s := db.Session.Copy()
defer s.Close()
c := s.DB("").C("user")
var users []*models.User
err := c.Pipe(query).All(&users)
if err != nil {
return nil, err
}
return users, nil
}

82
examples/blog/main.go Normal file
View File

@@ -0,0 +1,82 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/iron-io/functions/examples/blog/database"
"github.com/iron-io/functions/examples/blog/models"
"github.com/iron-io/functions/examples/blog/routes"
)
func main() {
request := fmt.Sprintf("%s %s", os.Getenv("METHOD"), os.Getenv("ROUTE"))
dbURI := os.Getenv("CONFIG_DB")
if dbURI == "" {
dbURI = "127.0.0.1/blog"
}
db := database.New(dbURI)
if created := createUser(db); created {
return
}
if os.Getenv("ROUTE") == "/token" {
route.HandleToken(db)
return
}
auth, valid := route.Authentication()
if !valid {
route.SendError("Invalid authentication")
return
}
switch request {
case "GET /posts":
route.HandlePostList(db, auth)
break
case "POST /posts":
route.HandlePostCreate(db, auth)
break
case "GET /posts/:id":
route.HandlePostRead(db, auth)
break
default:
route.SendError("Not found")
}
}
func createUser(db *database.Database) bool {
env := os.Getenv("NEWUSER")
if env == "" {
return false
}
var user *models.User
err := json.Unmarshal([]byte(env), &user)
if err != nil {
fmt.Println(err)
return true
}
if user.Username == "" || user.NewPassword == "" {
fmt.Println("missing username or password")
return true
}
user.Password = []byte(user.NewPassword)
user.NewPassword = ""
user, err = db.SaveUser(user)
if err != nil {
fmt.Println("couldn't create user")
} else {
fmt.Println("user created")
}
return true
}

View File

@@ -0,0 +1,9 @@
package models
import "gopkg.in/mgo.v2/bson"
type Post struct {
ID bson.ObjectId `json:"id" bson:"_id,omitempty"`
Title string `json:"title" bson:"title"`
Body string `json:"body" bson:"body"`
}

View File

@@ -0,0 +1,17 @@
package models
import "golang.org/x/crypto/bcrypt"
type User struct {
Username string `json:"username" bson:"_id,omitempty"`
Password []byte `json:"-" bson:"password"`
NewPassword string `json:"password" bson:"-"`
}
func UserPasswordEncrypt(pass []byte) []byte {
hashedPassword, err := bcrypt.GenerateFromPassword(pass, bcrypt.DefaultCost)
if err != nil {
panic(err)
}
return hashedPassword
}

View File

@@ -0,0 +1,30 @@
package route
import (
"encoding/json"
"fmt"
"os"
"github.com/iron-io/functions/examples/blog/database"
"github.com/iron-io/functions/examples/blog/models"
)
func HandlePostCreate(db *database.Database, auth map[string]interface{}) {
var post *models.Post
err := json.Unmarshal([]byte(os.Getenv("PAYLOAD")), &post)
if err != nil {
fmt.Println("Invalid post")
return
}
post, err = db.SavePost(post)
if err != nil {
fmt.Println("Couldn't save that post")
return
}
SendResponse(Response{
"post": post,
})
}

View File

@@ -0,0 +1,18 @@
package route
import (
"github.com/iron-io/functions/examples/blog/database"
"gopkg.in/mgo.v2/bson"
)
func HandlePostList(db *database.Database, auth map[string]interface{}) {
posts, err := db.GetPosts([]bson.M{})
if err != nil {
SendError("Couldn't retrieve posts")
return
}
SendResponse(Response{
"posts": posts,
})
}

View File

@@ -0,0 +1,26 @@
package route
import (
"os"
"github.com/iron-io/functions/examples/blog/database"
)
func HandlePostRead(db *database.Database, auth map[string]interface{}) {
id := os.Getenv("PARAM_ID")
if id == "" {
SendError("Missing post ID")
return
}
post, err := db.GetPost(id)
if err != nil {
SendError("Couldn't retrieve that post")
return
}
SendResponse(Response{
"post": post,
})
}

View File

@@ -0,0 +1,145 @@
package route
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/iron-io/functions/examples/blog/database"
"github.com/iron-io/functions/examples/blog/models"
"golang.org/x/crypto/bcrypt"
)
// import "github.com/iron-io/functions/examples/blog/database"
var jwtSignKey = []byte("mysecretblog")
type Response map[string]interface{}
func SendResponse(resp Response) {
data, _ := json.Marshal(resp)
fmt.Println(string(data))
}
func SendError(err interface{}) {
SendResponse(Response{
"error": err,
})
}
func HandleToken(db *database.Database) {
var login *models.User
err := json.Unmarshal([]byte(os.Getenv("PAYLOAD")), &login)
if err != nil {
fmt.Println("Missing username and password")
return
}
user, err := db.GetUser(login.Username)
if err != nil {
SendError("Couldn't create a token")
return
}
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(login.NewPassword)); err != nil {
SendError("Couldn't create a token")
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user": login.Username,
"exp": time.Now().Add(1 * time.Hour),
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(jwtSignKey)
if err != nil {
SendError("Couldn't create a token")
return
}
SendResponse(Response{"token": tokenString})
}
func Authentication() (map[string]interface{}, bool) {
authorization := os.Getenv("HEADER_AUTHORIZATION")
p := strings.Split(authorization, " ")
if len(p) <= 1 {
return nil, false
}
token, err := jwt.Parse(p[1], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return jwtSignKey, nil
})
if err != nil {
return nil, false
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return claims, true
}
return nil, false
}
// func New(db *database.Database) *gin.Engine {
// DB = db
// r := gin.New()
// r.POST("/auth", func(c *gin.Context) {
// username := c.PostForm("username")
// password := c.PostForm("password")
// user, err := db.GetUser(username)
// if err != nil {
// c.JSON(500, gin.H{"message": "Could not generate token"})
// return
// }
// err = bcrypt.CompareHashAndPassword(user.Password, []byte(password))
// if err != nil {
// c.JSON(500, gin.H{"message": "Could not generate token"})
// return
// }
// token := jwt_lib.New(jwt_lib.GetSigningMethod("HS256"))
// claims := token.Claims.(jwt_lib.MapClaims)
// claims["ID"] = username
// claims["exp"] = time.Now().Add(time.Hour * 1).Unix()
// tokenString, err := token.SignedString([]byte(jwtSignKey))
// if err != nil {
// c.JSON(500, gin.H{"message": "Could not generate token"})
// return
// }
// c.JSON(200, gin.H{"token": tokenString})
// })
// r.POST("/testuser", func(c *gin.Context) {
// _, err := db.SaveUser(&models.User{
// Username: "test",
// Password: []byte("test"),
// })
// if err != nil {
// c.JSON(500, gin.H{"message": "Could create test user"})
// return
// }
// c.JSON(200, gin.H{"message": "test user created"})
// })
// blog := r.Group("/blog")
// blog.Use(jwtAuth(jwtSignKey))
// blog.GET("/posts", handlePostList)
// blog.POST("/posts", handlePostCreate)
// blog.GET("/posts/:id", handlePostRead)
// return r
// }