Wheel Doc

Getting Started

1  Install

1.1  Go

Check the Go documentation to download and install.

1.2  Dependences


$> go get github.com/iancoleman/strcase
$> go get github.com/jinzhu/inflection              
          

1.3  Wheel


$> go get github.com/unity26org/wheel
$> cd GOPATH/src/github.com/unity26org/wheel
$> go build -o wheel main.go 
$> sudo mv wheel /usr/bin
          

export PATH=$PATH:YOUR_DESIRED_PATH
          

2  Usage

Wheel has only two options: new and generate.

  • new: creates new APIs
  • generate: adds new resources into an API.

Check help for more details.


wheel --help
          

2.1  Creating a New Project

Let's create an API for a car catalog.

In this example Let's assume that we are working on the path github.com/account_name/car_catalog. Where github.com means the code repository system, account_name is your account name on github.com and car_catalog is the name of the application.


$> wheel new github.com/account_name/car_catalog
          

It will output something like this:

"Go" seems installed
Checking dependences...
         ...
Generating new app...
         created: GOPATH/src/github.com/account_name/car_catalog
         ...

Your RESTful API was successfully created!

Change to the root directory using the command line below:
cd GOPATH/src/github.com/account_name/car_catalog

Set up your database connection modifying the file config/database.yml

For more details call help:
go run main.go --help
          

2.2  File System

Directory/FileDefinition
app/Holds the models, views and handlers (controllers) of you application. The models and views of each resource are grouped inside the same directory. All handlers are together inside the directory "handlers".
commons/Contains internals functions for log, send mail, locale, cryptograph and type convertors. Also, inside the directory app, it has commons functions for the models, handlers and views.
config/Configure your API, database and send mail.
db/Manages the database schema and contains all information about its entities.
main.goThe starting point.
routes/Contains the routes, middleware and authorization functionalities.

2.3  Configure

Configure your application.

Database

Currently, Wheel has support only for Postgresql. Edit config/database.yml and set up your database connection.

Email

Edit config/email.yml and set up with your send mail account.

Application

Edit config/app.yml and set the following options:

ItemDefinition
app_nameYour app name
app_repositoryRepository name
frontend_base_urlURL to be used on your frontend
secret_keyKey to encrypt passwords on database
reset_password_expiration_secondsExpiration time just after password reset functionality is requested
token_expiration_secondsAccess token expiration time just after sign in
localesList of available locales
Locales

Available locales files that contains words and phrases for internacionalization. You can add your own locales files, but remember to add to config/app.yml configuration file first.

2.4  Running


$> go run main.go -mode=migrate
          

Run:


$> go run main.go
          

Now go to http://localhost:8081 and you'll see:


{
  "system_message": {
    "type": "notice",
    "content": "Yeah! Your API is working!"
  }
}
          

2.5  Creating new CRUD for cars


$> wheel g scaffold car description:string year:integer price:decimal available:bool
          

And it will return something like that:


"Go" seems installed
Checking dependences...
         ...
Generating new CRUD...
         created: app/post/car_model.go
         created: app/post/car_view.go
         created: db/entities/car_entity.go
         created: app/handlers/car_handler.go
         updated: routes/routes.go
         updated: db/schema/migrate.go
         updated: routes/authorize.go
          

Run the migration mode to update the database schema.


$> go run main.go -mode=migrate
          

Don't forget run your application again.


$> go run main.go
          

2.6  Routes

When generates a new CRUD, Wheel adds to file routes/routes.go the cars RESTful interfaces.


router.HandleFunc("/cars", handlers.CarList).Methods("GET")
router.HandleFunc("/cars/{id}", handlers.CarShow).Methods("GET")
router.HandleFunc("/cars", handlers.CarCreate).Methods("POST")
router.HandleFunc("/cars/{id}", handlers.CarUpdate).Methods("PUT")
router.HandleFunc("/cars/{id}", handlers.CarDestroy).Methods("DELETE")
          

  1. First route is used to retrieve a list of cars.
  2. Second route is used to retrieve one single car identified by the id.
  3. Third route is used to create new cars.
  4. Fourth route is used to update cars identified by the id.
  5. Fifth (and last) route is used to destroy cars identified by the id.

Each route is identified by a combination of a path (/cars or /cars/{id}) and a HTTP verb (GET, POST, PUT or DELETE). And this combination represents an interface to a handler function (handlers.CarCreate, handlers.CarList, handlers.CarShow, handlers.CarUpdate or handlers.CarDelete).

At this point we already have the resources to interact with the API. To make requests to the API we are going to use the curl program. If you are not confortable with command lines, we recommend you to use the software Postman. But feel free to use another similar software.

2.7  Creating cars

To create new car records, we need to make HTTP requests to the path /cars using the HTTP verb POST. As you can see in the previous item.

Let's build the request filling the form params with appropriated values and execute it.


curl -X POST -H 'Content-Type: application/json' \
    -d '{ \
        "description": "Chevrolet", \
        "available": "true", \ 
        "price": "25000.00", \ 
        "year": "2019" \
      }' \
    http://localhost:8081/cars
          

The result will be something like this:


{
    "system_message": {
        "type": "notice",
        "content": "car was successfully created"
    },
    "car": {
        "id": 1,
        "description": "Chevrolet",
        "year": 2019,
        "price": 25000.00,
        "available": true,
        "created_at": "2019-08-26T23:30:26.820820046Z",
        "updated_at": "2019-08-26T23:30:26.820820046Z"
    }
}
          

2.8  Listing cars

To list the car records, we need to make HTTP requests to the path /cars using the HTTP verb GET.


curl -X GET http://localhost:8081/cars
          

The result will contain the pagination information and an array of cars.


{
    "pagination": {
        "current_page": 1,
        "total_pages": 1,
        "total_entries": 1
    },
    "cars": [
        {
            "id": 1,
            "description": "Chevrolet",
            "year": 2019,
            "price": 25000.00,
            "available": true,
            "created_at": "2019-08-26T23:30:26.820820046Z",
            "updated_at": "2019-08-26T23:30:26.820820046Z"
        },
    ]
}
          

2.9  Showing a single car

To show one single car record, we need to make HTTP requests to the path /cars/{id} using the HTTP verb GET. Again, {id} is the parameter that identifies uniquely a record on the database.


curl -X GET http://localhost:8081/cars/1
          

The result will be something like this:


{
    "id": 1,
    "description": "Chevrolet",
    "year": 2019,
    "price": 25000.00,
    "available": false,
    "created_at": "2019-08-26T23:30:26.820820046Z",
    "updated_at": "2019-08-26T23:30:26.820820046Z"
}
          

2.10  Updating a car

To update a car record, we need to make HTTP requests to the path /cars/{id} using the HTTP verb PUT. Again and again, {id} is the parameter that identifies uniquely a record on the database. At this example we will update only the field price and year. But you can try to update the fields description and available too. The fields id, created_at and updated_at should not be updated by this application.


curl -X PUT -H 'Content-Type: application/json' \
    -d '{ \
        "description": "Chevrolet", \
        "available":"true", \ 
        "price": "18500.00", \
        "year":"2018" \
      }' \
    http://localhost:8081/cars
          

The result will be something like this:


{
    "system_message": {
      "type": "notice",
      "content": "car was successfully updated"
    },
    "car": {
        "id": 1,
        "description": "Chevrolet",
        "year": 2018,
        "price": 18500.00,
        "available": true,
        "created_at": "2019-08-26T23:30:26.820820046Z",
        "updated_at": "2019-08-27T00:08:26.820820046Z"
    }
}
          

2.11  Destroying a car

To desroy a car record, we need to make HTTP requests to the path /cars/{id} using the HTTP verb DELETE. One more time, {id} is the parameter that identifies uniquely a record on the database.


curl -X DELETE http://localhost:8081/cars/1
          

The result will be identical to:


{
    "system_message": {
        "type": "notice",
        "content": "car was successfully destroyed"
    }
}
          

3  Validation

Now let's see how to validate the parameters sent by the users. Open the file app/car/car_model.go and find the function IsValid. Inside it you can write the validation code.


func IsValid(car *entities.Car) (bool, []error) {
	var errs []error
  
	// write your validation code here
	// if CONDITION {
	//    errs = append(errs, errors.New("an error message"))
	// }

	return (len(errs) == 0), errs
}
          

For each not valid input you should append to the variable errs a error message. See the example below:


func IsValid(car *entities.Car) (bool, []error) {
	var errs []error
  
	if len(car.Description) < 3 {
		errs = append(errs, errors.New("description is too short"))
	} else if len(car.Description) > 255 {
		errs = append(errs, errors.New("description is too long"))
	}

	if car.Price >= 0.01 {
		errs = append(errs, errors.New("price can't be less than $ 0.01"))
	}

	return (len(errs) == 0), errs
}
          

The example above means that the field description should have at least three character and should not have more than 255. Also, the field price should be at least one cent.

To validate the field year we are going to need import the package time to make sure users can't register car years in the future. Also, for this example, is not allowed to register car's years before 2010. As you can see in the following code:


import "time"

  ...

func IsValid(car *entities.Car) (bool, []error) {
  
  ...
  
  currentTime := time.Now()
  if car.Year < 2010 {
    errs = append(errs, errors.New("year can't be less than 2010"))
  } else if car.Year > int64(currentTime.Year()) {
    errs = append(errs, errors.New("year can't be greater than current year"))
  }
  
  ...
  
}
          

Now let's try to create a new car with invalid parameters:


curl -X POST -H 'Content-Type: application/json' \
    -d '{ \
        "description": "", \
        "available":"true", \ 
        "price": "0", \
        "year":"3000" \
      }' \
    http://localhost:8081/cars
          

The result will be identical to:


{
    "system_message": {
        "type": "alert",
        "content": "car was not created"
    },
    "errors": [
        "description is too short",
        "price can't be less than $ 0.01",
        "year can't be greater than current year"
    ]
}
          

Now it's up to you write more validations.

4  Authentication

Authentication is one of many default features generated by Wheel. And it creates a admin user when you run the migration. Check file db/schema/migrate.go. If you didn't change the default admin credentials, here they are:

emailuser@example.com
passwordSecret123!

4.1  Signing in

The sign in request is:


curl -X POST -H 'Content-Type: application/json' \
    -d '{ \
        "email": "user@example.com", \
        "password": "Secret123!" \
      }' \
    http://localhost:8081/sessions/sign_in
          

The result will be something like this:


{
    "system_message": {
        "type": "notice",
        "content": "signed in successfully"
    },
    "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires": 7200
}
          

4.2  Signing out

To sign out, the user must to be currently signed in and have a valid token.


curl -X DELETE -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \ 
    http://localhost:8081/sessions/sign_out
          

The result will be something like this:


{
    "system_message": {
        "type": "notice",
        "content": "signed out successfully"
    }
}
          

5  Authorization

In the real-world, developers should grant access to application, or its parts, for authorized users only. That's why Wheel generates the file routes/authorization.go. This file has permissions to grant users access to resources.

When a new CRUD is generated, Wheel generates automatically the permissions for these new resources, but it has no restrictions, yet.

5.1  Setting permissions

To grant permission, it must match a combination of these three items:

  1. Regular expression: must match to the current URL.
  2. HTTP method: must be identical to one element in array.
  3. Role: must be identical to one element in array. Available roles are public, signed_in and admin. Public are the internet public audience. Signed_in are regular signed in users. Admin are administrators of the application.
Let's check the permissions for cars.


  Permissions = append(Permissions,
    Permission{
      UrlRegexp: regexp.MustCompile(`\A\/cars(\/){0,1}.*\z`),
      Methods:   []string{"GET", "POST", "DELETE", "PUT"},
      UserRoles: []string{"public", "signed_in", "admin"},
  })
          

Our goal now is to garantee the only admin users can create, update and delete cars. So let's separate two parts.

  1. Read-only permission to all roles.
  2. Write permission to admin role only.
And the result is:


  // part 1
  Permissions = append(Permissions,
    Permission{
      UrlRegexp: regexp.MustCompile(`\A\/cars(\/){0,1}.*\z`),
      Methods:   []string{"GET"},
      UserRoles: []string{"public", "signed_in", "admin"},
  })

  // part 2
  Permissions = append(Permissions,
    Permission{
      UrlRegexp: regexp.MustCompile(`\A\/cars(\/){0,1}.*\z`),
      Methods:   []string{"POST", "DELETE", "PUT"},
      UserRoles: []string{"admin"},
  })
          

Let's create a new car, supposing the user is signed in as admin and has a valid token.


curl -X POST -H 'Content-Type: application/json' \ 
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \ 
    -d '{ \
        "description":"Ford", \
        "available":"true", \
        "price":"15300.00", \
        "year":"2016" \
      }' \ 
    http://localhost:8081/cars
          

{
    "system_message": {
        "type": "notice",
        "content": "car was successfully created"
    },
    "car": {
        "id": 1,
        "description": "Ford",
        "year": 2016,
        "price": 15300.00,
        "available": true,
        "created_at": "2019-08-31T19:16:08.685901034Z",
        "updated_at": "2019-08-31T19:16:08.685901034Z"
    }
}
          

5.2  Error 401 - Unauthorized

Following the previous item, if you do not include a valid token in the request header it will return the 401 HTTP error:


curl -X POST -H 'Content-Type: application/json' \ 
    -d '{ \
        "description":"Ford", \
        "available":"true", \
        "price":"15300.00", \
        "year":"2016" \
      }' \ 
    http://localhost:8081/cars
          

{
    "system_message": {
        "type": "alert",
        "content": "401 Unauthorized"
    }
}
          

5.3  Error 403 - Forbidden

If a signed_in user (not admin) tries to reach the same resource above it will return the 403 HTTP error:


curl -X POST -H 'Content-Type: application/json' \ 
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
    -d '{ \
        "description": "Ford", \
        "available": "true", \
        "price": "15300.00", \
        "year": "2016" \
      }' \
    http://localhost:8081/cars
          

{
    "system_message": {
        "type": "alert",
        "content": "403 Forbidden"
    }
}
          

6  New CRUD for Wishlist

Now let's emphasize the relationship between database entities. In this example, let's keep in mind these requirements:

  • One wishlist item belongs to user (and one user has many wishlist items).
  • One wishlist item belongs to car (and one car has many wishlist items).
And run the command line:


$> wheel generate scaffold wishlist user:reference car:reference
...
Generating new CRUD...
         created: app/wishlist/wishlist_model.go
         created: app/wishlist/wishlist_view.go
         created: db/entities/wishlist_entity.go
         created: app/handlers/wishlist_handler.go
         updated: routes/routes.go
         updated: db/schema/migrate.go
         updated: routes/authorize.go
           

The reference type means that the entity wishlists has references to entity users and to entity cars as well.


$> go run main.go -mode=migration
          

6.1  Adding items to wishlist

Users can add cars only to their own wishlist. To identify the current user we need the internal function SessionCheck and pass the token as the parameter. The token should be in the header of the request.


wishlistNew.UserID, _ = SessionCheck(r.Header.Get("token"))
          

Now let's add this line (above) to right position in the file app/handlers/wishlist_handler.go inside the function WishlistCreate:


func WishlistCreate(w http.ResponseWriter, r *http.Request) {
  var wishlistNew = entities.Wishlist{}

  log.Info.Println("Handler: WishlistCreate")
  w.Header().Set("Content-Type", "application/json")


  var whishListParams WhishListPermittedParams
  _ = json.NewDecoder(r.Body).Decode(&whishListParams)
  handler.SetPermittedParamsToEntity(&whishListParams, &wishlistNew)

  // Add this line (below) at this position
  wishlistNew.UserID, _ = SessionCheck(r.Header.Get("token"))

  valid, errs := wishlist.Create(&wishlistNew)

  if valid {
    json.NewEncoder(w).Encode(wishlist.SuccessfullySavedJson{SystemMessage: view.SetSystemMessage("notice", "wishlist was successfully created"), Wishlist: wishlist.SetJson(newWishlist)})
  } else {
    json.NewEncoder(w).Encode(view.SetErrorMessage("alert", "wishlist was not created", errs))
  }
}
          

Make sure you have a valid token, a valid car_id and execute this request:


curl -X POST -H 'Content-Type: application/json' \ 
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \ 
    -d '{ \
        "car_id": "8" \
      }' \
    http://localhost:8081/wishlist
          

It will return something like this:


{
    "system_message": {
        "type": "notice",
        "content": "wishlist was successfully created"
    }, 
    "wishlist": {
        "id":1,    
        "user_id":1,
        "car_id":8,
        "created_at":"2019-09-01T16:31:30.701116524Z",
        "updated_at":"2019-09-01T16:31:30.701116524Z"
    }
}
          

One more:


curl -X POST -H 'Content-Type: application/json' \ 
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \ 
    -d '{ \
        "car_id":"5" \
      }' \
    http://localhost:8081/wishlist
          

It will return something like this:


{
    "system_message": {
        "type": "notice",
        "content": "wishlist was successfully created"
    }, 
    "wishlist": {
        "id":2,
        "user_id":1,
        "car_id":5,
        "created_at":"2019-09-01T16:34:38.621803316Z",
        "updated_at":"2019-09-01T16:34:38.621803316Z"
    }
}
          

Great! We have just created a wishlist with two items.

6.2  Listing wishlist items

Users can just list their own wishlist items. To filter the wishlist items for the current user we need to add these lines, below, to file app/handlers/wishlist_handler.go.


import strconv

...

currentUserID, _ := SessionCheck(r.Header.Get("token"))
criteria["user_id_eq"] = strconv.FormatUint(uint64(currentUserID), 10)
          

Package strconv shoud be included to import statement. The others two lines should be inserted in the function WishlistList, as you can see the code below.


import strconv

...

func WishlistList(w http.ResponseWriter, r *http.Request) {
  var i, page, entries, pages int
  var wishlistList []entities.Wishlist

  wishlistJsons := []wishlist.Json{}

  log.Info.Println("Handler: WishlistList")
  w.Header().Set("Content-Type", "application/json")

  order := wishlistSanitizeOrder(r.FormValue("order"))
  criteria := handler.QueryParamsToMapCriteria("search", r.URL.Query())
  // Add these two lines below
  userID, _ := SessionCheck(r.Header.Get("token"))
  criteria["user_id_eq"] = strconv.FormatUint(uint64(userID), 10)

  wishlistList, page, pages, entries = wishlist.Paginate(criteria, order, r.FormValue("page"), r.FormValue("per_page"))

  for i = 0; i < len(wishlistList); i++ {
    wishlistJsons = append(wishlistJsons, wishlist.SetJson(wishlistList[i]))
  }

  pagination := view.MainPagination{CurrentPage: page, TotalPages: pages, TotalEntries: entries}
  json.NewEncoder(w).Encode(wishlist.PaginationJson{Pagination: pagination, Wishlists: wishlistJsons})
}
          

The request is:


curl -X GET -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
    http://localhost:8081/wishlist
          

It will return something like this:


{
    "system_message": {
        "current_page":1,
        "total_pages":1,
        "total_entries":2
    }, 
    "wishlists": [
        {
            "id":1,
            "user_id":1,
            "car_id":8,
            "created_at":"2019-09-01T16:31:30.701117Z",
            "updated_at":"2019-09-01T16:31:30.701117Z"
        },
        {
            "id":2,
            "user_id":1,
            "car_id":5,
            "created_at":"2019-09-01T16:34:38.621803316Z",
            "updated_at":"2019-09-01T16:34:38.621803316Z"
        }
    ]
}
          

As you can see, the information card_id is not conclusive. Don't worry, the framework generated by Wheel is fexibe, let's customize it changing the file app/wishlist/wishlist_view.go.

First: import the car resources adding this line to the import statement.


import (
  "github.com/adilsonchacon/car_store/app/car"

  ...

)
          

Second: at Json struct, replace the CarID field by this line:


type Json struct {
  ID        uint      `json:"id"`
  UserID    uint      `json:"user_id"`
  // CarID       uint  `json:"car_id"` // remove this line
  Car       car.Json  `json:"car"`     // add this line
  CreatedAt time.Time `json:"created_at"`
  UpdatedAt time.Time `json:"updated_at"`
}
          

Third: at the function SetJson, do the following changes:


func SetJson(wishlist entities.Wishlist) Json {
  wishlistCar, _ := car.Find(wishlist.CarID) // add this line

  return Json{
    ID:        wishlist.ID,
    UserID:    wishlist.UserID,
    // CarID:       wishlist.CardID,      // remove this line
    Car:       car.SetJson(wishlistCar),  // add this line
    CreatedAt: wishlist.CreatedAt,
    UpdatedAt: wishlist.UpdatedAt,
  }
}
          

Restart the application and repeat the previous request:


curl -X GET -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
    http://localhost:8081/wishlist
          

It will return something like this:


{
    "system_message": {
        "current_page":1,
        "total_pages":1,
        "total_entries":2
    }, 
    "wishlists": [
        {
            "id":1,
            "user_id":1, 
            "car":{
                "id":8,
                "description":"VolksWagen",
                "year":2019,
                "price":19990,
                "available":true,
                "created_at":"2019-09-01T16:25:26.189186Z",
                "updated_at":"2019-09-01T16:25:26.189186Z"
            },
            "created_at":"2019-09-01T16:31:30.701117Z",
            "updated_at":"2019-09-01T16:31:30.701117Z"
        },
        {
            "id":2,
            "user_id":1,
            "car":{
                "id":5,
                "description":"Ferrari",
                "year":2018,
                "price":200000,
                "available":true,
                "created_at":"2019-09-01T16:12:45.004372Z",
                "updated_at":"2019-09-01T16:12:45.004372Z"
            },
            "created_at":"2019-09-01T16:34:38.621803316Z",
            "updated_at":"2019-09-01T16:34:38.621803316Z"
        }
    ]
}
          

6.3  Showing a single wishlist item

Add this function to the file app/wishlist/wishlist_model.go to make sure current users just can find their own wishlist items:


func FindByIdAndUserId(id interface{}, userID interface{}) (entities.Wishlist, error) {
  var wishlist entities.Wishlist
  var err error

  model.Db.Where("id = ? AND user_id = ?", id, userID).First(&wishlist)
  if model.Db.NewRecord(wishlist) {
    err = errors.New(NotFound)
  }

  return wishlist, err
}
          

Edit file app/handlers/wishlist_handler.go and make the following changes at WishlistShow function:


func WishlistShow(w http.ResponseWriter, r *http.Request) {
  log.Info.Println("Handler: WishlistShow")
  w.Header().Set("Content-Type", "application/json")

  params := mux.Vars(r)
  // wishlistCurrent, err := wishlist.Find(params["id"]) // remove this line
  // Add these two following lines
  userID, _ := SessionCheck(r.Header.Get("token"))
  wishlistCurrent, err := wishlist.FindByIdAndUserId(params["id"], userID)

  if err == nil {
    json.NewEncoder(w).Encode(wishlist.SetJson(wishlistCurrent))
  } else {
    json.NewEncoder(w).Encode(view.SetSystemMessage("alert", "wishlist was not found"))
  }
}
          

Run the request (don't forget to set the wishlist's id at the end of the URL):


curl -X GET -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
    http://localhost:8081/wishlist/2
          

It will return something like this:


{
    "id":1,
    "user_id":1, 
    "car":{
        "id":8,
        "description":"VolksWagen",
        "year":2019,
        "price":19990,
        "available":true,
        "created_at":"2019-09-01T16:25:26.189186Z",
        "updated_at":"2019-09-01T16:25:26.189186Z"
    },
    "created_at":"2019-09-01T16:31:30.701117Z",
    "updated_at":"2019-09-01T16:31:30.701117Z"
}
          

6.4  Updating a wishlist item

Edit file app/handlers/wishlist_handler.go and make the following changes at WishlistUpdate function:


func WishlistUpdate(w http.ResponseWriter, r *http.Request) {
  log.Info.Println("Handler: WishlistUpdate")
  w.Header().Set("Content-Type", "application/json")

  params := mux.Vars(r)
  userID, _ := SessionCheck(r.Header.Get("token")) // add this line

  // and replace this line
  // wishlistCurrent, err := wishlist.Find(params["id"])
  // for this single one
  wishlistCurrent, err := wishlist.FindByIdAndUserId(params["id"], userID)
  
  if err != nil {
    json.NewEncoder(w).Encode(view.SetErrorMessage("alert", "wishlist was not updated", []error{err}))
    return
  }

  var wishlistParams WishlistPermittedParams
  _ = json.NewDecoder(r.Body).Decode(&wishlistParams)
  handler.SetPermittedParamsToEntity(&wishlistParams, &wishlistCurrent)
  
  // Add this line (below) for avoiding change the wish list user
  wishlistCurrent.UserId = UserID

  if valid, errs := wishlist.Update(&wishlistCurrent); valid {
    json.NewEncoder(w).Encode(wishlist.SuccessfullySavedJson{SystemMessage: view.SetSystemMessage("notice", "wishlist was successfully updated"), Wishlist: wishlist.SetJson(wishlistCurrent)})
  } else {
    json.NewEncoder(w).Encode(view.SetErrorMessage("alert", "wishlist was not updated", errs))
  }
}
          

Update request:


curl -X PUT -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
    -d '{ \
        "car_id": "7" \
      }' \
    http://localhost:8081/wishlist/2
          

It will return something like this:


{
    "system_message": {
        "type": "notice",
        "content": "wishlist was successfully created"
    }, 
    "wishlist": {
        "id":2,
        "user_id":1, 
        "car":{
            "id":7,
            "description":"Audi",
            "year":2017,
            "price":78000,
            "available":true,
            "created_at":"2019-09-01T16:25:26.189186Z",
            "updated_at":"2019-09-01T16:25:26.189186Z"
        },
        "created_at":"2019-09-01T20:31:30.701117Z",
        "updated_at":"2019-09-01T29:31:30.701117Z"
    }
}
          

6.5  Destroying a wishlist item

Edit file app/handlers/wishlist_handler.go and make the following changes at WishlistDestroy function:


func WishlistDestroy(w http.ResponseWriter, r *http.Request) {
  log.Info.Println("Handler: WishlistDestroy")
  w.Header().Set("Content-Type", "application/json")

  params := mux.Vars(r)
  // replace this single line below
  // wishlistCurrent, err := wishlist.Find(params["id"])
  // for these two following lines
  userID, _ := SessionCheck(r.Header.Get("token"))
  wishlistCurrent, err := wishlist.FindByIdAndUserId(params["id"], userID)

  if err == nil && wishlist.Destroy(&wishlistCurrent) {
    json.NewEncoder(w).Encode(view.SetDefaultMessage("notice", "wishlist was successfully destroyed"))
  } else {
    json.NewEncoder(w).Encode(view.SetDefaultMessage("alert", "wishlist was not found"))
  }
}
          

Destroy request:


curl -X DELETE -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' \
    http://localhost:8081/wishlist/2
          

It will return something like this:


{
    "system_message": {
        "type": "notice",
        "content": "wishlist was successfully destroyed"
    }
}