├── .circleci
└── config.yml
├── .env
├── .gitignore
├── .idea
├── .gitignore
├── fullstack.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── Dockerfile
├── README.md
├── api
├── auth
│ └── token.go
├── controllers
│ ├── base.go
│ ├── home_controller.go
│ ├── login_controller.go
│ ├── posts_controller.go
│ ├── routes.go
│ └── users_controller.go
├── middlewares
│ └── middlewares.go
├── models
│ ├── Post.go
│ └── User.go
├── responses
│ └── json.go
├── seed
│ └── seeder.go
├── server.go
└── utils
│ └── formaterror
│ └── formaterror.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
└── tests
├── controllertests
├── controller_test.go
├── login_controller_test.go
├── post_controller_test.go
└── user_controller_test.go
└── modeltests
├── model_test.go
├── post_model_test.go
└── user_model_test.go
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2 # use CircleCI 2.0
2 | jobs: # basic units of work in a run
3 | build: # runs not using Workflows must have a `build` job as entry point
4 | docker: # run the steps with Docker
5 | - image: circleci/golang:1.12
6 | - image: circleci/postgres:9.6-alpine
7 | environment: # environment variables for primary container
8 | POSTGRES_USER: steven
9 | POSTGRES_DB: fullstack_db_test
10 |
11 | environment: # environment variables for the build itself
12 | GO111MODULE: "on" #we don't rely on GOPATH
13 |
14 | working_directory: ~/usr/src/app # Go module is used, so we dont need to worry about GOPATH
15 |
16 | steps: # steps that comprise the `build` job
17 | - checkout # check out source code to working directory
18 | - run:
19 | name: "Fetch dependencies"
20 | command: go mod download
21 |
22 | # Wait for Postgres to be ready before proceeding
23 | - run:
24 | name: Waiting for Postgres to be ready
25 | command: dockerize -wait tcp://localhost:5432 -timeout 1m
26 |
27 | - run:
28 | name: Run unit tests
29 | environment: # environment variables for the database url and path to migration files
30 | FORUM_DB_URL: "postgres://steven@localhost:5432/fullstack_db_test?sslmode=disable"
31 | command: go test -v ./tests/... # our test is inside the "tests" folder, so target only that
32 |
33 | workflows:
34 | version: 2
35 | build-workflow:
36 | jobs:
37 | - build
38 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # Postgres Live
2 | # DB_HOST=127.0.0.1
3 | # DB_DRIVER=postgres
4 | # API_SECRET=98hbun98h #Used for creating a JWT. Can be anything
5 | # DB_USER=steven
6 | # DB_PASSWORD=
7 | # DB_NAME=fullstack_api
8 | # DB_PORT=5432
9 |
10 | # Postgres Test
11 | # TestDbHost=127.0.0.1
12 | # TestDbDriver=postgres
13 | # TestApiSecret=98hbun98h
14 | # TestDbUser=steven
15 | # TestDbPassword=
16 | # TestDbName=fullstack_api_test
17 | # TestDbPort=5432
18 |
19 | # Mysql Live
20 | # DB_HOST=127.0.0.1
21 | # DB_DRIVER=mysql
22 | # API_SECRET=98hbun98h #Used for creating a JWT. Can be anything
23 | # DB_USER=steven
24 | # DB_PASSWORD=here
25 | # DB_NAME=fullstack_mysql
26 | # DB_PORT=3306 #Default mysql port
27 |
28 | # # Mysql Test
29 | # TestDbHost=127.0.0.1
30 | # TestDbDriver=mysql
31 | # TestApiSecret=98hbun98h
32 | # TestDbUser=steven
33 | # TestDbPassword=here
34 | # TestDbName=fullstack_api_test
35 | # TestDbPort=3306
36 |
37 | # sqlite live
38 | DB_DRIVER=sqlite3
39 | API_SECRET=98hbun98h #Used for creating a JWT. Can be anything
40 | DB_NAME=fullstack.sqlite
41 |
42 | # sqlite test
43 | TestDbDriver=sqlite3
44 | TestApiSecret=98hbun98h
45 | TestDbName=fullstack_test.sqlite
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 | *.idea/*
3 | *.idea
4 | *.idea/.*
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Default ignored files
3 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/fullstack.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Start from golang base image
2 | FROM golang:alpine as builder
3 |
4 | # ENV GO111MODULE=on
5 |
6 | # Add Maintainer info
7 | LABEL maintainer="Steven Victor "
8 |
9 | # Install git.
10 | # Git is required for fetching the dependencies.
11 | RUN apk update && apk add --no-cache git
12 |
13 | # Set the current working directory inside the container
14 | WORKDIR /app
15 |
16 | # Copy go mod and sum files
17 | COPY go.mod go.sum ./
18 |
19 | # RUN go mod tidy
20 |
21 | # Download all dependencies. Dependencies will be cached if the go.mod and the go.sum files are not changed
22 | RUN go mod download
23 |
24 | # Copy the source from the current directory to the working Directory inside the container
25 |
26 | COPY . .
27 | # Build the Go app
28 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
29 |
30 | # Start a new stage from scratch
31 | FROM alpine:latest
32 | RUN apk --no-cache add ca-certificates
33 |
34 | WORKDIR /root/
35 |
36 | # Copy the Pre-built binary file from the previous stage
37 | COPY --from=builder /app/main .
38 | COPY --from=builder /app/.env .
39 |
40 | # Expose port 8080 to the outside world
41 | EXPOSE 8080
42 |
43 | #Command to run the executable
44 | CMD ["./main"]
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/victorsteven/Go-JWT-Postgres-Mysql-Restful-API)
2 |
3 | # Go-JWT-Postgres-Mysql-Restful-API
4 | This is an application built with golang, jwt, gorm, postgresql, mysql.
5 |
6 | You can follow the guide here:
7 | https://levelup.gitconnected.com/crud-restful-api-with-go-gorm-jwt-postgres-mysql-and-testing-460a85ab7121
8 |
9 | ### Dockerizing the API
10 | The dockerized API can be found here:
11 | https://github.com/victorsteven/Dockerized-Golang-Postgres-Mysql-API
12 |
--------------------------------------------------------------------------------
/api/auth/token.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "os"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | jwt "github.com/dgrijalva/jwt-go"
14 | )
15 |
16 | func CreateToken(user_id uint32) (string, error) {
17 | claims := jwt.MapClaims{}
18 | claims["authorized"] = true
19 | claims["user_id"] = user_id
20 | claims["exp"] = time.Now().Add(time.Hour * 1).Unix() //Token expires after 1 hour
21 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
22 | return token.SignedString([]byte(os.Getenv("API_SECRET")))
23 |
24 | }
25 |
26 | func TokenValid(r *http.Request) error {
27 | tokenString := ExtractToken(r)
28 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
29 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
30 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
31 | }
32 | return []byte(os.Getenv("API_SECRET")), nil
33 | })
34 | if err != nil {
35 | return err
36 | }
37 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
38 | Pretty(claims)
39 | }
40 | return nil
41 | }
42 |
43 | func ExtractToken(r *http.Request) string {
44 | keys := r.URL.Query()
45 | token := keys.Get("token")
46 | if token != "" {
47 | return token
48 | }
49 | bearerToken := r.Header.Get("Authorization")
50 | if len(strings.Split(bearerToken, " ")) == 2 {
51 | return strings.Split(bearerToken, " ")[1]
52 | }
53 | return ""
54 | }
55 |
56 | func ExtractTokenID(r *http.Request) (uint32, error) {
57 |
58 | tokenString := ExtractToken(r)
59 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
60 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
61 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
62 | }
63 | return []byte(os.Getenv("API_SECRET")), nil
64 | })
65 | if err != nil {
66 | return 0, err
67 | }
68 | claims, ok := token.Claims.(jwt.MapClaims)
69 | if ok && token.Valid {
70 | uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32)
71 | if err != nil {
72 | return 0, err
73 | }
74 | return uint32(uid), nil
75 | }
76 | return 0, nil
77 | }
78 |
79 | //Pretty display the claims licely in the terminal
80 | func Pretty(data interface{}) {
81 | b, err := json.MarshalIndent(data, "", " ")
82 | if err != nil {
83 | log.Println(err)
84 | return
85 | }
86 |
87 | fmt.Println(string(b))
88 | }
89 |
--------------------------------------------------------------------------------
/api/controllers/base.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 |
8 | "github.com/gorilla/mux"
9 | "github.com/jinzhu/gorm"
10 |
11 | _ "github.com/jinzhu/gorm/dialects/mysql" //mysql database driver
12 | _ "github.com/jinzhu/gorm/dialects/postgres" //postgres database driver
13 | _ "github.com/jinzhu/gorm/dialects/sqlite" // sqlite database driver
14 | "github.com/victorsteven/fullstack/api/models"
15 | )
16 |
17 | type Server struct {
18 | DB *gorm.DB
19 | Router *mux.Router
20 | }
21 |
22 | func (server *Server) Initialize(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) {
23 |
24 | var err error
25 |
26 | if Dbdriver == "mysql" {
27 | DBURL := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", DbUser, DbPassword, DbHost, DbPort, DbName)
28 | server.DB, err = gorm.Open(Dbdriver, DBURL)
29 | if err != nil {
30 | fmt.Printf("Cannot connect to %s database", Dbdriver)
31 | log.Fatal("This is the error:", err)
32 | } else {
33 | fmt.Printf("We are connected to the %s database", Dbdriver)
34 | }
35 | }
36 | if Dbdriver == "postgres" {
37 | DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
38 | server.DB, err = gorm.Open(Dbdriver, DBURL)
39 | if err != nil {
40 | fmt.Printf("Cannot connect to %s database", Dbdriver)
41 | log.Fatal("This is the error:", err)
42 | } else {
43 | fmt.Printf("We are connected to the %s database", Dbdriver)
44 | }
45 | }
46 | if Dbdriver == "sqlite3" {
47 | //DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
48 | server.DB, err = gorm.Open(Dbdriver, DbName)
49 | if err != nil {
50 | fmt.Printf("Cannot connect to %s database\n", Dbdriver)
51 | log.Fatal("This is the error:", err)
52 | } else {
53 | fmt.Printf("We are connected to the %s database\n", Dbdriver)
54 | }
55 | server.DB.Exec("PRAGMA foreign_keys = ON")
56 | }
57 |
58 | server.DB.Debug().AutoMigrate(&models.User{}, &models.Post{}) //database migration
59 |
60 | server.Router = mux.NewRouter()
61 |
62 | server.initializeRoutes()
63 | }
64 |
65 | func (server *Server) Run(addr string) {
66 | fmt.Println("Listening to port 8080")
67 | log.Fatal(http.ListenAndServe(addr, server.Router))
68 | }
69 |
--------------------------------------------------------------------------------
/api/controllers/home_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/victorsteven/fullstack/api/responses"
7 | )
8 |
9 | func (server *Server) Home(w http.ResponseWriter, r *http.Request) {
10 | responses.JSON(w, http.StatusOK, "Welcome To This Awesome API")
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/api/controllers/login_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "net/http"
7 |
8 | "github.com/victorsteven/fullstack/api/auth"
9 | "github.com/victorsteven/fullstack/api/models"
10 | "github.com/victorsteven/fullstack/api/responses"
11 | "github.com/victorsteven/fullstack/api/utils/formaterror"
12 | "golang.org/x/crypto/bcrypt"
13 | )
14 |
15 | func (server *Server) Login(w http.ResponseWriter, r *http.Request) {
16 | body, err := ioutil.ReadAll(r.Body)
17 | if err != nil {
18 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
19 | return
20 | }
21 | user := models.User{}
22 | err = json.Unmarshal(body, &user)
23 | if err != nil {
24 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
25 | return
26 | }
27 |
28 | user.Prepare()
29 | err = user.Validate("login")
30 | if err != nil {
31 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
32 | return
33 | }
34 | token, err := server.SignIn(user.Email, user.Password)
35 | if err != nil {
36 | formattedError := formaterror.FormatError(err.Error())
37 | responses.ERROR(w, http.StatusUnprocessableEntity, formattedError)
38 | return
39 | }
40 | responses.JSON(w, http.StatusOK, token)
41 | }
42 |
43 | func (server *Server) SignIn(email, password string) (string, error) {
44 |
45 | var err error
46 |
47 | user := models.User{}
48 |
49 | err = server.DB.Debug().Model(models.User{}).Where("email = ?", email).Take(&user).Error
50 | if err != nil {
51 | return "", err
52 | }
53 | err = models.VerifyPassword(user.Password, password)
54 | if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
55 | return "", err
56 | }
57 | return auth.CreateToken(user.ID)
58 | }
59 |
--------------------------------------------------------------------------------
/api/controllers/posts_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/gorilla/mux"
12 | "github.com/victorsteven/fullstack/api/auth"
13 | "github.com/victorsteven/fullstack/api/models"
14 | "github.com/victorsteven/fullstack/api/responses"
15 | "github.com/victorsteven/fullstack/api/utils/formaterror"
16 | )
17 |
18 | func (server *Server) CreatePost(w http.ResponseWriter, r *http.Request) {
19 |
20 | body, err := ioutil.ReadAll(r.Body)
21 | if err != nil {
22 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
23 | return
24 | }
25 | post := models.Post{}
26 | err = json.Unmarshal(body, &post)
27 | if err != nil {
28 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
29 | return
30 | }
31 | post.Prepare()
32 | err = post.Validate()
33 | if err != nil {
34 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
35 | return
36 | }
37 | uid, err := auth.ExtractTokenID(r)
38 | if err != nil {
39 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
40 | return
41 | }
42 | if uid != post.AuthorID {
43 | responses.ERROR(w, http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized)))
44 | return
45 | }
46 | postCreated, err := post.SavePost(server.DB)
47 | if err != nil {
48 | formattedError := formaterror.FormatError(err.Error())
49 | responses.ERROR(w, http.StatusInternalServerError, formattedError)
50 | return
51 | }
52 | w.Header().Set("Location", fmt.Sprintf("%s%s/%d", r.Host, r.URL.Path, postCreated.ID))
53 | responses.JSON(w, http.StatusCreated, postCreated)
54 | }
55 |
56 | func (server *Server) GetPosts(w http.ResponseWriter, r *http.Request) {
57 |
58 | post := models.Post{}
59 |
60 | posts, err := post.FindAllPosts(server.DB)
61 | if err != nil {
62 | responses.ERROR(w, http.StatusInternalServerError, err)
63 | return
64 | }
65 | responses.JSON(w, http.StatusOK, posts)
66 | }
67 |
68 | func (server *Server) GetPost(w http.ResponseWriter, r *http.Request) {
69 |
70 | vars := mux.Vars(r)
71 | pid, err := strconv.ParseUint(vars["id"], 10, 64)
72 | if err != nil {
73 | responses.ERROR(w, http.StatusBadRequest, err)
74 | return
75 | }
76 | post := models.Post{}
77 |
78 | postReceived, err := post.FindPostByID(server.DB, pid)
79 | if err != nil {
80 | responses.ERROR(w, http.StatusInternalServerError, err)
81 | return
82 | }
83 | responses.JSON(w, http.StatusOK, postReceived)
84 | }
85 |
86 | func (server *Server) UpdatePost(w http.ResponseWriter, r *http.Request) {
87 |
88 | vars := mux.Vars(r)
89 |
90 | // Check if the post id is valid
91 | pid, err := strconv.ParseUint(vars["id"], 10, 64)
92 | if err != nil {
93 | responses.ERROR(w, http.StatusBadRequest, err)
94 | return
95 | }
96 |
97 | //CHeck if the auth token is valid and get the user id from it
98 | uid, err := auth.ExtractTokenID(r)
99 | if err != nil {
100 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
101 | return
102 | }
103 |
104 | // Check if the post exist
105 | post := models.Post{}
106 | err = server.DB.Debug().Model(models.Post{}).Where("id = ?", pid).Take(&post).Error
107 | if err != nil {
108 | responses.ERROR(w, http.StatusNotFound, errors.New("Post not found"))
109 | return
110 | }
111 |
112 | // If a user attempt to update a post not belonging to him
113 | if uid != post.AuthorID {
114 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
115 | return
116 | }
117 | // Read the data posted
118 | body, err := ioutil.ReadAll(r.Body)
119 | if err != nil {
120 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
121 | return
122 | }
123 |
124 | // Start processing the request data
125 | postUpdate := models.Post{}
126 | err = json.Unmarshal(body, &postUpdate)
127 | if err != nil {
128 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
129 | return
130 | }
131 |
132 | //Also check if the request user id is equal to the one gotten from token
133 | if uid != postUpdate.AuthorID {
134 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
135 | return
136 | }
137 |
138 | postUpdate.Prepare()
139 | err = postUpdate.Validate()
140 | if err != nil {
141 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
142 | return
143 | }
144 |
145 | postUpdate.ID = post.ID //this is important to tell the model the post id to update, the other update field are set above
146 |
147 | postUpdated, err := postUpdate.UpdateAPost(server.DB)
148 |
149 | if err != nil {
150 | formattedError := formaterror.FormatError(err.Error())
151 | responses.ERROR(w, http.StatusInternalServerError, formattedError)
152 | return
153 | }
154 | responses.JSON(w, http.StatusOK, postUpdated)
155 | }
156 |
157 | func (server *Server) DeletePost(w http.ResponseWriter, r *http.Request) {
158 |
159 | vars := mux.Vars(r)
160 |
161 | // Is a valid post id given to us?
162 | pid, err := strconv.ParseUint(vars["id"], 10, 64)
163 | if err != nil {
164 | responses.ERROR(w, http.StatusBadRequest, err)
165 | return
166 | }
167 |
168 | // Is this user authenticated?
169 | uid, err := auth.ExtractTokenID(r)
170 | if err != nil {
171 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
172 | return
173 | }
174 |
175 | // Check if the post exist
176 | post := models.Post{}
177 | err = server.DB.Debug().Model(models.Post{}).Where("id = ?", pid).Take(&post).Error
178 | if err != nil {
179 | responses.ERROR(w, http.StatusNotFound, errors.New("Unauthorized"))
180 | return
181 | }
182 |
183 | // Is the authenticated user, the owner of this post?
184 | if uid != post.AuthorID {
185 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
186 | return
187 | }
188 | _, err = post.DeleteAPost(server.DB, pid, uid)
189 | if err != nil {
190 | responses.ERROR(w, http.StatusBadRequest, err)
191 | return
192 | }
193 | w.Header().Set("Entity", fmt.Sprintf("%d", pid))
194 | responses.JSON(w, http.StatusNoContent, "")
195 | }
196 |
--------------------------------------------------------------------------------
/api/controllers/routes.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import "github.com/victorsteven/fullstack/api/middlewares"
4 |
5 | func (s *Server) initializeRoutes() {
6 |
7 | // Home Route
8 | s.Router.HandleFunc("/", middlewares.SetMiddlewareJSON(s.Home)).Methods("GET")
9 |
10 | // Login Route
11 | s.Router.HandleFunc("/login", middlewares.SetMiddlewareJSON(s.Login)).Methods("POST")
12 |
13 | //Users routes
14 | s.Router.HandleFunc("/users", middlewares.SetMiddlewareJSON(s.CreateUser)).Methods("POST")
15 | s.Router.HandleFunc("/users", middlewares.SetMiddlewareJSON(s.GetUsers)).Methods("GET")
16 | s.Router.HandleFunc("/users/{id}", middlewares.SetMiddlewareJSON(s.GetUser)).Methods("GET")
17 | s.Router.HandleFunc("/users/{id}", middlewares.SetMiddlewareJSON(middlewares.SetMiddlewareAuthentication(s.UpdateUser))).Methods("PUT")
18 | s.Router.HandleFunc("/users/{id}", middlewares.SetMiddlewareAuthentication(s.DeleteUser)).Methods("DELETE")
19 |
20 | //Posts routes
21 | s.Router.HandleFunc("/posts", middlewares.SetMiddlewareJSON(s.CreatePost)).Methods("POST")
22 | s.Router.HandleFunc("/posts", middlewares.SetMiddlewareJSON(s.GetPosts)).Methods("GET")
23 | s.Router.HandleFunc("/posts/{id}", middlewares.SetMiddlewareJSON(s.GetPost)).Methods("GET")
24 | s.Router.HandleFunc("/posts/{id}", middlewares.SetMiddlewareJSON(middlewares.SetMiddlewareAuthentication(s.UpdatePost))).Methods("PUT")
25 | s.Router.HandleFunc("/posts/{id}", middlewares.SetMiddlewareAuthentication(s.DeletePost)).Methods("DELETE")
26 | }
27 |
--------------------------------------------------------------------------------
/api/controllers/users_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/gorilla/mux"
12 | "github.com/victorsteven/fullstack/api/auth"
13 | "github.com/victorsteven/fullstack/api/models"
14 | "github.com/victorsteven/fullstack/api/responses"
15 | "github.com/victorsteven/fullstack/api/utils/formaterror"
16 | )
17 |
18 | func (server *Server) CreateUser(w http.ResponseWriter, r *http.Request) {
19 |
20 | body, err := ioutil.ReadAll(r.Body)
21 | if err != nil {
22 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
23 | }
24 | user := models.User{}
25 | err = json.Unmarshal(body, &user)
26 | if err != nil {
27 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
28 | return
29 | }
30 | user.Prepare()
31 | err = user.Validate("")
32 | if err != nil {
33 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
34 | return
35 | }
36 | userCreated, err := user.SaveUser(server.DB)
37 |
38 | if err != nil {
39 |
40 | formattedError := formaterror.FormatError(err.Error())
41 |
42 | responses.ERROR(w, http.StatusInternalServerError, formattedError)
43 | return
44 | }
45 | w.Header().Set("Location", fmt.Sprintf("%s%s/%d", r.Host, r.RequestURI, userCreated.ID))
46 | responses.JSON(w, http.StatusCreated, userCreated)
47 | }
48 |
49 | func (server *Server) GetUsers(w http.ResponseWriter, r *http.Request) {
50 |
51 | user := models.User{}
52 |
53 | users, err := user.FindAllUsers(server.DB)
54 | if err != nil {
55 | responses.ERROR(w, http.StatusInternalServerError, err)
56 | return
57 | }
58 | responses.JSON(w, http.StatusOK, users)
59 | }
60 |
61 | func (server *Server) GetUser(w http.ResponseWriter, r *http.Request) {
62 |
63 | vars := mux.Vars(r)
64 | uid, err := strconv.ParseUint(vars["id"], 10, 32)
65 | if err != nil {
66 | responses.ERROR(w, http.StatusBadRequest, err)
67 | return
68 | }
69 | user := models.User{}
70 | userGotten, err := user.FindUserByID(server.DB, uint32(uid))
71 | if err != nil {
72 | responses.ERROR(w, http.StatusBadRequest, err)
73 | return
74 | }
75 | responses.JSON(w, http.StatusOK, userGotten)
76 | }
77 |
78 | func (server *Server) UpdateUser(w http.ResponseWriter, r *http.Request) {
79 |
80 | vars := mux.Vars(r)
81 | uid, err := strconv.ParseUint(vars["id"], 10, 32)
82 | if err != nil {
83 | responses.ERROR(w, http.StatusBadRequest, err)
84 | return
85 | }
86 | body, err := ioutil.ReadAll(r.Body)
87 | if err != nil {
88 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
89 | return
90 | }
91 | user := models.User{}
92 | err = json.Unmarshal(body, &user)
93 | if err != nil {
94 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
95 | return
96 | }
97 | tokenID, err := auth.ExtractTokenID(r)
98 | if err != nil {
99 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
100 | return
101 | }
102 | if tokenID != uint32(uid) {
103 | responses.ERROR(w, http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized)))
104 | return
105 | }
106 | user.Prepare()
107 | err = user.Validate("update")
108 | if err != nil {
109 | responses.ERROR(w, http.StatusUnprocessableEntity, err)
110 | return
111 | }
112 | updatedUser, err := user.UpdateAUser(server.DB, uint32(uid))
113 | if err != nil {
114 | formattedError := formaterror.FormatError(err.Error())
115 | responses.ERROR(w, http.StatusInternalServerError, formattedError)
116 | return
117 | }
118 | responses.JSON(w, http.StatusOK, updatedUser)
119 | }
120 |
121 | func (server *Server) DeleteUser(w http.ResponseWriter, r *http.Request) {
122 |
123 | vars := mux.Vars(r)
124 |
125 | user := models.User{}
126 |
127 | uid, err := strconv.ParseUint(vars["id"], 10, 32)
128 | if err != nil {
129 | responses.ERROR(w, http.StatusBadRequest, err)
130 | return
131 | }
132 | tokenID, err := auth.ExtractTokenID(r)
133 | if err != nil {
134 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
135 | return
136 | }
137 | if tokenID != 0 && tokenID != uint32(uid) {
138 | responses.ERROR(w, http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized)))
139 | return
140 | }
141 | _, err = user.DeleteAUser(server.DB, uint32(uid))
142 | if err != nil {
143 | responses.ERROR(w, http.StatusInternalServerError, err)
144 | return
145 | }
146 | w.Header().Set("Entity", fmt.Sprintf("%d", uid))
147 | responses.JSON(w, http.StatusNoContent, "")
148 | }
149 |
--------------------------------------------------------------------------------
/api/middlewares/middlewares.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/victorsteven/fullstack/api/auth"
8 | "github.com/victorsteven/fullstack/api/responses"
9 | )
10 |
11 | func SetMiddlewareJSON(next http.HandlerFunc) http.HandlerFunc {
12 | return func(w http.ResponseWriter, r *http.Request) {
13 | w.Header().Set("Content-Type", "application/json")
14 | next(w, r)
15 | }
16 | }
17 |
18 | func SetMiddlewareAuthentication(next http.HandlerFunc) http.HandlerFunc {
19 | return func(w http.ResponseWriter, r *http.Request) {
20 | err := auth.TokenValid(r)
21 | if err != nil {
22 | responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
23 | return
24 | }
25 | next(w, r)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/api/models/Post.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "html"
6 | "strings"
7 | "time"
8 |
9 | "github.com/jinzhu/gorm"
10 | )
11 |
12 | type Post struct {
13 | ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
14 | Title string `gorm:"size:255;not null;unique" json:"title"`
15 | Content string `gorm:"size:255;not null;" json:"content"`
16 | Author User `json:"author"`
17 | AuthorID uint32 `sql:"type:int REFERENCES users(id)" json:"author_id"`
18 | CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
19 | UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
20 | }
21 |
22 | func (p *Post) Prepare() {
23 | p.ID = 0
24 | p.Title = html.EscapeString(strings.TrimSpace(p.Title))
25 | p.Content = html.EscapeString(strings.TrimSpace(p.Content))
26 | p.Author = User{}
27 | p.CreatedAt = time.Now()
28 | p.UpdatedAt = time.Now()
29 | }
30 |
31 | func (p *Post) Validate() error {
32 |
33 | if p.Title == "" {
34 | return errors.New("Required Title")
35 | }
36 | if p.Content == "" {
37 | return errors.New("Required Content")
38 | }
39 | if p.AuthorID < 1 {
40 | return errors.New("Required Author")
41 | }
42 | return nil
43 | }
44 |
45 | func (p *Post) SavePost(db *gorm.DB) (*Post, error) {
46 | var err error
47 | err = db.Debug().Model(&Post{}).Create(&p).Error
48 | if err != nil {
49 | return &Post{}, err
50 | }
51 | if p.ID != 0 {
52 | err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
53 | if err != nil {
54 | return &Post{}, err
55 | }
56 | }
57 | return p, nil
58 | }
59 |
60 | func (p *Post) FindAllPosts(db *gorm.DB) (*[]Post, error) {
61 | var err error
62 | posts := []Post{}
63 | err = db.Debug().Model(&Post{}).Limit(100).Find(&posts).Error
64 | if err != nil {
65 | return &[]Post{}, err
66 | }
67 | if len(posts) > 0 {
68 | for i, _ := range posts {
69 | err := db.Debug().Model(&User{}).Where("id = ?", posts[i].AuthorID).Take(&posts[i].Author).Error
70 | if err != nil {
71 | return &[]Post{}, err
72 | }
73 | }
74 | }
75 | return &posts, nil
76 | }
77 |
78 | func (p *Post) FindPostByID(db *gorm.DB, pid uint64) (*Post, error) {
79 | var err error
80 | err = db.Debug().Model(&Post{}).Where("id = ?", pid).Take(&p).Error
81 | if err != nil {
82 | return &Post{}, err
83 | }
84 | if p.ID != 0 {
85 | err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
86 | if err != nil {
87 | return &Post{}, err
88 | }
89 | }
90 | return p, nil
91 | }
92 |
93 | func (p *Post) UpdateAPost(db *gorm.DB) (*Post, error) {
94 |
95 | var err error
96 | // db = db.Debug().Model(&Post{}).Where("id = ?", pid).Take(&Post{}).UpdateColumns(
97 | // map[string]interface{}{
98 | // "title": p.Title,
99 | // "content": p.Content,
100 | // "updated_at": time.Now(),
101 | // },
102 | // )
103 | // err = db.Debug().Model(&Post{}).Where("id = ?", pid).Take(&p).Error
104 | // if err != nil {
105 | // return &Post{}, err
106 | // }
107 | // if p.ID != 0 {
108 | // err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
109 | // if err != nil {
110 | // return &Post{}, err
111 | // }
112 | // }
113 | err = db.Debug().Model(&Post{}).Where("id = ?", p.ID).Updates(Post{Title: p.Title, Content: p.Content, UpdatedAt: time.Now()}).Error
114 | if err != nil {
115 | return &Post{}, err
116 | }
117 | if p.ID != 0 {
118 | err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
119 | if err != nil {
120 | return &Post{}, err
121 | }
122 | }
123 | return p, nil
124 | }
125 |
126 | func (p *Post) DeleteAPost(db *gorm.DB, pid uint64, uid uint32) (int64, error) {
127 |
128 | db = db.Debug().Model(&Post{}).Where("id = ? and author_id = ?", pid, uid).Take(&Post{}).Delete(&Post{})
129 |
130 | if db.Error != nil {
131 | if gorm.IsRecordNotFoundError(db.Error) {
132 | return 0, errors.New("Post not found")
133 | }
134 | return 0, db.Error
135 | }
136 | return db.RowsAffected, nil
137 | }
138 |
--------------------------------------------------------------------------------
/api/models/User.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "html"
6 | "log"
7 | "strings"
8 | "time"
9 |
10 | "github.com/badoux/checkmail"
11 | "github.com/jinzhu/gorm"
12 | "golang.org/x/crypto/bcrypt"
13 | )
14 |
15 | type User struct {
16 | ID uint32 `gorm:"primary_key;auto_increment" json:"id"`
17 | Nickname string `gorm:"size:255;not null;unique" json:"nickname"`
18 | Email string `gorm:"size:100;not null;unique" json:"email"`
19 | Password string `gorm:"size:100;not null;" json:"password"`
20 | CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
21 | UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
22 | }
23 |
24 | func Hash(password string) ([]byte, error) {
25 | return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
26 | }
27 |
28 | func VerifyPassword(hashedPassword, password string) error {
29 | return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
30 | }
31 |
32 | func (u *User) BeforeSave() error {
33 | hashedPassword, err := Hash(u.Password)
34 | if err != nil {
35 | return err
36 | }
37 | u.Password = string(hashedPassword)
38 | return nil
39 | }
40 |
41 | func (u *User) Prepare() {
42 | u.ID = 0
43 | u.Nickname = html.EscapeString(strings.TrimSpace(u.Nickname))
44 | u.Email = html.EscapeString(strings.TrimSpace(u.Email))
45 | u.CreatedAt = time.Now()
46 | u.UpdatedAt = time.Now()
47 | }
48 |
49 | func (u *User) Validate(action string) error {
50 | switch strings.ToLower(action) {
51 | case "update":
52 | if u.Nickname == "" {
53 | return errors.New("Required Nickname")
54 | }
55 | if u.Password == "" {
56 | return errors.New("Required Password")
57 | }
58 | if u.Email == "" {
59 | return errors.New("Required Email")
60 | }
61 | if err := checkmail.ValidateFormat(u.Email); err != nil {
62 | return errors.New("Invalid Email")
63 | }
64 |
65 | return nil
66 | case "login":
67 | if u.Password == "" {
68 | return errors.New("Required Password")
69 | }
70 | if u.Email == "" {
71 | return errors.New("Required Email")
72 | }
73 | if err := checkmail.ValidateFormat(u.Email); err != nil {
74 | return errors.New("Invalid Email")
75 | }
76 | return nil
77 |
78 | default:
79 | if u.Nickname == "" {
80 | return errors.New("Required Nickname")
81 | }
82 | if u.Password == "" {
83 | return errors.New("Required Password")
84 | }
85 | if u.Email == "" {
86 | return errors.New("Required Email")
87 | }
88 | if err := checkmail.ValidateFormat(u.Email); err != nil {
89 | return errors.New("Invalid Email")
90 | }
91 | return nil
92 | }
93 | }
94 |
95 | func (u *User) SaveUser(db *gorm.DB) (*User, error) {
96 |
97 | var err error
98 | err = db.Debug().Create(&u).Error
99 | if err != nil {
100 | return &User{}, err
101 | }
102 | return u, nil
103 | }
104 |
105 | func (u *User) FindAllUsers(db *gorm.DB) (*[]User, error) {
106 | var err error
107 | users := []User{}
108 | err = db.Debug().Model(&User{}).Limit(100).Find(&users).Error
109 | if err != nil {
110 | return &[]User{}, err
111 | }
112 | return &users, err
113 | }
114 |
115 | func (u *User) FindUserByID(db *gorm.DB, uid uint32) (*User, error) {
116 | var err error
117 | err = db.Debug().Model(User{}).Where("id = ?", uid).Take(&u).Error
118 | if err != nil {
119 | return &User{}, err
120 | }
121 | if gorm.IsRecordNotFoundError(err) {
122 | return &User{}, errors.New("User Not Found")
123 | }
124 | return u, err
125 | }
126 |
127 | func (u *User) UpdateAUser(db *gorm.DB, uid uint32) (*User, error) {
128 |
129 | // To hash the password
130 | err := u.BeforeSave()
131 | if err != nil {
132 | log.Fatal(err)
133 | }
134 | db = db.Debug().Model(&User{}).Where("id = ?", uid).Take(&User{}).UpdateColumns(
135 | map[string]interface{}{
136 | "password": u.Password,
137 | "nickname": u.Nickname,
138 | "email": u.Email,
139 | "updated_at": time.Now(),
140 | },
141 | )
142 | if db.Error != nil {
143 | return &User{}, db.Error
144 | }
145 | // This is the display the updated user
146 | err = db.Debug().Model(&User{}).Where("id = ?", uid).Take(&u).Error
147 | if err != nil {
148 | return &User{}, err
149 | }
150 | return u, nil
151 | }
152 |
153 | func (u *User) DeleteAUser(db *gorm.DB, uid uint32) (int64, error) {
154 |
155 | db = db.Debug().Model(&User{}).Where("id = ?", uid).Take(&User{}).Delete(&User{})
156 |
157 | if db.Error != nil {
158 | return 0, db.Error
159 | }
160 | return db.RowsAffected, nil
161 | }
162 |
--------------------------------------------------------------------------------
/api/responses/json.go:
--------------------------------------------------------------------------------
1 | package responses
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | func JSON(w http.ResponseWriter, statusCode int, data interface{}) {
10 | w.WriteHeader(statusCode)
11 | err := json.NewEncoder(w).Encode(data)
12 | if err != nil {
13 | fmt.Fprintf(w, "%s", err.Error())
14 | }
15 | }
16 |
17 | func ERROR(w http.ResponseWriter, statusCode int, err error) {
18 | if err != nil {
19 | JSON(w, statusCode, struct {
20 | Error string `json:"error"`
21 | }{
22 | Error: err.Error(),
23 | })
24 | return
25 | }
26 | JSON(w, http.StatusBadRequest, nil)
27 | }
28 |
--------------------------------------------------------------------------------
/api/seed/seeder.go:
--------------------------------------------------------------------------------
1 | package seed
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/jinzhu/gorm"
7 | "github.com/victorsteven/fullstack/api/models"
8 | )
9 |
10 | var users = []models.User{
11 | models.User{
12 | Nickname: "Steven victor",
13 | Email: "steven@gmail.com",
14 | Password: "password",
15 | },
16 | models.User{
17 | Nickname: "Martin Luther",
18 | Email: "luther@gmail.com",
19 | Password: "password",
20 | },
21 | }
22 |
23 | var posts = []models.Post{
24 | models.Post{
25 | Title: "Title 1",
26 | Content: "Hello world 1",
27 | },
28 | models.Post{
29 | Title: "Title 2",
30 | Content: "Hello world 2",
31 | },
32 | }
33 |
34 | func Load(db *gorm.DB) {
35 |
36 | err := db.Debug().DropTableIfExists(&models.Post{}, &models.User{}).Error
37 | if err != nil {
38 | log.Fatalf("cannot drop table: %v", err)
39 | }
40 | err = db.Debug().AutoMigrate(&models.User{}, &models.Post{}).Error
41 | if err != nil {
42 | log.Fatalf("cannot migrate table: %v", err)
43 | }
44 |
45 | /*
46 | err = db.Debug().Model(&models.Post{}).AddForeignKey("author_id", "users(id)", "cascade", "cascade").Error
47 | if err != nil {
48 | log.Fatalf("attaching foreign key error: %v", err)
49 | }
50 | */
51 |
52 | for i, _ := range users {
53 | err = db.Debug().Model(&models.User{}).Create(&users[i]).Error
54 | if err != nil {
55 | log.Fatalf("cannot seed users table: %v", err)
56 | }
57 | posts[i].AuthorID = users[i].ID
58 |
59 | err = db.Debug().Model(&models.Post{}).Create(&posts[i]).Error
60 | if err != nil {
61 | log.Fatalf("cannot seed posts table: %v", err)
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/api/server.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 | "github.com/victorsteven/fullstack/api/controllers"
10 | "github.com/victorsteven/fullstack/api/seed"
11 | )
12 |
13 | var server = controllers.Server{}
14 |
15 | func init() {
16 | // loads values from .env into the system
17 | if err := godotenv.Load(); err != nil {
18 | log.Print("sad .env file found")
19 | }
20 | }
21 |
22 | func Run() {
23 |
24 | var err error
25 | err = godotenv.Load()
26 | if err != nil {
27 | log.Fatalf("Error getting env, %v", err)
28 | } else {
29 | fmt.Println("We are getting the env values")
30 | }
31 |
32 | server.Initialize(os.Getenv("DB_DRIVER"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_PORT"), os.Getenv("DB_HOST"), os.Getenv("DB_NAME"))
33 |
34 | seed.Load(server.DB)
35 |
36 | server.Run(":8080")
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/api/utils/formaterror/formaterror.go:
--------------------------------------------------------------------------------
1 | package formaterror
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | func FormatError(err string) error {
9 |
10 | if strings.Contains(err, "nickname") {
11 | return errors.New("Nickname Already Taken")
12 | }
13 |
14 | if strings.Contains(err, "email") {
15 | return errors.New("Email Already Taken")
16 | }
17 |
18 | if strings.Contains(err, "title") {
19 | return errors.New("Title Already Taken")
20 | }
21 | if strings.Contains(err, "hashedPassword") {
22 | return errors.New("Incorrect Password")
23 | }
24 | return errors.New("Incorrect Details")
25 | }
26 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | app:
4 | container_name: full_app
5 | build: .
6 | ports:
7 | - 8080:8080 # Forward the exposed port 8080 on the container to port 8080 on the host machine
8 | restart: on-failure
9 | volumes:
10 | - api:/usr/src/app/
11 | depends_on:
12 | - postgres # This service depends on postgres. Start that first.
13 | # - mysql # This service depends on mysql. Start that first.
14 | networks:
15 | - fullstack
16 |
17 | postgres:
18 | image: postgres:latest
19 | container_name: full_db
20 | environment:
21 | - POSTGRES_USER=${DB_USER}
22 | - POSTGRES_PASSWORD=${DB_PASSWORD}
23 | - POSTGRES_DB=${DB_NAME}
24 | - DATABASE_HOST=${DB_HOST}
25 | ports:
26 | - '5432:5432'
27 | volumes:
28 | - database_postgres:/var/lib/postgresql/data
29 | networks:
30 | - fullstack
31 |
32 | # mysql:
33 | # image: mysql:5.7
34 | # ports:
35 | # - 3306:3306
36 | # environment:
37 | # - MYSQL_DATABASE=${DB_NAME}
38 | # - MYSQL_USER=${DB_USER}
39 | # - MYSQL_PASSWORD=${DB_PASSWORD}
40 | # - MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
41 | # - DATABASE_HOST=${DB_HOST}
42 | # volumes:
43 | # - database_mysql:/var/lib/postgresql/data
44 | # networks:
45 | # - fullstack
46 |
47 | volumes:
48 | api:
49 | database_postgres:
50 |
51 | # Networks to be created to facilitate communication between containers
52 | networks:
53 | fullstack:
54 |
55 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/victorsteven/fullstack
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/DATA-DOG/go-sqlmock v1.3.3
7 | github.com/badoux/checkmail v0.0.0-20181210160741-9661bd69e9ad
8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
9 | github.com/fatih/structs v1.1.0
10 | github.com/gin-gonic/gin v1.4.0
11 | github.com/go-sql-driver/mysql v1.4.1
12 | github.com/go-test/deep v1.0.2
13 | github.com/gorilla/handlers v1.4.2
14 | github.com/gorilla/mux v1.6.2
15 | github.com/jinzhu/gorm v1.9.10
16 | github.com/joho/godotenv v1.3.0
17 | github.com/kr/pretty v0.1.0 // indirect
18 | github.com/satori/go.uuid v1.2.0
19 | github.com/stretchr/testify v1.3.0
20 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
21 | gopkg.in/go-playground/assert.v1 v1.2.1
22 | )
23 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
4 | cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
6 | github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
7 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
8 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
9 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
10 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
11 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
12 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
13 | github.com/badoux/checkmail v0.0.0-20181210160741-9661bd69e9ad h1:kXfVkP8xPSJXzicomzjECcw6tv1Wl9h1lNenWBfNKdg=
14 | github.com/badoux/checkmail v0.0.0-20181210160741-9661bd69e9ad/go.mod h1:r5ZalvRl3tXevRNJkwIB6DC4DD3DMjIlY9NEU1XGoaQ=
15 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
16 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
20 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
21 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
22 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
23 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
24 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
25 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
26 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
27 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
28 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
29 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
30 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
31 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
32 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
33 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
34 | github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
35 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
36 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
37 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
38 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
39 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
40 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
41 | github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
42 | github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
43 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
44 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
45 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
46 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
47 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
48 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
49 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
50 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
51 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
52 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
53 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
54 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
55 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
56 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
57 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
58 | github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
59 | github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
60 | github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
61 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
62 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
63 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
64 | github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac=
65 | github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
66 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
67 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
68 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
69 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
70 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
71 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
72 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
73 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
74 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
75 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
76 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
77 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
78 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
79 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
80 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
81 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
82 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
83 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
84 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
85 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
86 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
87 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
88 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
89 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
90 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
91 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
92 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
93 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
94 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
95 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
96 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
97 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
98 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
99 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
100 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
101 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
102 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
103 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
104 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
105 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
106 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
107 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
108 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
109 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
110 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
111 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
112 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
113 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
114 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
115 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
116 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
117 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
118 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
119 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
120 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
121 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
122 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
123 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
124 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
125 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
126 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
127 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
128 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
129 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
130 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
131 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
132 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
133 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
134 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
135 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
136 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
137 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
138 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
139 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
140 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
141 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
142 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
143 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
144 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
145 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
146 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
147 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
148 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
149 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
150 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
151 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
152 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
153 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
154 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
155 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
156 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
157 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
158 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
159 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
160 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
161 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
162 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
163 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
164 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
165 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
166 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
167 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
168 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
169 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
170 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
171 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
172 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
173 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
174 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
175 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
176 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
177 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
178 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
179 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
180 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
181 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
182 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
183 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
184 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
185 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
186 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/victorsteven/fullstack/api"
5 | )
6 |
7 | func main() {
8 |
9 | api.Run()
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/tests/controllertests/controller_test.go:
--------------------------------------------------------------------------------
1 | package controllertests
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "testing"
8 |
9 | "github.com/jinzhu/gorm"
10 | "github.com/joho/godotenv"
11 | "github.com/victorsteven/fullstack/api/controllers"
12 | "github.com/victorsteven/fullstack/api/models"
13 | )
14 |
15 | var server = controllers.Server{}
16 | var userInstance = models.User{}
17 | var postInstance = models.Post{}
18 |
19 | func TestMain(m *testing.M) {
20 | err := godotenv.Load(os.ExpandEnv("../../.env"))
21 | if err != nil {
22 | log.Fatalf("Error getting env %v\n", err)
23 | }
24 | Database()
25 |
26 | os.Exit(m.Run())
27 |
28 | }
29 |
30 | func Database() {
31 |
32 | var err error
33 |
34 | TestDbDriver := os.Getenv("TestDbDriver")
35 |
36 | if TestDbDriver == "mysql" {
37 | DBURL := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", os.Getenv("TestDbUser"), os.Getenv("TestDbPassword"), os.Getenv("TestDbHost"), os.Getenv("TestDbPort"), os.Getenv("TestDbName"))
38 | server.DB, err = gorm.Open(TestDbDriver, DBURL)
39 | if err != nil {
40 | fmt.Printf("Cannot connect to %s database\n", TestDbDriver)
41 | log.Fatal("This is the error:", err)
42 | } else {
43 | fmt.Printf("We are connected to the %s database\n", TestDbDriver)
44 | }
45 | }
46 | if TestDbDriver == "postgres" {
47 | DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", os.Getenv("TestDbHost"), os.Getenv("TestDbPort"), os.Getenv("TestDbUser"), os.Getenv("TestDbName"), os.Getenv("TestDbPassword"))
48 | server.DB, err = gorm.Open(TestDbDriver, DBURL)
49 | if err != nil {
50 | fmt.Printf("Cannot connect to %s database\n", TestDbDriver)
51 | log.Fatal("This is the error:", err)
52 | } else {
53 | fmt.Printf("We are connected to the %s database\n", TestDbDriver)
54 | }
55 | }
56 | if TestDbDriver == "sqlite3" {
57 | //DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
58 | testDbName := os.Getenv("TestDbName")
59 | server.DB, err = gorm.Open(TestDbDriver, testDbName)
60 | if err != nil {
61 | fmt.Printf("Cannot connect to %s database\n", TestDbDriver)
62 | log.Fatal("This is the error:", err)
63 | } else {
64 | fmt.Printf("We are connected to the %s database\n", TestDbDriver)
65 | }
66 | server.DB.Exec("PRAGMA foreign_keys = ON")
67 | }
68 | }
69 |
70 | func refreshUserTable() error {
71 | /*
72 | err := server.DB.DropTableIfExists(&models.User{}).Error
73 | if err != nil {
74 | return err
75 | }
76 | err = server.DB.AutoMigrate(&models.User{}).Error
77 | if err != nil {
78 | return err
79 | }
80 | */
81 | err := server.DB.DropTableIfExists(&models.Post{}, &models.User{}).Error
82 | if err != nil {
83 | return err
84 | }
85 | err = server.DB.AutoMigrate(&models.User{}, &models.Post{}).Error
86 | if err != nil {
87 | return err
88 | }
89 |
90 | log.Printf("Successfully refreshed table(s)")
91 | return nil
92 | }
93 |
94 | func seedOneUser() (models.User, error) {
95 |
96 | err := refreshUserTable()
97 | if err != nil {
98 | log.Fatal(err)
99 | }
100 |
101 | user := models.User{
102 | Nickname: "Pet",
103 | Email: "pet@gmail.com",
104 | Password: "password",
105 | }
106 |
107 | err = server.DB.Model(&models.User{}).Create(&user).Error
108 | if err != nil {
109 | return models.User{}, err
110 | }
111 | return user, nil
112 | }
113 |
114 | func seedUsers() ([]models.User, error) {
115 |
116 | var err error
117 | if err != nil {
118 | return nil, err
119 | }
120 | users := []models.User{
121 | models.User{
122 | Nickname: "Steven victor",
123 | Email: "steven@gmail.com",
124 | Password: "password",
125 | },
126 | models.User{
127 | Nickname: "Kenny Morris",
128 | Email: "kenny@gmail.com",
129 | Password: "password",
130 | },
131 | }
132 | for i, _ := range users {
133 | err := server.DB.Model(&models.User{}).Create(&users[i]).Error
134 | if err != nil {
135 | return []models.User{}, err
136 | }
137 | }
138 | return users, nil
139 | }
140 |
141 | func refreshUserAndPostTable() error {
142 |
143 | err := server.DB.DropTableIfExists(&models.Post{}, &models.User{}).Error
144 | if err != nil {
145 | return err
146 | }
147 | err = server.DB.AutoMigrate(&models.User{}, &models.Post{}).Error
148 | if err != nil {
149 | return err
150 | }
151 | log.Printf("Successfully refreshed tables")
152 | return nil
153 | }
154 |
155 | func seedOneUserAndOnePost() (models.Post, error) {
156 |
157 | err := refreshUserAndPostTable()
158 | if err != nil {
159 | return models.Post{}, err
160 | }
161 | user := models.User{
162 | Nickname: "Sam Phil",
163 | Email: "sam@gmail.com",
164 | Password: "password",
165 | }
166 | err = server.DB.Model(&models.User{}).Create(&user).Error
167 | if err != nil {
168 | return models.Post{}, err
169 | }
170 | post := models.Post{
171 | Title: "This is the title sam",
172 | Content: "This is the content sam",
173 | AuthorID: user.ID,
174 | }
175 | err = server.DB.Model(&models.Post{}).Create(&post).Error
176 | if err != nil {
177 | return models.Post{}, err
178 | }
179 | return post, nil
180 | }
181 |
182 | func seedUsersAndPosts() ([]models.User, []models.Post, error) {
183 |
184 | var err error
185 |
186 | if err != nil {
187 | return []models.User{}, []models.Post{}, err
188 | }
189 | var users = []models.User{
190 | models.User{
191 | Nickname: "Steven victor",
192 | Email: "steven@gmail.com",
193 | Password: "password",
194 | },
195 | models.User{
196 | Nickname: "Magu Frank",
197 | Email: "magu@gmail.com",
198 | Password: "password",
199 | },
200 | }
201 | var posts = []models.Post{
202 | models.Post{
203 | Title: "Title 1",
204 | Content: "Hello world 1",
205 | },
206 | models.Post{
207 | Title: "Title 2",
208 | Content: "Hello world 2",
209 | },
210 | }
211 |
212 | for i, _ := range users {
213 | err = server.DB.Model(&models.User{}).Create(&users[i]).Error
214 | if err != nil {
215 | log.Fatalf("cannot seed users table: %v", err)
216 | }
217 | posts[i].AuthorID = users[i].ID
218 |
219 | err = server.DB.Model(&models.Post{}).Create(&posts[i]).Error
220 | if err != nil {
221 | log.Fatalf("cannot seed posts table: %v", err)
222 | }
223 | }
224 | return users, posts, nil
225 | }
226 |
--------------------------------------------------------------------------------
/tests/controllertests/login_controller_test.go:
--------------------------------------------------------------------------------
1 | package controllertests
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "log"
9 | "net/http"
10 | "net/http/httptest"
11 | "testing"
12 |
13 | "gopkg.in/go-playground/assert.v1"
14 | )
15 |
16 | func TestSignIn(t *testing.T) {
17 |
18 | err := refreshUserTable()
19 | if err != nil {
20 | log.Fatal(err)
21 | }
22 | user, err := seedOneUser()
23 | if err != nil {
24 | fmt.Printf("This is the error %v\n", err)
25 | }
26 |
27 | samples := []struct {
28 | email string
29 | password string
30 | errorMessage string
31 | }{
32 | {
33 | email: user.Email,
34 | password: "password", //Note the password has to be this, not the hashed one from the database
35 | errorMessage: "",
36 | },
37 | {
38 | email: user.Email,
39 | password: "Wrong password",
40 | errorMessage: "crypto/bcrypt: hashedPassword is not the hash of the given password",
41 | },
42 | {
43 | email: "Wrong email",
44 | password: "password",
45 | errorMessage: "record not found",
46 | },
47 | }
48 |
49 | for _, v := range samples {
50 |
51 | token, err := server.SignIn(v.email, v.password)
52 | if err != nil {
53 | assert.Equal(t, err, errors.New(v.errorMessage))
54 | } else {
55 | assert.NotEqual(t, token, "")
56 | }
57 | }
58 | }
59 |
60 | func TestLogin(t *testing.T) {
61 |
62 | refreshUserTable()
63 |
64 | _, err := seedOneUser()
65 | if err != nil {
66 | fmt.Printf("This is the error %v\n", err)
67 | }
68 | samples := []struct {
69 | inputJSON string
70 | statusCode int
71 | email string
72 | password string
73 | errorMessage string
74 | }{
75 | {
76 | inputJSON: `{"email": "pet@gmail.com", "password": "password"}`,
77 | statusCode: 200,
78 | errorMessage: "",
79 | },
80 | {
81 | inputJSON: `{"email": "pet@gmail.com", "password": "wrong password"}`,
82 | statusCode: 422,
83 | errorMessage: "Incorrect Password",
84 | },
85 | {
86 | inputJSON: `{"email": "frank@gmail.com", "password": "password"}`,
87 | statusCode: 422,
88 | errorMessage: "Incorrect Details",
89 | },
90 | {
91 | inputJSON: `{"email": "kangmail.com", "password": "password"}`,
92 | statusCode: 422,
93 | errorMessage: "Invalid Email",
94 | },
95 | {
96 | inputJSON: `{"email": "", "password": "password"}`,
97 | statusCode: 422,
98 | errorMessage: "Required Email",
99 | },
100 | {
101 | inputJSON: `{"email": "kan@gmail.com", "password": ""}`,
102 | statusCode: 422,
103 | errorMessage: "Required Password",
104 | },
105 | {
106 | inputJSON: `{"email": "", "password": "password"}`,
107 | statusCode: 422,
108 | errorMessage: "Required Email",
109 | },
110 | }
111 |
112 | for _, v := range samples {
113 |
114 | req, err := http.NewRequest("POST", "/login", bytes.NewBufferString(v.inputJSON))
115 | if err != nil {
116 | t.Errorf("this is the error: %v", err)
117 | }
118 | rr := httptest.NewRecorder()
119 | handler := http.HandlerFunc(server.Login)
120 | handler.ServeHTTP(rr, req)
121 |
122 | assert.Equal(t, rr.Code, v.statusCode)
123 | if v.statusCode == 200 {
124 | assert.NotEqual(t, rr.Body.String(), "")
125 | }
126 |
127 | if v.statusCode == 422 && v.errorMessage != "" {
128 | responseMap := make(map[string]interface{})
129 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
130 | if err != nil {
131 | t.Errorf("Cannot convert to json: %v", err)
132 | }
133 | assert.Equal(t, responseMap["error"], v.errorMessage)
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/tests/controllertests/post_controller_test.go:
--------------------------------------------------------------------------------
1 | package controllertests
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "net/http/httptest"
10 | "strconv"
11 | "testing"
12 |
13 | "github.com/gorilla/mux"
14 | "github.com/victorsteven/fullstack/api/models"
15 | "gopkg.in/go-playground/assert.v1"
16 | )
17 |
18 | func TestCreatePost(t *testing.T) {
19 |
20 | err := refreshUserAndPostTable()
21 | if err != nil {
22 | log.Fatal(err)
23 | }
24 | user, err := seedOneUser()
25 | if err != nil {
26 | log.Fatalf("Cannot seed user %v\n", err)
27 | }
28 | token, err := server.SignIn(user.Email, "password") //Note the password in the database is already hashed, we want unhashed
29 | if err != nil {
30 | log.Fatalf("cannot login: %v\n", err)
31 | }
32 | tokenString := fmt.Sprintf("Bearer %v", token)
33 |
34 | samples := []struct {
35 | inputJSON string
36 | statusCode int
37 | title string
38 | content string
39 | author_id uint32
40 | tokenGiven string
41 | errorMessage string
42 | }{
43 | {
44 | inputJSON: `{"title":"The title", "content": "the content", "author_id": 1}`,
45 | statusCode: 201,
46 | tokenGiven: tokenString,
47 | title: "The title",
48 | content: "the content",
49 | author_id: user.ID,
50 | errorMessage: "",
51 | },
52 | {
53 | inputJSON: `{"title":"The title", "content": "the content", "author_id": 1}`,
54 | statusCode: 500,
55 | tokenGiven: tokenString,
56 | errorMessage: "Title Already Taken",
57 | },
58 | {
59 | // When no token is passed
60 | inputJSON: `{"title":"When no token is passed", "content": "the content", "author_id": 1}`,
61 | statusCode: 401,
62 | tokenGiven: "",
63 | errorMessage: "Unauthorized",
64 | },
65 | {
66 | // When incorrect token is passed
67 | inputJSON: `{"title":"When incorrect token is passed", "content": "the content", "author_id": 1}`,
68 | statusCode: 401,
69 | tokenGiven: "This is an incorrect token",
70 | errorMessage: "Unauthorized",
71 | },
72 | {
73 | inputJSON: `{"title": "", "content": "The content", "author_id": 1}`,
74 | statusCode: 422,
75 | tokenGiven: tokenString,
76 | errorMessage: "Required Title",
77 | },
78 | {
79 | inputJSON: `{"title": "This is a title", "content": "", "author_id": 1}`,
80 | statusCode: 422,
81 | tokenGiven: tokenString,
82 | errorMessage: "Required Content",
83 | },
84 | {
85 | inputJSON: `{"title": "This is an awesome title", "content": "the content"}`,
86 | statusCode: 422,
87 | tokenGiven: tokenString,
88 | errorMessage: "Required Author",
89 | },
90 | {
91 | // When user 2 uses user 1 token
92 | inputJSON: `{"title": "This is an awesome title", "content": "the content", "author_id": 2}`,
93 | statusCode: 401,
94 | tokenGiven: tokenString,
95 | errorMessage: "Unauthorized",
96 | },
97 | }
98 | for _, v := range samples {
99 |
100 | req, err := http.NewRequest("POST", "/posts", bytes.NewBufferString(v.inputJSON))
101 | if err != nil {
102 | t.Errorf("this is the error: %v\n", err)
103 | }
104 | rr := httptest.NewRecorder()
105 | handler := http.HandlerFunc(server.CreatePost)
106 |
107 | req.Header.Set("Authorization", v.tokenGiven)
108 | handler.ServeHTTP(rr, req)
109 |
110 | responseMap := make(map[string]interface{})
111 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
112 | if err != nil {
113 | fmt.Printf("Cannot convert to json: %v", err)
114 | }
115 | assert.Equal(t, rr.Code, v.statusCode)
116 | if v.statusCode == 201 {
117 | assert.Equal(t, responseMap["title"], v.title)
118 | assert.Equal(t, responseMap["content"], v.content)
119 | assert.Equal(t, responseMap["author_id"], float64(v.author_id)) //just for both ids to have the same type
120 | }
121 | if v.statusCode == 401 || v.statusCode == 422 || v.statusCode == 500 && v.errorMessage != "" {
122 | assert.Equal(t, responseMap["error"], v.errorMessage)
123 | }
124 | }
125 | }
126 |
127 | func TestGetPosts(t *testing.T) {
128 |
129 | err := refreshUserAndPostTable()
130 | if err != nil {
131 | log.Fatal(err)
132 | }
133 | _, _, err = seedUsersAndPosts()
134 | if err != nil {
135 | log.Fatal(err)
136 | }
137 |
138 | req, err := http.NewRequest("GET", "/posts", nil)
139 | if err != nil {
140 | t.Errorf("this is the error: %v\n", err)
141 | }
142 | rr := httptest.NewRecorder()
143 | handler := http.HandlerFunc(server.GetPosts)
144 | handler.ServeHTTP(rr, req)
145 |
146 | var posts []models.Post
147 | err = json.Unmarshal([]byte(rr.Body.String()), &posts)
148 |
149 | assert.Equal(t, rr.Code, http.StatusOK)
150 | assert.Equal(t, len(posts), 2)
151 | }
152 | func TestGetPostByID(t *testing.T) {
153 |
154 | err := refreshUserAndPostTable()
155 | if err != nil {
156 | log.Fatal(err)
157 | }
158 | post, err := seedOneUserAndOnePost()
159 | if err != nil {
160 | log.Fatal(err)
161 | }
162 | postSample := []struct {
163 | id string
164 | statusCode int
165 | title string
166 | content string
167 | author_id uint32
168 | errorMessage string
169 | }{
170 | {
171 | id: strconv.Itoa(int(post.ID)),
172 | statusCode: 200,
173 | title: post.Title,
174 | content: post.Content,
175 | author_id: post.AuthorID,
176 | },
177 | {
178 | id: "unknwon",
179 | statusCode: 400,
180 | },
181 | }
182 | for _, v := range postSample {
183 |
184 | req, err := http.NewRequest("GET", "/posts", nil)
185 | if err != nil {
186 | t.Errorf("this is the error: %v\n", err)
187 | }
188 | req = mux.SetURLVars(req, map[string]string{"id": v.id})
189 |
190 | rr := httptest.NewRecorder()
191 | handler := http.HandlerFunc(server.GetPost)
192 | handler.ServeHTTP(rr, req)
193 |
194 | responseMap := make(map[string]interface{})
195 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
196 | if err != nil {
197 | log.Fatalf("Cannot convert to json: %v", err)
198 | }
199 | assert.Equal(t, rr.Code, v.statusCode)
200 |
201 | if v.statusCode == 200 {
202 | assert.Equal(t, post.Title, responseMap["title"])
203 | assert.Equal(t, post.Content, responseMap["content"])
204 | assert.Equal(t, float64(post.AuthorID), responseMap["author_id"]) //the response author id is float64
205 | }
206 | }
207 | }
208 |
209 | func TestUpdatePost(t *testing.T) {
210 |
211 | var PostUserEmail, PostUserPassword string
212 | var AuthPostAuthorID uint32
213 | var AuthPostID uint64
214 |
215 | err := refreshUserAndPostTable()
216 | if err != nil {
217 | log.Fatal(err)
218 | }
219 | users, posts, err := seedUsersAndPosts()
220 | if err != nil {
221 | log.Fatal(err)
222 | }
223 | // Get only the first user
224 | for _, user := range users {
225 | if user.ID == 2 {
226 | continue
227 | }
228 | PostUserEmail = user.Email
229 | PostUserPassword = "password" //Note the password in the database is already hashed, we want unhashed
230 | }
231 | //Login the user and get the authentication token
232 | token, err := server.SignIn(PostUserEmail, PostUserPassword)
233 | if err != nil {
234 | log.Fatalf("cannot login: %v\n", err)
235 | }
236 | tokenString := fmt.Sprintf("Bearer %v", token)
237 |
238 | // Get only the first post
239 | for _, post := range posts {
240 | if post.ID == 2 {
241 | continue
242 | }
243 | AuthPostID = post.ID
244 | AuthPostAuthorID = post.AuthorID
245 | }
246 | // fmt.Printf("this is the auth post: %v\n", AuthPostID)
247 |
248 | samples := []struct {
249 | id string
250 | updateJSON string
251 | statusCode int
252 | title string
253 | content string
254 | author_id uint32
255 | tokenGiven string
256 | errorMessage string
257 | }{
258 | {
259 | // Convert int64 to int first before converting to string
260 | id: strconv.Itoa(int(AuthPostID)),
261 | updateJSON: `{"title":"The updated post", "content": "This is the updated content", "author_id": 1}`,
262 | statusCode: 200,
263 | title: "The updated post",
264 | content: "This is the updated content",
265 | author_id: AuthPostAuthorID,
266 | tokenGiven: tokenString,
267 | errorMessage: "",
268 | },
269 | {
270 | // When no token is provided
271 | id: strconv.Itoa(int(AuthPostID)),
272 | updateJSON: `{"title":"This is still another title", "content": "This is the updated content", "author_id": 1}`,
273 | tokenGiven: "",
274 | statusCode: 401,
275 | errorMessage: "Unauthorized",
276 | },
277 | {
278 | // When incorrect token is provided
279 | id: strconv.Itoa(int(AuthPostID)),
280 | updateJSON: `{"title":"This is still another title", "content": "This is the updated content", "author_id": 1}`,
281 | tokenGiven: "this is an incorrect token",
282 | statusCode: 401,
283 | errorMessage: "Unauthorized",
284 | },
285 | {
286 | //Note: "Title 2" belongs to post 2, and title must be unique
287 | id: strconv.Itoa(int(AuthPostID)),
288 | updateJSON: `{"title":"Title 2", "content": "This is the updated content", "author_id": 1}`,
289 | statusCode: 500,
290 | tokenGiven: tokenString,
291 | errorMessage: "Title Already Taken",
292 | },
293 | {
294 | id: strconv.Itoa(int(AuthPostID)),
295 | updateJSON: `{"title":"", "content": "This is the updated content", "author_id": 1}`,
296 | statusCode: 422,
297 | tokenGiven: tokenString,
298 | errorMessage: "Required Title",
299 | },
300 | {
301 | id: strconv.Itoa(int(AuthPostID)),
302 | updateJSON: `{"title":"Awesome title", "content": "", "author_id": 1}`,
303 | statusCode: 422,
304 | tokenGiven: tokenString,
305 | errorMessage: "Required Content",
306 | },
307 | {
308 | id: strconv.Itoa(int(AuthPostID)),
309 | updateJSON: `{"title":"This is another title", "content": "This is the updated content"}`,
310 | statusCode: 401,
311 | tokenGiven: tokenString,
312 | errorMessage: "Unauthorized",
313 | },
314 | {
315 | id: "unknwon",
316 | statusCode: 400,
317 | },
318 | {
319 | id: strconv.Itoa(int(AuthPostID)),
320 | updateJSON: `{"title":"This is still another title", "content": "This is the updated content", "author_id": 2}`,
321 | tokenGiven: tokenString,
322 | statusCode: 401,
323 | errorMessage: "Unauthorized",
324 | },
325 | }
326 |
327 | for _, v := range samples {
328 |
329 | req, err := http.NewRequest("POST", "/posts", bytes.NewBufferString(v.updateJSON))
330 | if err != nil {
331 | t.Errorf("this is the error: %v\n", err)
332 | }
333 | req = mux.SetURLVars(req, map[string]string{"id": v.id})
334 | rr := httptest.NewRecorder()
335 | handler := http.HandlerFunc(server.UpdatePost)
336 |
337 | req.Header.Set("Authorization", v.tokenGiven)
338 |
339 | handler.ServeHTTP(rr, req)
340 |
341 | responseMap := make(map[string]interface{})
342 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
343 | if err != nil {
344 | t.Errorf("Cannot convert to json: %v", err)
345 | }
346 | assert.Equal(t, rr.Code, v.statusCode)
347 | if v.statusCode == 200 {
348 | assert.Equal(t, responseMap["title"], v.title)
349 | assert.Equal(t, responseMap["content"], v.content)
350 | assert.Equal(t, responseMap["author_id"], float64(v.author_id)) //just to match the type of the json we receive thats why we used float64
351 | }
352 | if v.statusCode == 401 || v.statusCode == 422 || v.statusCode == 500 && v.errorMessage != "" {
353 | assert.Equal(t, responseMap["error"], v.errorMessage)
354 | }
355 | }
356 | }
357 |
358 | func TestDeletePost(t *testing.T) {
359 |
360 | var PostUserEmail, PostUserPassword string
361 | var PostUserID uint32
362 | var AuthPostID uint64
363 |
364 | err := refreshUserAndPostTable()
365 | if err != nil {
366 | log.Fatal(err)
367 | }
368 | users, posts, err := seedUsersAndPosts()
369 | if err != nil {
370 | log.Fatal(err)
371 | }
372 | //Let's get only the Second user
373 | for _, user := range users {
374 | if user.ID == 1 {
375 | continue
376 | }
377 | PostUserEmail = user.Email
378 | PostUserPassword = "password" //Note the password in the database is already hashed, we want unhashed
379 | }
380 | //Login the user and get the authentication token
381 | token, err := server.SignIn(PostUserEmail, PostUserPassword)
382 | if err != nil {
383 | log.Fatalf("cannot login: %v\n", err)
384 | }
385 | tokenString := fmt.Sprintf("Bearer %v", token)
386 |
387 | // Get only the second post
388 | for _, post := range posts {
389 | if post.ID == 1 {
390 | continue
391 | }
392 | AuthPostID = post.ID
393 | PostUserID = post.AuthorID
394 | }
395 | postSample := []struct {
396 | id string
397 | author_id uint32
398 | tokenGiven string
399 | statusCode int
400 | errorMessage string
401 | }{
402 | {
403 | // Convert int64 to int first before converting to string
404 | id: strconv.Itoa(int(AuthPostID)),
405 | author_id: PostUserID,
406 | tokenGiven: tokenString,
407 | statusCode: 204,
408 | errorMessage: "",
409 | },
410 | {
411 | // When empty token is passed
412 | id: strconv.Itoa(int(AuthPostID)),
413 | author_id: PostUserID,
414 | tokenGiven: "",
415 | statusCode: 401,
416 | errorMessage: "Unauthorized",
417 | },
418 | {
419 | // When incorrect token is passed
420 | id: strconv.Itoa(int(AuthPostID)),
421 | author_id: PostUserID,
422 | tokenGiven: "This is an incorrect token",
423 | statusCode: 401,
424 | errorMessage: "Unauthorized",
425 | },
426 | {
427 | id: "unknwon",
428 | tokenGiven: tokenString,
429 | statusCode: 400,
430 | },
431 | {
432 | id: strconv.Itoa(int(1)),
433 | author_id: 1,
434 | statusCode: 401,
435 | errorMessage: "Unauthorized",
436 | },
437 | }
438 | for _, v := range postSample {
439 |
440 | req, _ := http.NewRequest("GET", "/posts", nil)
441 | req = mux.SetURLVars(req, map[string]string{"id": v.id})
442 |
443 | rr := httptest.NewRecorder()
444 | handler := http.HandlerFunc(server.DeletePost)
445 |
446 | req.Header.Set("Authorization", v.tokenGiven)
447 |
448 | handler.ServeHTTP(rr, req)
449 |
450 | assert.Equal(t, rr.Code, v.statusCode)
451 |
452 | if v.statusCode == 401 && v.errorMessage != "" {
453 |
454 | responseMap := make(map[string]interface{})
455 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
456 | if err != nil {
457 | t.Errorf("Cannot convert to json: %v", err)
458 | }
459 | assert.Equal(t, responseMap["error"], v.errorMessage)
460 | }
461 | }
462 | }
463 |
--------------------------------------------------------------------------------
/tests/controllertests/user_controller_test.go:
--------------------------------------------------------------------------------
1 | package controllertests
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "net/http/httptest"
10 | "strconv"
11 | "testing"
12 |
13 | "github.com/gorilla/mux"
14 | "github.com/victorsteven/fullstack/api/models"
15 | "gopkg.in/go-playground/assert.v1"
16 | )
17 |
18 | func TestCreateUser(t *testing.T) {
19 |
20 | err := refreshUserTable()
21 | if err != nil {
22 | log.Fatal(err)
23 | }
24 | samples := []struct {
25 | inputJSON string
26 | statusCode int
27 | nickname string
28 | email string
29 | errorMessage string
30 | }{
31 | {
32 | inputJSON: `{"nickname":"Pet", "email": "pet@gmail.com", "password": "password"}`,
33 | statusCode: 201,
34 | nickname: "Pet",
35 | email: "pet@gmail.com",
36 | errorMessage: "",
37 | },
38 | {
39 | inputJSON: `{"nickname":"Frank", "email": "pet@gmail.com", "password": "password"}`,
40 | statusCode: 500,
41 | errorMessage: "Email Already Taken",
42 | },
43 | {
44 | inputJSON: `{"nickname":"Pet", "email": "grand@gmail.com", "password": "password"}`,
45 | statusCode: 500,
46 | errorMessage: "Nickname Already Taken",
47 | },
48 | {
49 | inputJSON: `{"nickname":"Kan", "email": "kangmail.com", "password": "password"}`,
50 | statusCode: 422,
51 | errorMessage: "Invalid Email",
52 | },
53 | {
54 | inputJSON: `{"nickname": "", "email": "kan@gmail.com", "password": "password"}`,
55 | statusCode: 422,
56 | errorMessage: "Required Nickname",
57 | },
58 | {
59 | inputJSON: `{"nickname": "Kan", "email": "", "password": "password"}`,
60 | statusCode: 422,
61 | errorMessage: "Required Email",
62 | },
63 | {
64 | inputJSON: `{"nickname": "Kan", "email": "kan@gmail.com", "password": ""}`,
65 | statusCode: 422,
66 | errorMessage: "Required Password",
67 | },
68 | }
69 |
70 | for _, v := range samples {
71 |
72 | req, err := http.NewRequest("POST", "/users", bytes.NewBufferString(v.inputJSON))
73 | if err != nil {
74 | t.Errorf("this is the error: %v", err)
75 | }
76 | rr := httptest.NewRecorder()
77 | handler := http.HandlerFunc(server.CreateUser)
78 | handler.ServeHTTP(rr, req)
79 |
80 | responseMap := make(map[string]interface{})
81 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
82 | if err != nil {
83 | fmt.Printf("Cannot convert to json: %v", err)
84 | }
85 | assert.Equal(t, rr.Code, v.statusCode)
86 | if v.statusCode == 201 {
87 | assert.Equal(t, responseMap["nickname"], v.nickname)
88 | assert.Equal(t, responseMap["email"], v.email)
89 | }
90 | if v.statusCode == 422 || v.statusCode == 500 && v.errorMessage != "" {
91 | assert.Equal(t, responseMap["error"], v.errorMessage)
92 | }
93 | }
94 | }
95 |
96 | func TestGetUsers(t *testing.T) {
97 |
98 | err := refreshUserTable()
99 | if err != nil {
100 | log.Fatal(err)
101 | }
102 | _, err = seedUsers()
103 | if err != nil {
104 | log.Fatal(err)
105 | }
106 | req, err := http.NewRequest("GET", "/users", nil)
107 | if err != nil {
108 | t.Errorf("this is the error: %v\n", err)
109 | }
110 | rr := httptest.NewRecorder()
111 | handler := http.HandlerFunc(server.GetUsers)
112 | handler.ServeHTTP(rr, req)
113 |
114 | var users []models.User
115 | err = json.Unmarshal([]byte(rr.Body.String()), &users)
116 | if err != nil {
117 | log.Fatalf("Cannot convert to json: %v\n", err)
118 | }
119 | assert.Equal(t, rr.Code, http.StatusOK)
120 | assert.Equal(t, len(users), 2)
121 | }
122 |
123 | func TestGetUserByID(t *testing.T) {
124 |
125 | err := refreshUserTable()
126 | if err != nil {
127 | log.Fatal(err)
128 | }
129 | user, err := seedOneUser()
130 | if err != nil {
131 | log.Fatal(err)
132 | }
133 | userSample := []struct {
134 | id string
135 | statusCode int
136 | nickname string
137 | email string
138 | errorMessage string
139 | }{
140 | {
141 | id: strconv.Itoa(int(user.ID)),
142 | statusCode: 200,
143 | nickname: user.Nickname,
144 | email: user.Email,
145 | },
146 | {
147 | id: "unknwon",
148 | statusCode: 400,
149 | },
150 | }
151 | for _, v := range userSample {
152 |
153 | req, err := http.NewRequest("GET", "/users", nil)
154 | if err != nil {
155 | t.Errorf("This is the error: %v\n", err)
156 | }
157 | req = mux.SetURLVars(req, map[string]string{"id": v.id})
158 | rr := httptest.NewRecorder()
159 | handler := http.HandlerFunc(server.GetUser)
160 | handler.ServeHTTP(rr, req)
161 |
162 | responseMap := make(map[string]interface{})
163 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
164 | if err != nil {
165 | log.Fatalf("Cannot convert to json: %v", err)
166 | }
167 |
168 | assert.Equal(t, rr.Code, v.statusCode)
169 |
170 | if v.statusCode == 200 {
171 | assert.Equal(t, user.Nickname, responseMap["nickname"])
172 | assert.Equal(t, user.Email, responseMap["email"])
173 | }
174 | }
175 | }
176 |
177 | func TestUpdateUser(t *testing.T) {
178 |
179 | var AuthEmail, AuthPassword string
180 | var AuthID uint32
181 |
182 | err := refreshUserTable()
183 | if err != nil {
184 | log.Fatal(err)
185 | }
186 | users, err := seedUsers() //we need atleast two users to properly check the update
187 | if err != nil {
188 | log.Fatalf("Error seeding user: %v\n", err)
189 | }
190 | // Get only the first user
191 | for _, user := range users {
192 | if user.ID == 2 {
193 | continue
194 | }
195 | AuthID = user.ID
196 | AuthEmail = user.Email
197 | AuthPassword = "password" //Note the password in the database is already hashed, we want unhashed
198 | }
199 | //Login the user and get the authentication token
200 | token, err := server.SignIn(AuthEmail, AuthPassword)
201 | if err != nil {
202 | log.Fatalf("cannot login: %v\n", err)
203 | }
204 | tokenString := fmt.Sprintf("Bearer %v", token)
205 |
206 | samples := []struct {
207 | id string
208 | updateJSON string
209 | statusCode int
210 | updateNickname string
211 | updateEmail string
212 | tokenGiven string
213 | errorMessage string
214 | }{
215 | {
216 | // Convert int32 to int first before converting to string
217 | id: strconv.Itoa(int(AuthID)),
218 | updateJSON: `{"nickname":"Grand", "email": "grand@gmail.com", "password": "password"}`,
219 | statusCode: 200,
220 | updateNickname: "Grand",
221 | updateEmail: "grand@gmail.com",
222 | tokenGiven: tokenString,
223 | errorMessage: "",
224 | },
225 | {
226 | // When password field is empty
227 | id: strconv.Itoa(int(AuthID)),
228 | updateJSON: `{"nickname":"Woman", "email": "woman@gmail.com", "password": ""}`,
229 | statusCode: 422,
230 | tokenGiven: tokenString,
231 | errorMessage: "Required Password",
232 | },
233 | {
234 | // When no token was passed
235 | id: strconv.Itoa(int(AuthID)),
236 | updateJSON: `{"nickname":"Man", "email": "man@gmail.com", "password": "password"}`,
237 | statusCode: 401,
238 | tokenGiven: "",
239 | errorMessage: "Unauthorized",
240 | },
241 | {
242 | // When incorrect token was passed
243 | id: strconv.Itoa(int(AuthID)),
244 | updateJSON: `{"nickname":"Woman", "email": "woman@gmail.com", "password": "password"}`,
245 | statusCode: 401,
246 | tokenGiven: "This is incorrect token",
247 | errorMessage: "Unauthorized",
248 | },
249 | {
250 | // Remember "kenny@gmail.com" belongs to user 2
251 | id: strconv.Itoa(int(AuthID)),
252 | updateJSON: `{"nickname":"Frank", "email": "kenny@gmail.com", "password": "password"}`,
253 | statusCode: 500,
254 | tokenGiven: tokenString,
255 | errorMessage: "Email Already Taken",
256 | },
257 | {
258 | // Remember "Kenny Morris" belongs to user 2
259 | id: strconv.Itoa(int(AuthID)),
260 | updateJSON: `{"nickname":"Kenny Morris", "email": "grand@gmail.com", "password": "password"}`,
261 | statusCode: 500,
262 | tokenGiven: tokenString,
263 | errorMessage: "Nickname Already Taken",
264 | },
265 | {
266 | id: strconv.Itoa(int(AuthID)),
267 | updateJSON: `{"nickname":"Kan", "email": "kangmail.com", "password": "password"}`,
268 | statusCode: 422,
269 | tokenGiven: tokenString,
270 | errorMessage: "Invalid Email",
271 | },
272 | {
273 | id: strconv.Itoa(int(AuthID)),
274 | updateJSON: `{"nickname": "", "email": "kan@gmail.com", "password": "password"}`,
275 | statusCode: 422,
276 | tokenGiven: tokenString,
277 | errorMessage: "Required Nickname",
278 | },
279 | {
280 | id: strconv.Itoa(int(AuthID)),
281 | updateJSON: `{"nickname": "Kan", "email": "", "password": "password"}`,
282 | statusCode: 422,
283 | tokenGiven: tokenString,
284 | errorMessage: "Required Email",
285 | },
286 | {
287 | id: "unknwon",
288 | tokenGiven: tokenString,
289 | statusCode: 400,
290 | },
291 | {
292 | // When user 2 is using user 1 token
293 | id: strconv.Itoa(int(2)),
294 | updateJSON: `{"nickname": "Mike", "email": "mike@gmail.com", "password": "password"}`,
295 | tokenGiven: tokenString,
296 | statusCode: 401,
297 | errorMessage: "Unauthorized",
298 | },
299 | }
300 |
301 | for _, v := range samples {
302 |
303 | req, err := http.NewRequest("POST", "/users", bytes.NewBufferString(v.updateJSON))
304 | if err != nil {
305 | t.Errorf("This is the error: %v\n", err)
306 | }
307 | req = mux.SetURLVars(req, map[string]string{"id": v.id})
308 |
309 | rr := httptest.NewRecorder()
310 | handler := http.HandlerFunc(server.UpdateUser)
311 |
312 | req.Header.Set("Authorization", v.tokenGiven)
313 |
314 | handler.ServeHTTP(rr, req)
315 |
316 | responseMap := make(map[string]interface{})
317 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
318 | if err != nil {
319 | t.Errorf("Cannot convert to json: %v", err)
320 | }
321 | assert.Equal(t, rr.Code, v.statusCode)
322 | if v.statusCode == 200 {
323 | assert.Equal(t, responseMap["nickname"], v.updateNickname)
324 | assert.Equal(t, responseMap["email"], v.updateEmail)
325 | }
326 | if v.statusCode == 401 || v.statusCode == 422 || v.statusCode == 500 && v.errorMessage != "" {
327 | assert.Equal(t, responseMap["error"], v.errorMessage)
328 | }
329 | }
330 | }
331 |
332 | func TestDeleteUser(t *testing.T) {
333 |
334 | var AuthEmail, AuthPassword string
335 | var AuthID uint32
336 |
337 | err := refreshUserTable()
338 | if err != nil {
339 | log.Fatal(err)
340 | }
341 |
342 | users, err := seedUsers() //we need atleast two users to properly check the update
343 | if err != nil {
344 | log.Fatalf("Error seeding user: %v\n", err)
345 | }
346 | // Get only the first and log him in
347 | for _, user := range users {
348 | if user.ID == 2 {
349 | continue
350 | }
351 | AuthID = user.ID
352 | AuthEmail = user.Email
353 | AuthPassword = "password" ////Note the password in the database is already hashed, we want unhashed
354 | }
355 | //Login the user and get the authentication token
356 | token, err := server.SignIn(AuthEmail, AuthPassword)
357 | if err != nil {
358 | log.Fatalf("cannot login: %v\n", err)
359 | }
360 | tokenString := fmt.Sprintf("Bearer %v", token)
361 |
362 | userSample := []struct {
363 | id string
364 | tokenGiven string
365 | statusCode int
366 | errorMessage string
367 | }{
368 | {
369 | // Convert int32 to int first before converting to string
370 | id: strconv.Itoa(int(AuthID)),
371 | tokenGiven: tokenString,
372 | statusCode: 204,
373 | errorMessage: "",
374 | },
375 | {
376 | // When no token is given
377 | id: strconv.Itoa(int(AuthID)),
378 | tokenGiven: "",
379 | statusCode: 401,
380 | errorMessage: "Unauthorized",
381 | },
382 | {
383 | // When incorrect token is given
384 | id: strconv.Itoa(int(AuthID)),
385 | tokenGiven: "This is an incorrect token",
386 | statusCode: 401,
387 | errorMessage: "Unauthorized",
388 | },
389 | {
390 | id: "unknwon",
391 | tokenGiven: tokenString,
392 | statusCode: 400,
393 | },
394 | {
395 | // User 2 trying to use User 1 token
396 | id: strconv.Itoa(int(2)),
397 | tokenGiven: tokenString,
398 | statusCode: 401,
399 | errorMessage: "Unauthorized",
400 | },
401 | }
402 | for _, v := range userSample {
403 |
404 | req, err := http.NewRequest("GET", "/users", nil)
405 | if err != nil {
406 | t.Errorf("This is the error: %v\n", err)
407 | }
408 | req = mux.SetURLVars(req, map[string]string{"id": v.id})
409 | rr := httptest.NewRecorder()
410 | handler := http.HandlerFunc(server.DeleteUser)
411 |
412 | req.Header.Set("Authorization", v.tokenGiven)
413 |
414 | handler.ServeHTTP(rr, req)
415 | assert.Equal(t, rr.Code, v.statusCode)
416 |
417 | if v.statusCode == 401 && v.errorMessage != "" {
418 | responseMap := make(map[string]interface{})
419 | err = json.Unmarshal([]byte(rr.Body.String()), &responseMap)
420 | if err != nil {
421 | t.Errorf("Cannot convert to json: %v", err)
422 | }
423 | assert.Equal(t, responseMap["error"], v.errorMessage)
424 | }
425 | }
426 | }
427 |
--------------------------------------------------------------------------------
/tests/modeltests/model_test.go:
--------------------------------------------------------------------------------
1 | package modeltests
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "testing"
8 |
9 | "github.com/jinzhu/gorm"
10 | "github.com/joho/godotenv"
11 | "github.com/victorsteven/fullstack/api/controllers"
12 | "github.com/victorsteven/fullstack/api/models"
13 | )
14 |
15 | var server = controllers.Server{}
16 | var userInstance = models.User{}
17 | var postInstance = models.Post{}
18 |
19 | func TestMain(m *testing.M) {
20 | var err error
21 | err = godotenv.Load(os.ExpandEnv("../../.env"))
22 | if err != nil {
23 | log.Fatalf("Error getting env %v\n", err)
24 | }
25 | Database()
26 |
27 | log.Printf("Before calling m.Run() !!!")
28 | ret := m.Run()
29 | log.Printf("After calling m.Run() !!!")
30 | //os.Exit(m.Run())
31 | os.Exit(ret)
32 | }
33 |
34 | func Database() {
35 |
36 | var err error
37 |
38 | TestDbDriver := os.Getenv("TestDbDriver")
39 |
40 | if TestDbDriver == "mysql" {
41 | DBURL := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", os.Getenv("TestDbUser"), os.Getenv("TestDbPassword"), os.Getenv("TestDbHost"), os.Getenv("TestDbPort"), os.Getenv("TestDbName"))
42 | server.DB, err = gorm.Open(TestDbDriver, DBURL)
43 | if err != nil {
44 | fmt.Printf("Cannot connect to %s database\n", TestDbDriver)
45 | log.Fatal("This is the error:", err)
46 | } else {
47 | fmt.Printf("We are connected to the %s database\n", TestDbDriver)
48 | }
49 | }
50 | if TestDbDriver == "postgres" {
51 | DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", os.Getenv("TestDbHost"), os.Getenv("TestDbPort"), os.Getenv("TestDbUser"), os.Getenv("TestDbName"), os.Getenv("TestDbPassword"))
52 | server.DB, err = gorm.Open(TestDbDriver, DBURL)
53 | if err != nil {
54 | fmt.Printf("Cannot connect to %s database\n", TestDbDriver)
55 | log.Fatal("This is the error:", err)
56 | } else {
57 | fmt.Printf("We are connected to the %s database\n", TestDbDriver)
58 | }
59 | }
60 | if TestDbDriver == "sqlite3" {
61 | //DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
62 | testDbName := os.Getenv("TestDbName")
63 | server.DB, err = gorm.Open(TestDbDriver, testDbName)
64 | if err != nil {
65 | fmt.Printf("Cannot connect to %s database\n", TestDbDriver)
66 | log.Fatal("This is the error:", err)
67 | } else {
68 | fmt.Printf("We are connected to the %s database\n", TestDbDriver)
69 | }
70 | server.DB.Exec("PRAGMA foreign_keys = ON")
71 | }
72 |
73 | }
74 |
75 | func refreshUserTable() error {
76 | server.DB.Exec("SET foreign_key_checks=0")
77 | err := server.DB.Debug().DropTableIfExists(&models.User{}).Error
78 | if err != nil {
79 | return err
80 | }
81 | server.DB.Exec("SET foreign_key_checks=1")
82 | err = server.DB.Debug().AutoMigrate(&models.User{}).Error
83 | if err != nil {
84 | return err
85 | }
86 | log.Printf("Successfully refreshed table")
87 | log.Printf("refreshUserTable routine OK !!!")
88 | return nil
89 | }
90 |
91 | func seedOneUser() (models.User, error) {
92 |
93 | _ = refreshUserTable()
94 |
95 | user := models.User{
96 | Nickname: "Pet",
97 | Email: "pet@gmail.com",
98 | Password: "password",
99 | }
100 |
101 | err := server.DB.Debug().Model(&models.User{}).Create(&user).Error
102 | if err != nil {
103 | log.Fatalf("cannot seed users table: %v", err)
104 | }
105 |
106 | log.Printf("seedOneUser routine OK !!!")
107 | return user, nil
108 | }
109 |
110 | func seedUsers() error {
111 |
112 | users := []models.User{
113 | models.User{
114 | Nickname: "Steven victor",
115 | Email: "steven@gmail.com",
116 | Password: "password",
117 | },
118 | models.User{
119 | Nickname: "Kenny Morris",
120 | Email: "kenny@gmail.com",
121 | Password: "password",
122 | },
123 | }
124 |
125 | for i := range users {
126 | err := server.DB.Debug().Model(&models.User{}).Create(&users[i]).Error
127 | if err != nil {
128 | return err
129 | }
130 | }
131 |
132 | log.Printf("seedUsers routine OK !!!")
133 | return nil
134 | }
135 |
136 | func refreshUserAndPostTable() error {
137 |
138 | server.DB.Exec("SET foreign_key_checks=0")
139 | // NOTE: when deleting first delete Post as Post is depending on User table
140 | err := server.DB.Debug().DropTableIfExists(&models.Post{}, &models.User{}).Error
141 | if err != nil {
142 | return err
143 | }
144 | server.DB.Exec("SET foreign_key_checks=1")
145 | err = server.DB.Debug().AutoMigrate(&models.User{}, &models.Post{}).Error
146 | if err != nil {
147 | return err
148 | }
149 | log.Printf("Successfully refreshed tables")
150 | log.Printf("refreshUserAndPostTable routine OK !!!")
151 | return nil
152 | }
153 |
154 | func seedOneUserAndOnePost() (models.Post, error) {
155 |
156 | err := refreshUserAndPostTable()
157 | if err != nil {
158 | return models.Post{}, err
159 | }
160 | user := models.User{
161 | Nickname: "Sam Phil",
162 | Email: "sam@gmail.com",
163 | Password: "password",
164 | }
165 | err = server.DB.Debug().Model(&models.User{}).Create(&user).Error
166 | if err != nil {
167 | return models.Post{}, err
168 | }
169 | post := models.Post{
170 | Title: "This is the title sam",
171 | Content: "This is the content sam",
172 | AuthorID: user.ID,
173 | }
174 | err = server.DB.Debug().Model(&models.Post{}).Create(&post).Error
175 | if err != nil {
176 | return models.Post{}, err
177 | }
178 |
179 | log.Printf("seedOneUserAndOnePost routine OK !!!")
180 | return post, nil
181 | }
182 |
183 | func seedUsersAndPosts() ([]models.User, []models.Post, error) {
184 |
185 | var err error
186 |
187 | if err != nil {
188 | return []models.User{}, []models.Post{}, err
189 | }
190 | var users = []models.User{
191 | models.User{
192 | Nickname: "Steven victor",
193 | Email: "steven@gmail.com",
194 | Password: "password",
195 | },
196 | models.User{
197 | Nickname: "Magu Frank",
198 | Email: "magu@gmail.com",
199 | Password: "password",
200 | },
201 | }
202 | var posts = []models.Post{
203 | models.Post{
204 | Title: "Title 1",
205 | Content: "Hello world 1",
206 | },
207 | models.Post{
208 | Title: "Title 2",
209 | Content: "Hello world 2",
210 | },
211 | }
212 |
213 | for i := range users {
214 | err = server.DB.Debug().Model(&models.User{}).Create(&users[i]).Error
215 | if err != nil {
216 | log.Fatalf("cannot seed users table: %v", err)
217 | }
218 | posts[i].AuthorID = users[i].ID
219 |
220 | err = server.DB.Debug().Model(&models.Post{}).Create(&posts[i]).Error
221 | if err != nil {
222 | log.Fatalf("cannot seed posts table: %v", err)
223 | }
224 | }
225 | log.Printf("seedUsersAndPosts routine OK !!!")
226 | return users, posts, nil
227 | }
228 |
--------------------------------------------------------------------------------
/tests/modeltests/post_model_test.go:
--------------------------------------------------------------------------------
1 | package modeltests
2 |
3 | import (
4 | "log"
5 | "testing"
6 |
7 | _ "github.com/jinzhu/gorm/dialects/mysql"
8 | "github.com/victorsteven/fullstack/api/models"
9 | "gopkg.in/go-playground/assert.v1"
10 | )
11 |
12 | func TestFindAllPosts(t *testing.T) {
13 |
14 | err := refreshUserAndPostTable()
15 | if err != nil {
16 | log.Fatalf("Error refreshing user and post table %v\n", err)
17 | }
18 | _, _, err = seedUsersAndPosts()
19 | if err != nil {
20 | log.Fatalf("Error seeding user and post table %v\n", err)
21 | }
22 | posts, err := postInstance.FindAllPosts(server.DB)
23 | if err != nil {
24 | t.Errorf("this is the error getting the posts: %v\n", err)
25 | return
26 | }
27 | assert.Equal(t, len(*posts), 2)
28 | }
29 |
30 | func TestSavePost(t *testing.T) {
31 |
32 | err := refreshUserAndPostTable()
33 | if err != nil {
34 | log.Fatalf("Error user and post refreshing table %v\n", err)
35 | }
36 |
37 | user, err := seedOneUser()
38 | if err != nil {
39 | log.Fatalf("Cannot seed user %v\n", err)
40 | }
41 |
42 | newPost := models.Post{
43 | ID: 1,
44 | Title: "This is the title",
45 | Content: "This is the content",
46 | AuthorID: user.ID,
47 | }
48 | savedPost, err := newPost.SavePost(server.DB)
49 | if err != nil {
50 | t.Errorf("this is the error getting the post: %v\n", err)
51 | return
52 | }
53 | assert.Equal(t, newPost.ID, savedPost.ID)
54 | assert.Equal(t, newPost.Title, savedPost.Title)
55 | assert.Equal(t, newPost.Content, savedPost.Content)
56 | assert.Equal(t, newPost.AuthorID, savedPost.AuthorID)
57 |
58 | }
59 |
60 | func TestGetPostByID(t *testing.T) {
61 |
62 | err := refreshUserAndPostTable()
63 | if err != nil {
64 | log.Fatalf("Error refreshing user and post table: %v\n", err)
65 | }
66 | post, err := seedOneUserAndOnePost()
67 | if err != nil {
68 | log.Fatalf("Error Seeding User and post table")
69 | }
70 | foundPost, err := postInstance.FindPostByID(server.DB, post.ID)
71 | if err != nil {
72 | t.Errorf("this is the error getting one user: %v\n", err)
73 | return
74 | }
75 | assert.Equal(t, foundPost.ID, post.ID)
76 | assert.Equal(t, foundPost.Title, post.Title)
77 | assert.Equal(t, foundPost.Content, post.Content)
78 | }
79 |
80 | func TestUpdateAPost(t *testing.T) {
81 |
82 | err := refreshUserAndPostTable()
83 | if err != nil {
84 | log.Fatalf("Error refreshing user and post table: %v\n", err)
85 | }
86 | post, err := seedOneUserAndOnePost()
87 | if err != nil {
88 | log.Fatalf("Error Seeding table")
89 | }
90 | postUpdate := models.Post{
91 | ID: 1,
92 | Title: "modiUpdate",
93 | Content: "modiupdate@gmail.com",
94 | AuthorID: post.AuthorID,
95 | }
96 | updatedPost, err := postUpdate.UpdateAPost(server.DB)
97 | if err != nil {
98 | t.Errorf("this is the error updating the user: %v\n", err)
99 | return
100 | }
101 | assert.Equal(t, updatedPost.ID, postUpdate.ID)
102 | assert.Equal(t, updatedPost.Title, postUpdate.Title)
103 | assert.Equal(t, updatedPost.Content, postUpdate.Content)
104 | assert.Equal(t, updatedPost.AuthorID, postUpdate.AuthorID)
105 | }
106 |
107 | func TestDeleteAPost(t *testing.T) {
108 |
109 | err := refreshUserAndPostTable()
110 | if err != nil {
111 | log.Fatalf("Error refreshing user and post table: %v\n", err)
112 | }
113 | post, err := seedOneUserAndOnePost()
114 | if err != nil {
115 | log.Fatalf("Error Seeding tables")
116 | }
117 | isDeleted, err := postInstance.DeleteAPost(server.DB, post.ID, post.AuthorID)
118 | if err != nil {
119 | t.Errorf("this is the error deleting the user: %v\n", err)
120 | return
121 | }
122 | //one shows that the record has been deleted or:
123 | // assert.Equal(t, int(isDeleted), 1)
124 |
125 | //Can be done this way too
126 | assert.Equal(t, isDeleted, int64(1))
127 | }
128 |
--------------------------------------------------------------------------------
/tests/modeltests/user_model_test.go:
--------------------------------------------------------------------------------
1 | package modeltests
2 |
3 | import (
4 | "log"
5 | "testing"
6 |
7 | _ "github.com/jinzhu/gorm/dialects/mysql" //mysql driver
8 | _ "github.com/jinzhu/gorm/dialects/postgres" //postgres driver
9 | "github.com/victorsteven/fullstack/api/models"
10 | "gopkg.in/go-playground/assert.v1"
11 | )
12 |
13 | func TestFindAllUsers(t *testing.T) {
14 |
15 | err := refreshUserTable()
16 | if err != nil {
17 | log.Fatalf("Error refreshing user table %v\n", err)
18 | }
19 |
20 | err = seedUsers()
21 | if err != nil {
22 | log.Fatalf("Error seeding user table %v\n", err)
23 | }
24 |
25 | users, err := userInstance.FindAllUsers(server.DB)
26 | if err != nil {
27 | t.Errorf("this is the error getting the users: %v\n", err)
28 | return
29 | }
30 | assert.Equal(t, len(*users), 2)
31 | }
32 |
33 | func TestSaveUser(t *testing.T) {
34 |
35 | err := refreshUserTable()
36 | if err != nil {
37 | log.Fatalf("Error user refreshing table %v\n", err)
38 | }
39 | newUser := models.User{
40 | ID: 1,
41 | Email: "test@gmail.com",
42 | Nickname: "test",
43 | Password: "password",
44 | }
45 | savedUser, err := newUser.SaveUser(server.DB)
46 | if err != nil {
47 | t.Errorf("Error while saving a user: %v\n", err)
48 | return
49 | }
50 | assert.Equal(t, newUser.ID, savedUser.ID)
51 | assert.Equal(t, newUser.Email, savedUser.Email)
52 | assert.Equal(t, newUser.Nickname, savedUser.Nickname)
53 | }
54 |
55 | func TestGetUserByID(t *testing.T) {
56 |
57 | err := refreshUserTable()
58 | if err != nil {
59 | log.Fatalf("Error user refreshing table %v\n", err)
60 | }
61 |
62 | user, err := seedOneUser()
63 | if err != nil {
64 | log.Fatalf("cannot seed users table: %v", err)
65 | }
66 | foundUser, err := userInstance.FindUserByID(server.DB, user.ID)
67 | if err != nil {
68 | t.Errorf("this is the error getting one user: %v\n", err)
69 | return
70 | }
71 | assert.Equal(t, foundUser.ID, user.ID)
72 | assert.Equal(t, foundUser.Email, user.Email)
73 | assert.Equal(t, foundUser.Nickname, user.Nickname)
74 | }
75 |
76 | func TestUpdateAUser(t *testing.T) {
77 |
78 | err := refreshUserTable()
79 | if err != nil {
80 | log.Fatal(err)
81 | }
82 |
83 | user, err := seedOneUser()
84 | if err != nil {
85 | log.Fatalf("Cannot seed user: %v\n", err)
86 | }
87 |
88 | userUpdate := models.User{
89 | ID: 1,
90 | Nickname: "modiUpdate",
91 | Email: "modiupdate@gmail.com",
92 | Password: "password",
93 | }
94 | updatedUser, err := userUpdate.UpdateAUser(server.DB, user.ID)
95 | if err != nil {
96 | t.Errorf("this is the error updating the user: %v\n", err)
97 | return
98 | }
99 | assert.Equal(t, updatedUser.ID, userUpdate.ID)
100 | assert.Equal(t, updatedUser.Email, userUpdate.Email)
101 | assert.Equal(t, updatedUser.Nickname, userUpdate.Nickname)
102 | }
103 |
104 | func TestDeleteAUser(t *testing.T) {
105 |
106 | err := refreshUserTable()
107 | if err != nil {
108 | log.Fatal(err)
109 | }
110 |
111 | user, err := seedOneUser()
112 |
113 | if err != nil {
114 | log.Fatalf("Cannot seed user: %v\n", err)
115 | }
116 |
117 | isDeleted, err := userInstance.DeleteAUser(server.DB, user.ID)
118 | if err != nil {
119 | t.Errorf("this is the error deleting the user: %v\n", err)
120 | return
121 | }
122 | //one shows that the record has been deleted or:
123 | // assert.Equal(t, int(isDeleted), 1)
124 |
125 | //Can be done this way too
126 | assert.Equal(t, isDeleted, int64(1))
127 | }
128 |
--------------------------------------------------------------------------------