Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
# todo

# HTTP Gateway with gRPC Backend


This project provides an HTTP gateway with a gRPC backend implementation. It allows you to interact with the backend services using HTTP requests.

## Getting Started


These instructions will help you get the project up and running on your local machine.


### Prerequisites


- Docker: Make sure you have Docker installed on your system. You can download Docker from the official website: [https://www.docker.com](https://www.docker.com)



### Installation



1. Clone the repository:



```shell

git clone https://github.com/jeferagudeloc/grpc-http-gateway.git

```

Navigate to the project directory:



```shell

cd grpc-http-gateway

```

Build and run the containers using Docker Compose:



```shell

docker-compose up --build

```

The HTTP gateway service will be accessible at http://localhost:9091.


## Project Structure

The project follows a typical directory structure:



*src/server:* Contains the implementation of the gRPC backend server.

*src/gateway:* Contains the implementation of the HTTP gateway.

*config/data:* Contains the SQL scripts for database initialization.

## Configuration

The project uses Docker Compose for container orchestration. The services and their configurations are defined in the docker-compose.yml file.


***server:*** Builds and runs the gRPC backend server.

***gateway:*** Builds and runs the HTTP gateway service.

***mysql:*** Runs the MySQL database server.

The environment variables for the services can be configured in the docker-compose.yml file.


### Contributing

Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.



### License

This project is licensed under the MIT License.
17 changes: 17 additions & 0 deletions config/data/data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Insert fake data into the "order" table
INSERT INTO http_gateway_grpc.`order` (`id`, `order_number`, `creation_date`, `updation_date`, `status`)
VALUES
('76045a01-93e4-45c9-8670-8ebe780056a2 ', 'ORD001', '2023-05-20', '2023-05-21', 'Pending'),
('8433937f-18a2-4501-ad58-2abc1707aa93 ', 'ORD002', '2023-05-19', '2023-05-20', 'Completed');

-- Insert fake data into the "profile" table
INSERT INTO http_gateway_grpc.`profile` (`id`, `name`, `type`)
VALUES
('1ba3f566-cf9a-4814-99c9-6ceca164c1e2 ', 'Profile 1', 'Type A'),
('553bfd79-f007-4b9f-a9c3-c926c8588881 ', 'Profile 2', 'Type B');

-- Insert fake data into the "user" table
INSERT INTO http_gateway_grpc.`user` (`id`, `name`, `lastname`, `email`, `profile`, `status`, `profile_id`)
VALUES
('64a498d9-09b3-4b0d-98ed-cc796e6457c5 ', 'John', 'Doe', 'john@example.com', 'User Profile', 'Active', '1ba3f566-cf9a-4814-99c9-6ceca164c1e2 '),
('e5a1893f-8ff9-4082-966a-ba6824d8fe50 ', 'Jane', 'Smith', 'jane@example.com', 'Admin Profile', 'Inactive', '553bfd79-f007-4b9f-a9c3-c926c8588881 ');
30 changes: 30 additions & 0 deletions config/data/schemas.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Create a schema
CREATE SCHEMA IF NOT EXISTS http_gateway_grpc;

-- Create the "order" table in the schema
CREATE TABLE http_gateway_grpc.`order` (
`id` VARCHAR(50) PRIMARY KEY,
`order_number` VARCHAR(255) NOT NULL,
`creation_date` DATE,
`updation_date` DATE,
`status` VARCHAR(50)
);

-- Create the "profile" table in the schema
CREATE TABLE http_gateway_grpc.`profile` (
`id` VARCHAR(50) PRIMARY KEY,
`name` VARCHAR(50) NOT NULL,
`type` VARCHAR(50)
);

-- Create the "user" table in the schema
CREATE TABLE http_gateway_grpc.`user` (
`id` VARCHAR(50) PRIMARY KEY,
`name` VARCHAR(50) NOT NULL,
`lastname` VARCHAR(50) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`profile` VARCHAR(50),
`status` VARCHAR(50),
`profile_id` VARCHAR(50),
FOREIGN KEY (`profile_id`) REFERENCES http_gateway_grpc.`profile` (`id`)
);
25 changes: 24 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ services:
container_name: server
volumes:
- ./src/server:/usr/src/app/
depends_on:
- mysql
restart: on-failure
environment:
- MYSQL_HOST=mysql
- MYSQL_DATABASE=http_gateway_grpc
- MYSQL_PORT=3306
- MYSQL_USER=root
- MYSQL_PASSWORD=root
networks:
- bridge
gateway:
Expand All @@ -20,7 +29,21 @@ services:
- server
restart: on-failure
volumes:
- ./src/client:/usr/src/app/
- ./src/gateway:/usr/src/app/
networks:
- bridge
mysql:
image: mysql
command: --default-authentication-plugin=mysql_native_password
hostname: mysql
environment:
MYSQL_ROOT_PASSWORD: root
container_name: mysql
ports:
- "3306:3306"
volumes:
- "./config/data/schemas.sql:/docker-entrypoint-initdb.d/1.sql"
- "./config/data/data.sql:/docker-entrypoint-initdb.d/2.sql"
networks:
- bridge
networks:
Expand Down
1 change: 0 additions & 1 deletion src/gateway/README.md

This file was deleted.

61 changes: 61 additions & 0 deletions src/gateway/application/adapter/api/action/create_order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package action

import (
"encoding/json"
"io/ioutil"
"net/http"

"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/api/response"
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger"
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logging"
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/usecase"
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/domain"
)

type CreateOrderAction struct {
uc usecase.CreateOrderUseCase
log logger.Logger
}

func NewCreateOrderAction(uc usecase.CreateOrderUseCase, log logger.Logger) CreateOrderAction {
return CreateOrderAction{
uc: uc,
log: log,
}
}

func (fova CreateOrderAction) Execute(w http.ResponseWriter, r *http.Request) {
const logKey = "save_device_token"

body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Unable to read request body", http.StatusBadRequest)
return
}

var dt domain.OrderRequest
err = json.Unmarshal(body, &dt)
if err != nil {
http.Error(w, "Unable to parse JSON data", http.StatusBadRequest)
return
}

output, err := fova.uc.Execute(r.Context(), dt)
if err != nil {
switch err {
default:
logging.NewError(
fova.log,
err,
logKey,
http.StatusInternalServerError,
).Log("error saving device token")

response.NewError(err, http.StatusInternalServerError).Send(w)
return
}
}
logging.NewInfo(fova.log, logKey, http.StatusOK).Log("device token has been saved")

response.NewSuccess(output, http.StatusOK).Send(w)
}
1 change: 0 additions & 1 deletion src/gateway/application/adapter/api/action/health_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ import "net/http"
func HealthCheck(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}

78 changes: 78 additions & 0 deletions src/gateway/application/adapter/api/middleware/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package middleware

import (
"bytes"
"io/ioutil"
"net/http"
"strings"
"time"

"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger"
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logging"
"github.com/pkg/errors"
"github.com/urfave/negroni"
)

type Logger struct {
log logger.Logger
}

func NewLogger(log logger.Logger) Logger {
return Logger{log: log}
}

func (l Logger) Execute(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
start := time.Now()

const (
logKey = "logger_middleware"
requestKey = "api_request"
responseKey = "api_response"
)

body, err := getRequestPayload(r)
if err != nil {
logging.NewError(
l.log,
err,
logKey,
http.StatusBadRequest,
).Log("error when getting payload")

return
}

l.log.WithFields(logger.Fields{
"key": requestKey,
"payload": body,
"url": r.URL.Path,
"http_method": r.Method,
}).Infof("started handling request")

next.ServeHTTP(w, r)

end := time.Since(start).Seconds()
res := w.(negroni.ResponseWriter)
l.log.WithFields(logger.Fields{
"key": responseKey,
"url": r.URL.Path,
"http_method": r.Method,
"http_status": res.Status(),
"response_time": end,
}).Infof("completed handling request")
}

func getRequestPayload(r *http.Request) (string, error) {
if r.Body == nil {
return "", errors.New("body not defined")
}

payload, err := ioutil.ReadAll(r.Body)
if err != nil {
return "", errors.Wrap(err, "error read body")
}

r.Body = ioutil.NopCloser(bytes.NewBuffer(payload))

return strings.TrimSpace(string(payload)), nil
}
37 changes: 37 additions & 0 deletions src/gateway/application/adapter/api/response/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package response

import (
"encoding/json"
"net/http"

"github.com/pkg/errors"
)

var (
ErrParameterInvalid = errors.New("parameter invalid")
)

type Error struct {
statusCode int
Errors []string `json:"errors"`
}

func NewError(err error, status int) *Error {
return &Error{
statusCode: status,
Errors: []string{err.Error()},
}
}

func NewErrorMessage(messages []string, status int) *Error {
return &Error{
statusCode: status,
Errors: messages,
}
}

func (e Error) Send(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(e.statusCode)
return json.NewEncoder(w).Encode(e)
}
24 changes: 24 additions & 0 deletions src/gateway/application/adapter/api/response/success.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package response

import (
"encoding/json"
"net/http"
)

type Success struct {
statusCode int
result interface{}
}

func NewSuccess(result interface{}, status int) Success {
return Success{
statusCode: status,
result: result,
}
}

func (r Success) Send(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(r.statusCode)
return json.NewEncoder(w).Encode(r.result)
}
Loading