diff --git a/.env b/.env new file mode 100644 index 0000000..9ae4bc2 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +POSTGRES_USER = "user" +POSTGRES_PASSWORD = "pass" +POSTGRES_HOST = "db" +POSTGRES_PORT = "5432" +POSTGRES_DB = "db" +API_PORT = "80" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d1438c5..083266b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,17 @@ -# 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 . . -WORKDIR /app +RUN go get github.com/lib/pq + +RUN go get github.com/joho/godotenv + +RUN go get github.com/rs/zerolog/log -# 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/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/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: + 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..d6290fb --- /dev/null +++ b/handler/create.handler.go @@ -0,0 +1,12 @@ +package handler + +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) + return err +} diff --git a/handler/update.handler.go b/handler/update.handler.go new file mode 100644 index 0000000..2de9cf6 --- /dev/null +++ b/handler/update.handler.go @@ -0,0 +1,22 @@ +package handler + +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 + 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..d1fde36 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,24 @@ package main import ( + "fmt" "net/http" + "os" + "screening/router" + "screening/utils" + + "github.com/joho/godotenv" ) func main() { - setupJsonApi() - http.ListenAndServe(":80", nil) + //Loading envs + godotenv.Load() + 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 new file mode 100644 index 0000000..9fe11e2 --- /dev/null +++ b/router/create.router.go @@ -0,0 +1,56 @@ +package router + +import ( + "database/sql" + "encoding/json" + "io/ioutil" + "net/http" + "screening/handler" + "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 { + 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 { + // Calling the handler method + 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 { + logger.Error().Msg("Error encoding JSON") + 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..6161699 --- /dev/null +++ b/router/register.go @@ -0,0 +1,11 @@ +package router + +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 new file mode 100644 index 0000000..3a0933a --- /dev/null +++ b/router/update.router.go @@ -0,0 +1,54 @@ +package router + +import ( + "database/sql" + "encoding/json" + "io/ioutil" + "net/http" + "screening/handler" + "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 + } + var user utils.UpdateUserType + err = json.Unmarshal(body, &user) + if user.Id == 0 { + 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 + 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 { + logger.Error().Msg("Error encoding JSON") + 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..9dc7352 --- /dev/null +++ b/utils/connection.go @@ -0,0 +1,49 @@ +package utils + +import ( + "database/sql" + "fmt" + _ "github.com/lib/pq" + "os" +) + +func CreateConnection() *sql.DB { + logger := Logger() + // Loading envs + 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 Connection + conn, err := sql.Open("postgres", connUrl) + if err != nil { + logger.Error().Msg("Error opening database connection: " + err.Error()) + } + err = conn.Ping() + if err != nil { + logger.Error().Msg("Error pinging database: " + err.Error()) + } + logger.Info().Msg("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 = conn.Exec(query) + if err != nil { + logger.Error().Msg("Error creating table: " + err.Error()) + } + + 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 new file mode 100644 index 0000000..913e51b --- /dev/null +++ b/utils/types.go @@ -0,0 +1,19 @@ +package utils + +// All structs used in this project + +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"` +}