// app.go
package main
import (
"database/sql"
// tom: for Initialize
"fmt"
"log"
// tom: for route handlers
"encoding/json"
"github.com/keploy/go-sdk/integrations/ksql"
"net/http"
"strconv"
// tom: go get required
"github.com/gorilla/mux"
"github.com/lib/pq"
)
type App struct {
Router *mux.Router
DB *sql.DB
}
// tom: initial function is empty, it's filled afterwards
// func (a *App) Initialize(user, password, dbname string) { }
// tom: added "sslmode=disable" to connection string
func (a *App) Initialize(user, password, dbname string) error {
connectionString := fmt.Sprintf("host=%s port=%s user=%s "+
"password=%s dbname=%s sslmode=disable",
"localhost", "5438", user, password, dbname)
// connectionString := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", user, password, dbname)
driver := ksql.Driver{Driver: pq.Driver{}}
sql.Register("keploy", &driver)
var err error
a.DB, err = sql.Open("keploy", connectionString)
if err != nil {
log.Fatal(err)
}
a.Router = mux.NewRouter()
// tom: this line is added after initializeRoutes is created later on
a.initializeRoutes()
return err
}
// tom: initial version
// func (a *App) Run(addr string) { }
// improved version
func (a *App) Run(addr string) {
log.Fatal(http.ListenAndServe(":8010", a.Router))
log.Printf("😃 Connected to 8010 port !!")
}
// tom: these are added later
func (a *App) getProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid product ID")
return
}
p := product{ID: id}
if err := p.getProduct(r.Context(), a.DB); err != nil {
switch err {
case sql.ErrNoRows:
respondWithError(w, http.StatusNotFound, "Product not found")
default:
respondWithError(w, http.StatusInternalServerError, err.Error())
}
return
}
respondWithJSON(w, http.StatusOK, p)
}
func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]string{"error": message})
}
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
response, _ := json.Marshal(payload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
func (a *App) getProducts(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
start, _ := strconv.Atoi(r.FormValue("start"))
if count > 10 || count < 1 {
count = 10
}
if start < 0 {
start = 0
}
products, err := getProducts(r.Context(), a.DB, start, count)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, products)
}
func (a *App) createProduct(w http.ResponseWriter, r *http.Request) {
var p product
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&p); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
if err := p.createProduct(r.Context(), a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusCreated, p)
}
func (a *App) updateProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid product ID")
return
}
var p product
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&p); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid resquest payload")
return
}
defer r.Body.Close()
p.ID = id
if err := p.updateProduct(r.Context(), a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, p)
}
func (a *App) deleteProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid Product ID")
return
}
p := product{ID: id}
if err := p.deleteProduct(r.Context(), a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"})
}
func (a *App) initializeRoutes() {
a.Router.HandleFunc("/products", a.getProducts).Methods("GET")
a.Router.HandleFunc("/product", a.createProduct).Methods("POST")
a.Router.HandleFunc("/product/{id:[0-9]+}", a.getProduct).Methods("GET")
a.Router.HandleFunc("/product/{id:[0-9]+}", a.updateProduct).Methods("PUT")
a.Router.HandleFunc("/product/{id:[0-9]+}", a.deleteProduct).Methods("DELETE")
}
package main
import (
"github.com/keploy/go-sdk/integrations/kmux"
"github.com/keploy/go-sdk/keploy"
"log"
)
func main() {
a := &App{}
err := a.Initialize(
"postgres",
"password",
"postgres")
if err != nil {
log.Fatal("Failed to initialize app", err)
}
port := "8010"
k := keploy.New(keploy.Config{
App: keploy.AppConfig{
Name: "my-app",
Port: port,
},
Server: keploy.ServerConfig{
URL: "http://localhost:6789/api",
},
})
a.Router.Use(kmux.MuxMiddleware(k))
log.Printf("😃 Connected to 8010 port !!")
a.Run(":8010")
}
// model.go
package main
import (
"context"
"database/sql"
// tom: errors is removed once functions are implemented
// "errors"
)
// tom: add backticks to json
type product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
// tom: these are initial empty definitions
// func (p *product) getProduct(db *sql.DB) error {
// return errors.New("Not implemented")
// }
// func (p *product) updateProduct(db *sql.DB) error {
// return errors.New("Not implemented")
// }
// func (p *product) deleteProduct(db *sql.DB) error {
// return errors.New("Not implemented")
// }
// func (p *product) createProduct(db *sql.DB) error {
// return errors.New("Not implemented")
// }
// func getProducts(db *sql.DB, start, count int) ([]product, error) {
// return nil, errors.New("Not implemented")
// }
// tom: these are added after tdd tests
func (p *product) getProduct(ctx context.Context, db *sql.DB) error {
return db.QueryRowContext(ctx, "SELECT name, price FROM products WHERE id=$1",
p.ID).Scan(&p.Name, &p.Price)
}
func (p *product) updateProduct(ctx context.Context, db *sql.DB) error {
_, err :=
db.ExecContext(ctx, "UPDATE products SET name=$1, price=$2 WHERE id=$3",
p.Name, p.Price, p.ID)
return err
}
func (p *product) deleteProduct(ctx context.Context, db *sql.DB) error {
_, err := db.ExecContext(ctx, "DELETE FROM products WHERE id=$1", p.ID)
return err
}
func (p *product) createProduct(ctx context.Context, db *sql.DB) error {
err := db.QueryRowContext(ctx,
"INSERT INTO products(name, price) VALUES($1, $2) RETURNING id",
p.Name, p.Price).Scan(&p.ID)
if err != nil {
return err
}
return nil
}
func getProducts(ctx context.Context, db *sql.DB, start, count int) ([]product, error) {
rows, err := db.QueryContext(ctx,
"SELECT id, name, price FROM products LIMIT $1 OFFSET $2",
count, start)
if err != nil {
return nil, err
}
defer rows.Close()
products := []product{}
for rows.Next() {
var p product
if err := rows.Scan(&p.ID, &p.Name, &p.Price); err != nil {
return nil, err
}
products = append(products, p)
}
return products, nil
}