From 7f65aad0932f386638a4f5079f4da91f774ef797 Mon Sep 17 00:00:00 2001 From: reenphygeorge Date: Sat, 20 Jan 2024 01:52:43 +0530 Subject: [PATCH 1/3] feat: containerized --- Dockerfile | 18 ++++++++++-------- docker-compose.yml | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index d1438c5..26c911d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,16 @@ -# docker file for building Go application -FROM ubuntu:latest +FROM golang:alpine -# Install dependencies -RUN sudo apt install -y git go wget +WORKDIR /app -COPY . /app +COPY . . + +RUN go get github.com/lib/pq + +RUN go get github.com/joho/godotenv -WORKDIR /app -# Build the application RUN go build -o main . -CMD [ "main" ] \ No newline at end of file +EXPOSE 80 + +CMD [ "./main" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fc5704e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3' + +services: + db: + image: postgres:latest + env_file: + - .env + volumes: + - postgres_data:/var/lib/postgresql/data + api: + build: + context: . + dockerfile: Dockerfile + ports: + - "8091:80" + depends_on: + - db +volumes: + postgres_data: + From e5f9f91d3bcfdd1932ea5674ef593ee121ff67a6 Mon Sep 17 00:00:00 2001 From: reenphygeorge Date: Sat, 20 Jan 2024 01:53:01 +0530 Subject: [PATCH 2/3] feat: api restructured --- .env | 6 +++++ api.go | 29 ---------------------- go.mod | 2 +- handler/create.handler.go | 11 +++++++++ handler/update.handler.go | 21 ++++++++++++++++ main.go | 9 ++++++- router/create.router.go | 51 +++++++++++++++++++++++++++++++++++++++ router/register.go | 10 ++++++++ router/update.router.go | 48 ++++++++++++++++++++++++++++++++++++ utils.go | 13 ---------- utils/connection.go | 46 +++++++++++++++++++++++++++++++++++ utils/types.go | 17 +++++++++++++ 12 files changed, 219 insertions(+), 44 deletions(-) create mode 100644 .env delete mode 100644 api.go create mode 100644 handler/create.handler.go create mode 100644 handler/update.handler.go create mode 100644 router/create.router.go create mode 100644 router/register.go create mode 100644 router/update.router.go delete mode 100644 utils.go create mode 100644 utils/connection.go create mode 100644 utils/types.go diff --git a/.env b/.env new file mode 100644 index 0000000..248dd0e --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +DATABASE_URL = "postgres://user:pass@db:5432/db?sslmode=disable" +POSTGRES_USER = "user" +POSTGRES_PASSWORD = "pass" +POSTGRES_HOST = "db" +POSTGRES_PORT = "5432" +POSTGRES_DB = "db" \ No newline at end of file diff --git a/api.go b/api.go deleted file mode 100644 index cbef2ef..0000000 --- a/api.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "fmt" - "net/http" -) - -func setupJsonApi() { - http.HandleFunc("/createUser", func(w http.ResponseWriter, r *http.Request) { - // create mysql connection - conn := createConnection() - name := r.FormValue("name") - email := r.FormValue("email") - query := "INSERT INTO users (name, email) VALUES (" + name + ", " + email + ")" - result, err := conn.Exec(query) - fmt.Println("result ", result, " err ", err.Error()) - w.Write([]byte("Created user successfully!")) - }) - http.HandleFunc("/updateUser", func(w http.ResponseWriter, r *http.Request) { - // create mysql connection - conn := createConnection() - name := r.FormValue("name") - email := r.FormValue("email") - query := "Update users set name=" + name + ", email=" + email + " where id=" + r.FormValue("id") - result, err := conn.Exec(query) - fmt.Println("result ", result, " err ", err.Error()) - w.Write([]byte("User updated successfully!")) - }) -} diff --git a/go.mod b/go.mod index 541428f..0f6acbf 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module screening -go 1.21.3 +go 1.21.3 \ No newline at end of file diff --git a/handler/create.handler.go b/handler/create.handler.go new file mode 100644 index 0000000..b8d374c --- /dev/null +++ b/handler/create.handler.go @@ -0,0 +1,11 @@ +package handler + +import ( + "database/sql" +) + +func CreateUser(conn *sql.DB, name string, email string) error { + query := "INSERT INTO Users (name, email) VALUES ($1, $2)" + _, err := conn.Exec(query, name, email) + return err +} diff --git a/handler/update.handler.go b/handler/update.handler.go new file mode 100644 index 0000000..57f9d99 --- /dev/null +++ b/handler/update.handler.go @@ -0,0 +1,21 @@ +package handler + +import ( + "database/sql" +) + +func UpdateUser(conn *sql.DB, name string, email string, id int) error { + var query string + var err error + if name == "" && email != "" { + query = "UPDATE Users SET email = $1 WHERE id = $2" + _, err = conn.Exec(query, email, id) + } else if name != "" && email == "" { + query = "UPDATE Users SET name = $1 WHERE id = $2" + _, err = conn.Exec(query, name, id) + } else { + query = "UPDATE Users SET name = $1, email = $2 WHERE id = $3" + _, err = conn.Exec(query, name, email, id) + } + return err +} diff --git a/main.go b/main.go index 18805dd..d003eea 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,16 @@ package main import ( "net/http" + "screening/router" + "screening/utils" + + "github.com/joho/godotenv" ) func main() { - setupJsonApi() + godotenv.Load() + db := utils.CreateConnection() + router.RegisterRoute(db) + defer db.Close() http.ListenAndServe(":80", nil) } diff --git a/router/create.router.go b/router/create.router.go new file mode 100644 index 0000000..0763e9a --- /dev/null +++ b/router/create.router.go @@ -0,0 +1,51 @@ +package router + +import ( + "database/sql" + "encoding/json" + "io/ioutil" + "net/http" + "screening/handler" + "screening/utils" +) + +func CreateRoute(conn *sql.DB) { + var responseData utils.ResponseData + http.HandleFunc("/createUser", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body", http.StatusBadRequest) + return + } + var user utils.CreateUserType + err = json.Unmarshal(body, &user) + if user.Name == "" { + responseData.Success = false + responseData.Message = "Name attribute missing" + } else if user.Email == "" { + responseData.Success = false + responseData.Message = "Email attribute missing" + } else { + err = handler.CreateUser(conn, user.Name, user.Email) + if err != nil { + responseData.Success = false + responseData.Message = "User Create Error!" + } else { + responseData.Success = true + responseData.Message = "User Created!" + } + } + } else { + responseData.Success = false + responseData.Message = "Method not found!" + } + jsonData, jsonError := json.Marshal(responseData) + if jsonError != nil { + http.Error(w, "Error encoding JSON", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(jsonData) + }) +} diff --git a/router/register.go b/router/register.go new file mode 100644 index 0000000..f63679e --- /dev/null +++ b/router/register.go @@ -0,0 +1,10 @@ +package router + +import ( + "database/sql" +) + +func RegisterRoute(conn *sql.DB) { + CreateRoute(conn) + UpdateRouter(conn) +} diff --git a/router/update.router.go b/router/update.router.go new file mode 100644 index 0000000..c5f53e5 --- /dev/null +++ b/router/update.router.go @@ -0,0 +1,48 @@ +package router + +import ( + "database/sql" + "encoding/json" + "io/ioutil" + "net/http" + "screening/handler" + "screening/utils" +) + +func UpdateRouter(conn *sql.DB) { + var responseData utils.ResponseData + http.HandleFunc("/updateUser", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPatch { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body", http.StatusBadRequest) + return + } + var user utils.UpdateUserType + err = json.Unmarshal(body, &user) + if user.Id == 0 { + responseData.Success = false + responseData.Message = "id attribute missing" + } else { + err = handler.UpdateUser(conn, user.Name, user.Email, user.Id) + if err != nil { + responseData.Success = false + responseData.Message = "User Updated Error!" + } else { + responseData.Success = true + responseData.Message = "User Updated!" + } + } + } else { + responseData.Success = false + responseData.Message = "Method not found!" + } + jsonData, jsonError := json.Marshal(responseData) + if jsonError != nil { + http.Error(w, "Error encoding JSON", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(jsonData) + }) +} diff --git a/utils.go b/utils.go deleted file mode 100644 index e65cd87..0000000 --- a/utils.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" -) - -// createConnection creates a connection to mysql database -func createConnection() *sql.DB { - db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/test") - fmt.Println("sql open " + err.Error()) - return db -} diff --git a/utils/connection.go b/utils/connection.go new file mode 100644 index 0000000..b9f42dc --- /dev/null +++ b/utils/connection.go @@ -0,0 +1,46 @@ +package utils + +import ( + "database/sql" + "fmt" + _ "github.com/lib/pq" + "log" + "os" +) + +func CreateConnection() *sql.DB { + user := os.Getenv("POSTGRES_USER") + password := os.Getenv("POSTGRES_PASSWORD") + dbName := os.Getenv("POSTGRES_DB") + port := os.Getenv("POSTGRES_PORT") + host := os.Getenv("POSTGRES_HOST") + connUrl := fmt.Sprintf( + "postgres://%s:%s@%s:%s/%s?sslmode=disable", + user, password, host, port, dbName, + ) + db, err := sql.Open("postgres", connUrl) + if err != nil { + log.Fatal("Error opening database connection:", err) + } + err = db.Ping() + if err != nil { + log.Fatal("Error pinging database:", err) + } + fmt.Println("Connected to the PostgreSQL database successfully!") + + // SQL query to create the "users" table + query := ` + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name VARCHAR(50), + email VARCHAR(50) + )` + + // Execute the query + _, err = db.Exec(query) + if err != nil { + log.Fatal("Error creating table:", err) + } + + return db +} diff --git a/utils/types.go b/utils/types.go new file mode 100644 index 0000000..19a8e66 --- /dev/null +++ b/utils/types.go @@ -0,0 +1,17 @@ +package utils + +type CreateUserType struct { + Name string `json:"name"` + Email string `json:"email"` +} + +type UpdateUserType struct { + Id int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +type ResponseData struct { + Success bool `json:"success"` + Message string `json:"message"` +} From d7026c8be97d17a68d684b30f3168646f220164f Mon Sep 17 00:00:00 2001 From: reenphygeorge Date: Sat, 20 Jan 2024 03:06:12 +0530 Subject: [PATCH 3/3] chore: file based logger --- .env | 4 ++-- Dockerfile | 1 + handler/create.handler.go | 1 + handler/update.handler.go | 1 + main.go | 15 +++++++++++---- router/create.router.go | 5 +++++ router/register.go | 1 + router/update.router.go | 6 ++++++ utils/connection.go | 21 ++++++++++++--------- utils/logger.go | 17 +++++++++++++++++ utils/types.go | 2 ++ 11 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 utils/logger.go diff --git a/.env b/.env index 248dd0e..9ae4bc2 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ -DATABASE_URL = "postgres://user:pass@db:5432/db?sslmode=disable" POSTGRES_USER = "user" POSTGRES_PASSWORD = "pass" POSTGRES_HOST = "db" POSTGRES_PORT = "5432" -POSTGRES_DB = "db" \ No newline at end of file +POSTGRES_DB = "db" +API_PORT = "80" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 26c911d..083266b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN go get github.com/lib/pq RUN go get github.com/joho/godotenv +RUN go get github.com/rs/zerolog/log RUN go build -o main . diff --git a/handler/create.handler.go b/handler/create.handler.go index b8d374c..d6290fb 100644 --- a/handler/create.handler.go +++ b/handler/create.handler.go @@ -4,6 +4,7 @@ import ( "database/sql" ) +// Raw query for inserting func CreateUser(conn *sql.DB, name string, email string) error { query := "INSERT INTO Users (name, email) VALUES ($1, $2)" _, err := conn.Exec(query, name, email) diff --git a/handler/update.handler.go b/handler/update.handler.go index 57f9d99..2de9cf6 100644 --- a/handler/update.handler.go +++ b/handler/update.handler.go @@ -4,6 +4,7 @@ import ( "database/sql" ) +// Raw query for updating with some conditional handlings func UpdateUser(conn *sql.DB, name string, email string, id int) error { var query string var err error diff --git a/main.go b/main.go index d003eea..d1fde36 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "net/http" + "os" "screening/router" "screening/utils" @@ -9,9 +11,14 @@ import ( ) func main() { + //Loading envs godotenv.Load() - db := utils.CreateConnection() - router.RegisterRoute(db) - defer db.Close() - http.ListenAndServe(":80", nil) + apiPort := os.Getenv("API_PORT") + apiPortFmt := fmt.Sprintf(":%s", apiPort) + + // DB Connection and Route registering + conn := utils.CreateConnection() + router.RegisterRoute(conn) + defer conn.Close() + http.ListenAndServe(apiPortFmt, nil) } diff --git a/router/create.router.go b/router/create.router.go index 0763e9a..9fe11e2 100644 --- a/router/create.router.go +++ b/router/create.router.go @@ -9,9 +9,12 @@ import ( "screening/utils" ) +// Router to create a user, db connection object as param func CreateRoute(conn *sql.DB) { + logger := utils.Logger() var responseData utils.ResponseData http.HandleFunc("/createUser", func(w http.ResponseWriter, r *http.Request) { + // route only for POST method if r.Method == http.MethodPost { body, err := ioutil.ReadAll(r.Body) if err != nil { @@ -27,6 +30,7 @@ func CreateRoute(conn *sql.DB) { responseData.Success = false responseData.Message = "Email attribute missing" } else { + // Calling the handler method err = handler.CreateUser(conn, user.Name, user.Email) if err != nil { responseData.Success = false @@ -42,6 +46,7 @@ func CreateRoute(conn *sql.DB) { } jsonData, jsonError := json.Marshal(responseData) if jsonError != nil { + logger.Error().Msg("Error encoding JSON") http.Error(w, "Error encoding JSON", http.StatusInternalServerError) return } diff --git a/router/register.go b/router/register.go index f63679e..6161699 100644 --- a/router/register.go +++ b/router/register.go @@ -4,6 +4,7 @@ import ( "database/sql" ) +// Registering all available routes func RegisterRoute(conn *sql.DB) { CreateRoute(conn) UpdateRouter(conn) diff --git a/router/update.router.go b/router/update.router.go index c5f53e5..3a0933a 100644 --- a/router/update.router.go +++ b/router/update.router.go @@ -9,11 +9,15 @@ import ( "screening/utils" ) +// Router to create a user, db connection object as param func UpdateRouter(conn *sql.DB) { + logger := utils.Logger() var responseData utils.ResponseData http.HandleFunc("/updateUser", func(w http.ResponseWriter, r *http.Request) { + // route only for PATCH method if r.Method == http.MethodPatch { body, err := ioutil.ReadAll(r.Body) + logger.Error().Msg("Error reading request body") if err != nil { http.Error(w, "Error reading request body", http.StatusBadRequest) return @@ -24,6 +28,7 @@ func UpdateRouter(conn *sql.DB) { responseData.Success = false responseData.Message = "id attribute missing" } else { + // Calling the handler method err = handler.UpdateUser(conn, user.Name, user.Email, user.Id) if err != nil { responseData.Success = false @@ -39,6 +44,7 @@ func UpdateRouter(conn *sql.DB) { } jsonData, jsonError := json.Marshal(responseData) if jsonError != nil { + logger.Error().Msg("Error encoding JSON") http.Error(w, "Error encoding JSON", http.StatusInternalServerError) return } diff --git a/utils/connection.go b/utils/connection.go index b9f42dc..9dc7352 100644 --- a/utils/connection.go +++ b/utils/connection.go @@ -4,11 +4,12 @@ import ( "database/sql" "fmt" _ "github.com/lib/pq" - "log" "os" ) func CreateConnection() *sql.DB { + logger := Logger() + // Loading envs user := os.Getenv("POSTGRES_USER") password := os.Getenv("POSTGRES_PASSWORD") dbName := os.Getenv("POSTGRES_DB") @@ -18,15 +19,17 @@ func CreateConnection() *sql.DB { "postgres://%s:%s@%s:%s/%s?sslmode=disable", user, password, host, port, dbName, ) - db, err := sql.Open("postgres", connUrl) + + // DB Connection + conn, err := sql.Open("postgres", connUrl) if err != nil { - log.Fatal("Error opening database connection:", err) + logger.Error().Msg("Error opening database connection: " + err.Error()) } - err = db.Ping() + err = conn.Ping() if err != nil { - log.Fatal("Error pinging database:", err) + logger.Error().Msg("Error pinging database: " + err.Error()) } - fmt.Println("Connected to the PostgreSQL database successfully!") + logger.Info().Msg("Connected to the PostgreSQL database successfully!") // SQL query to create the "users" table query := ` @@ -37,10 +40,10 @@ func CreateConnection() *sql.DB { )` // Execute the query - _, err = db.Exec(query) + _, err = conn.Exec(query) if err != nil { - log.Fatal("Error creating table:", err) + logger.Error().Msg("Error creating table: " + err.Error()) } - return db + return conn } diff --git a/utils/logger.go b/utils/logger.go new file mode 100644 index 0000000..5b510ed --- /dev/null +++ b/utils/logger.go @@ -0,0 +1,17 @@ +package utils + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "os" +) + +func Logger() zerolog.Logger { + // logger file setup + file, err := os.OpenFile("file.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Fatal().Err(err).Msg("Failed to open log file") + } + logger := zerolog.New(file).With().Timestamp().Logger() + return logger +} diff --git a/utils/types.go b/utils/types.go index 19a8e66..913e51b 100644 --- a/utils/types.go +++ b/utils/types.go @@ -1,5 +1,7 @@ package utils +// All structs used in this project + type CreateUserType struct { Name string `json:"name"` Email string `json:"email"`