mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
added blog example
This commit is contained in:
0
examples/blog/.gitignore
vendored
Normal file
0
examples/blog/.gitignore
vendored
Normal file
8
examples/blog/Dockerfile
Normal file
8
examples/blog/Dockerfile
Normal 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
103
examples/blog/README.md
Normal 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
1
examples/blog/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.0.1
|
||||
20
examples/blog/database/database.go
Normal file
20
examples/blog/database/database.go
Normal 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,
|
||||
}
|
||||
}
|
||||
73
examples/blog/database/post.go
Normal file
73
examples/blog/database/post.go
Normal 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
|
||||
}
|
||||
65
examples/blog/database/user.go
Normal file
65
examples/blog/database/user.go
Normal 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
82
examples/blog/main.go
Normal 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
|
||||
}
|
||||
9
examples/blog/models/post.go
Normal file
9
examples/blog/models/post.go
Normal 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"`
|
||||
}
|
||||
17
examples/blog/models/user.go
Normal file
17
examples/blog/models/user.go
Normal 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
|
||||
}
|
||||
30
examples/blog/routes/post_create.go
Normal file
30
examples/blog/routes/post_create.go
Normal 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,
|
||||
})
|
||||
}
|
||||
18
examples/blog/routes/post_list.go
Normal file
18
examples/blog/routes/post_list.go
Normal 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,
|
||||
})
|
||||
}
|
||||
26
examples/blog/routes/post_read.go
Normal file
26
examples/blog/routes/post_read.go
Normal 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,
|
||||
})
|
||||
}
|
||||
145
examples/blog/routes/server.go
Normal file
145
examples/blog/routes/server.go
Normal 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
|
||||
// }
|
||||
Reference in New Issue
Block a user