From 2bbef5ad4ee0966b482eac912886adcbdff3ea90 Mon Sep 17 00:00:00 2001 From: Anis Benziane Date: Wed, 19 Oct 2022 21:42:55 +0200 Subject: [PATCH] Added GetProduct, GetProducts, UpdateProduct, DeleteProduct --- .gitignore | 12 ++- Makefile | 2 +- app.env | 15 --- config/default.go | 54 +++++----- controllers/product.go | 145 +++++++++++++++++++++++++++ controllers/user.go | 1 + db_client/.db_client.go.swp | Bin 0 -> 12288 bytes db_client/db_client.go | 32 ++++++ docker-compose.yml | 26 ++--- go.mod | 6 +- go.sum | 6 +- handler/products.go | 0 main.go | 92 +++++++---------- migrations/000001_init_schema.up.sql | 8 +- model/product.go | 33 ------ model/user.go | 0 16 files changed, 277 insertions(+), 155 deletions(-) delete mode 100644 app.env create mode 100644 controllers/product.go create mode 100644 controllers/user.go create mode 100644 db_client/.db_client.go.swp create mode 100644 db_client/db_client.go delete mode 100644 handler/products.go delete mode 100644 model/product.go delete mode 100644 model/user.go diff --git a/.gitignore b/.gitignore index 34a60b1..c134ed4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -dist -.env -.air.toml -tmp/ \ No newline at end of file +# build, generated files +dist +tmp/ +.air.toml + + +# environment variable files +app.env \ No newline at end of file diff --git a/Makefile b/Makefile index 5fad503..c859a3d 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ -dev: +dev: gin --appPort 5432 -i run main.go \ No newline at end of file diff --git a/app.env b/app.env deleted file mode 100644 index c798142..0000000 --- a/app.env +++ /dev/null @@ -1,15 +0,0 @@ -POSTGRES_HOST=127.0.0.1 -POSTGRES_DB=shelf -POSTGRES_TEST_DB=shelf_test -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_PORT=5432 -ENV=dev - -POSTGRES_DRIVER=postgres -POSTGRES_SOURCE=postgresql://postgres:postgres@localhost:5432/shelf?sslmode=disable - -BCRYPT_PASSWORD=speak-friend-and-enter -SALT_ROUNDS=10 -TOKEN_SECRET=alohomora123! -TOKEN_SECRET_TEST=eyJhbGciOiJIUzI1NiIsInR \ No newline at end of file diff --git a/config/default.go b/config/default.go index f7aa68e..707ea77 100644 --- a/config/default.go +++ b/config/default.go @@ -1,27 +1,27 @@ -package config - -import ( - "github.com/spf13/viper" -) - -type Config struct { - PostgreDriver string `mapstructure:"POSTGRES_DRIVER"` - PostgresSource string `mapstructure:"POSTGRES_SOURCE"` - PostgresPort string `mapstructure:"POSTGRES_PORT"` -} - -func LoadConfig(path string) (config Config, err error) { - viper.AddConfigPath(path) - viper.SetConfigType("env") - viper.SetConfigName("app") - - viper.AutomaticEnv() - - err = viper.ReadInConfig() - if err != nil { - return - } - - err = viper.Unmarshal(&config) - return -} +package config + +import ( + "github.com/spf13/viper" +) + +type Config struct { + PostgresDriver string `mapstructure:"POSTGRES_DRIVER"` + PostgresSource string `mapstructure:"POSTGRES_SOURCE"` + PostgresPort string `mapstructure:"POSTGRES_PORT"` +} + +func LoadConfig(path string) (config Config, err error) { + viper.AddConfigPath(path) + viper.SetConfigType("env") + viper.SetConfigName("app") + + viper.AutomaticEnv() + + err = viper.ReadInConfig() + if err != nil { + return + } + + err = viper.Unmarshal(&config) + return +} diff --git a/controllers/product.go b/controllers/product.go new file mode 100644 index 0000000..7518f86 --- /dev/null +++ b/controllers/product.go @@ -0,0 +1,145 @@ +package controllers + +import ( + "database/sql" + "net/http" + "strconv" + + "git.sp4ke.xyz/AnisB/shelf_api_go/db_client" + "github.com/gin-gonic/gin" +) + +type Product struct { + Id int64 `db:"id"` + Name string `db:"name"` + Price float64 `db:"price"` +} + +// SQL +// row := db_client.ConnectDB.QueryRow("", id) + +// SQLX +// db_client.ConnectDB.Get() / SINGLE ROW +// db_client.ConnectDB.Select() / SLICE OF ROWS ( query ) + +func GetProducts(c *gin.Context) { + var products []Product + + err := db_client.ConnectDB.Select(&products, "SELECT * FROM products") + if err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "error": true, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, products) +} + +func GetProduct(c *gin.Context) { + idStr := c.Param("id") + id, _ := strconv.Atoi(idStr) + var product Product + + err := db_client.ConnectDB.Get(&product, "SELECT * FROM products WHERE id=($1)", id) + if err == sql.ErrNoRows { + c.JSON(http.StatusNotFound, gin.H{ + "error": true, + "message": "Product not found", + }) + return + } + + c.JSON(http.StatusOK, product) +} + +func CreateProduct(c *gin.Context) { + var reqBody Product + if err := c.ShouldBindJSON(&reqBody); err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "error": true, + "message": "Invalid request body", + }) + return + } + res, err := db_client.ConnectDB.Exec("INSERT INTO products (name, price) VALUES ($1, $2)", + reqBody.Name, + reqBody.Price) + + // Tow key methods we get form res + // res.LastInsertId + // res.RowsAffected + + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": true, + "message": "Could Create Product", + }) + return + } + + id, _ := res.LastInsertId() + + c.JSON(http.StatusCreated, gin.H{ + "error": false, + "id": id, + "message": "New product created", + }) +} + +func UpdateProduct(c *gin.Context) { + idStr := c.Param("id") + id, _ := strconv.Atoi(idStr) + var reqBody Product + + if err := c.ShouldBindJSON(&reqBody); err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "error": true, + "message": "Invalid request body", + }) + return + } + + res, err := db_client.ConnectDB.Exec("UPDATE products SET name=$1, price=$2 WHERE id=$3 RETURNING *", reqBody.Name, reqBody.Price, id) + if err == sql.ErrNoRows { + c.JSON(http.StatusNotFound, gin.H{ + "error": true, + "message": "Product not found", + }) + return + } + + AffectedId, _ := res.RowsAffected() + + c.JSON(http.StatusCreated, gin.H{ + "error": false, + "id": AffectedId, + "message": "Product have been updated successfully", + }) + +} + +func DeleteProduct(c *gin.Context) { + idStr := c.Param("id") + id, _ := strconv.Atoi(idStr) + + res, err := db_client.ConnectDB.Exec("DELETE FROM products WHERE id=($1)", id) + + if err == sql.ErrNoRows { + c.JSON(http.StatusNotFound, gin.H{ + "error": true, + "message": "Product not found", + }) + return + } + + AffectedId, _ := res.RowsAffected() + + c.JSON(http.StatusCreated, gin.H{ + "error": false, + "id": AffectedId, + "message": "Product have been deleted successfully", + }) + +} diff --git a/controllers/user.go b/controllers/user.go new file mode 100644 index 0000000..6d0531b --- /dev/null +++ b/controllers/user.go @@ -0,0 +1 @@ +package controllers diff --git a/db_client/.db_client.go.swp b/db_client/.db_client.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..a2b052ddc666882c91528ae0d4fb9d5bbf10a88f GIT binary patch literal 12288 zcmeI2&2G~`5P+v#kPj3D7dU{nMo127yrtN7hm$zCEHXG~pspENHWjx?dbkgGu?F+soV$IiNCLG?`6;Uwx z?yknNyQaDCcD+anoOAqShk9ys>L3GTV5)&Z7L=>AJC(&d3;CFv<{Z0nd1vZy>L3GT zfDDiUGC&5%02v?yWZ?fZki|3b8n<<>*xHSvo;gt$uT+r%GC&5%02v?yWPl8i0Wv@a z$N(821OK1_#|PNF0Pq#b|NsB>`~T=7zz^gL@)`Mr93t{eKE@C6skt1CDTam-}JP#^lfDDiUGC&5%02v?yWPl8i0W$D+ z23~=IA0yJCec#EPD6mXV4f-wnyoKCUYLyXWE=VO_+S%P4oJl0Y&lTIX4(X3-< zzH6zKRYGGhbP`syHS6s5cvnrlBae2w%nW6=J8*4J_jx2;9`Bi8{NDJyuVtbY&!-h# zb{a4Bggx4S!B>?`>pab`&UYQWG#_d{Nz=rGG4Z%fko{OE8M6RHMO0$P>p7vo11zrA F!%yHO=sN%a literal 0 HcmV?d00001 diff --git a/db_client/db_client.go b/db_client/db_client.go new file mode 100644 index 0000000..f169e93 --- /dev/null +++ b/db_client/db_client.go @@ -0,0 +1,32 @@ +package db_client + +import ( + "log" + + "git.sp4ke.xyz/AnisB/shelf_api_go/config" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +var ConnectDB *sqlx.DB + +func InitialiseDBConnection() { + config, err := config.LoadConfig(".") + + if err != nil { + log.Fatalf("could not load config: %v\n", err) + } + + db, err := sqlx.Open(config.PostgresDriver, config.PostgresSource) + + if err != nil { + log.Fatalf("could not connect to database: %v\n", err) + } + + err = db.Ping() + if err != nil { + log.Fatalf("could not ping database: %v\n", err) + } + + ConnectDB = db +} diff --git a/docker-compose.yml b/docker-compose.yml index 29cb24c..3acc082 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,14 @@ -version: '3.9' - -services: - postgres: - image: postgres - ports: - - '5432:5432' - env_file: - - .env - volumes: - - 'postgres:/var/lib/postgresql/data' - -volumes: +version: '3.9' + +services: + postgres: + image: postgres + ports: + - '5432:5432' + env_file: + - .env + volumes: + - 'postgres:/var/lib/postgresql/data' + +volumes: postgres: \ No newline at end of file diff --git a/go.mod b/go.mod index a695238..d90e3ba 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.19 require ( github.com/gin-gonic/gin v1.8.1 + github.com/jmoiron/sqlx v1.3.5 + github.com/lib/pq v1.10.6 github.com/spf13/viper v1.12.0 ) @@ -15,10 +17,8 @@ require ( github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/goccy/go-json v0.9.11 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.6 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -32,7 +32,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.3.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect - golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index b8c8e69..05ed036 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,7 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -166,6 +167,7 @@ github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamh github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -232,8 +234,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/handler/products.go b/handler/products.go deleted file mode 100644 index e69de29..0000000 diff --git a/main.go b/main.go index af68eb6..6fe5b3c 100644 --- a/main.go +++ b/main.go @@ -1,53 +1,39 @@ -package main - -import ( - "fmt" - "log" - - "git.sp4ke.xyz/AnisB/shelf_api_go/config" - "github.com/gin-gonic/gin" - "github.com/jmoiron/sqlx" - - _ "github.com/lib/pq" -) - -var ( - server *gin.Engine - db *sqlx.DB -) - -func main() { - config, err := config.LoadConfig(".") - - if err != nil { - log.Fatalf("could not load config: %v", err) - } - - fmt.Printf("%T\n", config) - - db, err := sqlx.Open(config.PostgreDriver, config.PostgresSource) - - if err != nil { - log.Fatalf("could not connect to database: %v", err) - } - - err = db.Ping() - fmt.Printf("Ping: %v\n", db.Ping()) - if err != nil { - log.Fatalf("could not ping database: %v", err) - } - - r := gin.Default() - - if err := r.Run(":5000"); err != nil { - panic(err.Error()) - } - - /*router := server.Group("/api") - - router.GET("/healthchecker", func(ctx *gin.Context) { - ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Welcome to Golang with PostgreSQL"}) - }) - - log.Fatal(server.Run(":" + config.PostgresPort))*/ -} +package main + +import ( + "net/http" + + "git.sp4ke.xyz/AnisB/shelf_api_go/controllers" + "git.sp4ke.xyz/AnisB/shelf_api_go/db_client" + "github.com/gin-gonic/gin" +) + +func main() { + + db_client.InitialiseDBConnection() + + r := gin.Default() + + r.GET("/", func(ctx *gin.Context) { + ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Welcome to Golang with PostgreSQL"}) + }) + + // Products + r.GET("/products", controllers.GetProducts) + r.GET("/products/:id", controllers.GetProduct) + r.POST("/products", controllers.CreateProduct) + r.PUT("products/:id", controllers.UpdateProduct) + r.DELETE("products/:id", controllers.DeleteProduct) + + if err := r.Run(":5000"); err != nil { + panic(err.Error()) + } + + /*router := server.Group("/api") + + router.GET("/healthchecker", func(ctx *gin.Context) { + ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Welcome to Golang with PostgreSQL"}) + }) + + log.Fatal(server.Run(":" + config.PostgresPort))*/ +} diff --git a/migrations/000001_init_schema.up.sql b/migrations/000001_init_schema.up.sql index 5b14f46..1e0b546 100644 --- a/migrations/000001_init_schema.up.sql +++ b/migrations/000001_init_schema.up.sql @@ -1,5 +1,5 @@ -CREATE TABLE IF NOT EXISTS products ( - id SERIAL PRIMARY KEY, - name VARCHAR(250) NOT NULL, - price INTEGER NOT NULL +CREATE TABLE IF NOT EXISTS products ( + id SERIAL PRIMARY KEY, + name VARCHAR(250) NOT NULL, + price INTEGER NOT NULL ); \ No newline at end of file diff --git a/model/product.go b/model/product.go deleted file mode 100644 index 901615a..0000000 --- a/model/product.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "database/sql" - "errors" -) - - -type product struct { - ID int `json:"id"` - Name string `json:"name"` - Price float64 `json:"price"` -} - -func getProducts(db *sql.DB, start, count int) ([]product, error) { - return nil, errors.New("Not implemented") -} - -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") -} \ No newline at end of file diff --git a/model/user.go b/model/user.go deleted file mode 100644 index e69de29..0000000