diff --git a/.env b/.env new file mode 100644 index 0000000..d98cf7f --- /dev/null +++ b/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=genitywebapp \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0328870..3bf1152 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ # Dependency directories (remove the comment below to include it) # vendor/ +# Output directories +bins/ + # ---> macOS # General .DS_Store diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index eab6d63..0000000 --- a/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM golang:alpine as builder - -RUN mkdir -p /go/src/app -WORKDIR /go/src/app - -ADD . /go/src/app - -RUN go get -d -v \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8e57b25 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +build: + docker-compose build + +up: build + docker-compose up + +build-binary: + go mod download + GOOS=linux GOARCH=amd64 go build -o bins/genityapp -v cmd/* + +run: build-binary up + + +.PHONY: build-binary run build up diff --git a/README.md b/README.md index e84423d..f0e4973 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ -Just basic golang and postgres docker poc. +#How to run this project + +##Navigate to root of project, run following command +`make run` + +###API interfacing +* Get request + `curl http://localhost:5000/get-data?title=shahzad` +* Post request + `curl --request POST 'http://localhost:5000/post-data' --data "title=shahzad"` To build docker compose `docker-compose up --build` @@ -10,6 +19,6 @@ To run docker compose To connect with psql using following command -`docker exec -it poc-golang-postgres-docker_db_1 psql -U root_user -W db_genity` +`docker exec -it genitywebapp_db_1 psql -U root_user -W db_genity` diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..e17a0bf --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:alpine as builder + + +ADD bins/genityapp /etc/local/genityapp + +WORKDIR /etc/local/ + +ENTRYPOINT ["./genityapp"] \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..623733f --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "domain/genityapp/internal/app" + "fmt" + _ "github.com/lib/pq" + "log" + "net/http" +) + +func main() { + app.InitDb() + + defer app.CloseDB() + + http.HandleFunc("/get-data", app.UserGetHandler) + http.HandleFunc("/post-data", app.UserPostHandler) + + fmt.Println("Listening on :5000") + log.Fatal(http.ListenAndServe(":5000", nil)) + +} diff --git a/configs/db.env b/configs/db.env new file mode 100644 index 0000000..b81c7e3 --- /dev/null +++ b/configs/db.env @@ -0,0 +1,3 @@ +POSTGRES_DB=db_genity +POSTGRES_USER=root_user +POSTGRES_PASSWORD=shahzad12 \ No newline at end of file diff --git a/configs/web.env b/configs/web.env new file mode 100644 index 0000000..35d07fd --- /dev/null +++ b/configs/web.env @@ -0,0 +1,5 @@ +dbhost=genitywebapp_db_1 +dbport=5432 +dbuser=root_user +dbpass=shahzad12 +dbname=db_genity \ No newline at end of file diff --git a/db_dump b/db_dump deleted file mode 100644 index cfacdf5..0000000 --- a/db_dump +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE users ( user_id serial PRIMARY KEY, title VARCHAR ( 50 ) UNIQUE NOT NULL, created_on TIMESTAMP NOT NULL); -INSERT INTO users (title,created_on) VALUES('shahzad','2013-06-01'); - - diff --git a/docker-compose.yml b/docker-compose.yml index 22eb0de..5431da7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,30 +1,33 @@ -version: '3' +version: "3" services: db: image: postgres - environment: - POSTGRES_DB: db_genity - POSTGRES_USER: root_user - POSTGRES_PASSWORD: shahzad12 + env_file: ./configs/db.env volumes: - db_postgres:/var/lib/postgresql/data - ports: - - "5436:5432" + - ./scripts/db/ddl_01.sql:/docker-entrypoint-initdb.d/10_schema.sql + - ./scripts/db/dml_01.sql:/docker-entrypoint-initdb.d/20_insert.sql + expose: + - "5432" + restart: always networks: - - genity_network - app: - build: "" - command: [ "go", "run", "main.go","api.go"] + - genityapp_network + web: + build: + context: . + dockerfile: build/Dockerfile + env_file: ./configs/web.env ports: - - "8080:8080" + - "5000:5000" + restart: always depends_on: - db links: - db networks: - - genity_network + - genityapp_network volumes: db_postgres: networks: - genity_network: + genityapp_network: driver: bridge \ No newline at end of file diff --git a/go.mod b/go.mod index 3b7747b..f3d8b8e 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,8 @@ -module genity-code-review +module domain/genityapp go 1.14 require ( github.com/lib/pq v1.8.0 + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d ) diff --git a/api.go b/internal/app/api.go similarity index 53% rename from api.go rename to internal/app/api.go index 4bcc3dc..39d432c 100644 --- a/api.go +++ b/internal/app/api.go @@ -1,8 +1,9 @@ -package main +package app import ( "encoding/json" "fmt" + uuid "github.com/nu7hatch/gouuid" "net/http" "time" ) @@ -11,53 +12,81 @@ type User struct { UserID int Title string CreatedOn time.Time + Uuid string } type Users struct { users []User } +/** +Sample request http://localhost:5000/get-data?title=shahzad +*/ func UserGetHandler(w http.ResponseWriter, req *http.Request) { if req.Method == "GET" { - users := Users{} - err := queryRepos(&users) + title, ok := req.URL.Query()["title"] + + if !ok || len(title[0]) < 1 { + http.Error(w, "Url Param 'title' is missing", 400) + return + } + + // Query()["key"] will return an array of items, + // we only want the single item. + userTitle := title[0] + + usersData := Users{} + + err := queryRepos(&usersData, userTitle) if err != nil { http.Error(w, err.Error(), 500) return } - out, err := json.Marshal(users.users) - if err != nil { - http.Error(w, err.Error(), 500) - return - } + if len(usersData.users) > 0 { - fmt.Fprintf(w, string(out)) + out, err := json.Marshal(usersData.users) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + fmt.Fprintf(w, string(out)) + } else { + fmt.Fprintf(w, "User does not exist") + } } else { fmt.Fprintf(w, "Sorry, only Get methods are supported for thus api.") } } +/** +Sample request curl --request POST 'http://localhost:5000/post-data' --data "title=shahzad" +*/ func UserPostHandler(w http.ResponseWriter, req *http.Request) { if req.Method == "POST" { if err := req.ParseForm(); err != nil { fmt.Fprintf(w, "ParseForm() err: %v", err) return } + + uuid, err := uuid.NewV4() + if err != nil { + http.Error(w, err.Error(), 500) + + } + title := req.FormValue("title") - sqlInsert := ` -INSERT INTO users (title, created_on) -VALUES ($1, $2) -RETURNING user_id` + if len(title) > 0 { user_id := 0 - row := db.QueryRow(sqlInsert, title, time.Now()) + row := Db.QueryRow(SqlPostUser, title, time.Now().Format(time.RFC3339), uuid.String()) err := row.Scan(&user_id) if err != nil { - panic(err) + fmt.Fprintf(w, err.Error()) } - fmt.Fprintf(w, "user creted") + fmt.Fprintf(w, `title=`+title) } else { fmt.Fprintf(w, "Invalid data") } @@ -66,15 +95,9 @@ RETURNING user_id` } } -func queryRepos(users *Users) error { - rows, err := db.Query(` - SELECT - user_id, - title, - created_on - - FROM users; - `) +func queryRepos(users *Users, userTitle string) error { + + rows, err := Db.Query(SqlGetUser, userTitle) if err != nil { return err } @@ -85,6 +108,7 @@ func queryRepos(users *Users) error { &user.UserID, &user.Title, &user.CreatedOn, + &user.Uuid, ) if err != nil { return err diff --git a/internal/app/db.go b/internal/app/db.go new file mode 100644 index 0000000..5c5aad1 --- /dev/null +++ b/internal/app/db.go @@ -0,0 +1,90 @@ +package app + +import ( + "database/sql" + "fmt" + "os" +) + +const ( + dbhost = "dbhost" + dbport = "dbport" + dbuser = "dbuser" + dbpass = "dbpass" + dbname = "dbname" +) + +const ( + SqlGetUser = ` + SELECT + user_id, + title, + created_on, + uuid + + FROM users + WHERE title=$1; + ` + SqlPostUser = ` +INSERT INTO users (title, created_on,uuid) +VALUES ($1, $2, $3) +RETURNING user_id` +) + +var Db *sql.DB + +func InitDb() { + config := dbConfig() + var err error + psqlInfo := fmt.Sprintf("postgres://%v:%v@%v:%v/%v?sslmode=disable", + config[dbuser], config[dbpass], + config[dbhost], config[dbport], config[dbname]) + + fmt.Println(psqlInfo) + Db, err = sql.Open("postgres", psqlInfo) + if err != nil { + panic(err) + } + err = Db.Ping() + if err != nil { + panic(err) + } + fmt.Println("Successfully connected!") +} + +func CloseDB() { + Db.Close() +} + +func dbConfig() map[string]string { + + conf := make(map[string]string) + + host, ok := os.LookupEnv(dbhost) + if !ok { + panic("DBHOST environment variable required but not set") + } + port, ok := os.LookupEnv(dbport) + if !ok { + panic("DBPORT environment variable required but not set") + } + user, ok := os.LookupEnv(dbuser) + if !ok { + panic("DBUSER environment variable required but not set") + } + password, ok := os.LookupEnv(dbpass) + if !ok { + panic("DBPASS environment variable required but not set") + } + name, ok := os.LookupEnv(dbname) + if !ok { + panic("DBNAME environment variable required but not set") + } + + conf[dbhost] = host + conf[dbport] = port + conf[dbuser] = user + conf[dbpass] = password + conf[dbname] = name + return conf +} diff --git a/main.go b/main.go deleted file mode 100644 index af6072a..0000000 --- a/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" - _ "github.com/lib/pq" - "log" - "net/http" -) - -var db *sql.DB - -const ( - dbhost = "DBHOST" - dbport = "DBPORT" - dbuser = "DBUSER" - dbpass = "DBPASS" - dbname = "DBNAME" -) - -func main() { - initDb() - defer db.Close() - - http.HandleFunc("/api/users", UserGetHandler) - http.HandleFunc("/api/user", UserPostHandler) - - fmt.Println("Listening on :8080") - log.Fatal(http.ListenAndServe(":8080", nil)) - -} - - -func initDb() { - config := dbConfig() - var err error - psqlInfo := fmt.Sprintf("postgres://%v:%v@%v:%v/%v?sslmode=disable", - config[dbuser], config[dbpass], - config[dbhost], config[dbport], config[dbname]) - - fmt.Println(psqlInfo) - db, err = sql.Open("postgres", psqlInfo) - if err != nil { - panic(err) - } - err = db.Ping() - if err != nil { - panic(err) - } - fmt.Println("Successfully connected!") - - - - - -} - -func dbConfig() map[string]string { - conf := make(map[string]string) - conf[dbhost] = "poc-golang-postgres-docker_db_1" - conf[dbport] = "5432" - conf[dbuser] = "root_user" - conf[dbpass] = "shahzad12" - conf[dbname] = "db_genity" - return conf -} diff --git a/scripts/db/ddl_01.sql b/scripts/db/ddl_01.sql new file mode 100644 index 0000000..f132c71 --- /dev/null +++ b/scripts/db/ddl_01.sql @@ -0,0 +1,2 @@ +CREATE TABLE users (user_id serial PRIMARY KEY, title VARCHAR ( 50 ) UNIQUE NOT NULL, + uuid VARCHAR ( 50 ) UNIQUE NOT NULL, created_on TIMESTAMP NOT NULL); \ No newline at end of file diff --git a/scripts/db/dml_01.sql b/scripts/db/dml_01.sql new file mode 100644 index 0000000..0d46f89 --- /dev/null +++ b/scripts/db/dml_01.sql @@ -0,0 +1 @@ +INSERT INTO users (title,created_on, uuid) VALUES('shahzad','2013-06-01', '8272-28292-287292'); \ No newline at end of file