Skip to content

Commit e95bf07

Browse files
Merge pull request #3 from jeferagudeloc/develop
[develop]: finish connection between gateway and server + add mysql d…
2 parents a1f7c6b + d31ea35 commit e95bf07

File tree

41 files changed

+964
-61
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+964
-61
lines changed

README.md

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,91 @@
1-
# todo
1+
2+
# HTTP Gateway with gRPC Backend
3+
4+
5+
This project provides an HTTP gateway with a gRPC backend implementation. It allows you to interact with the backend services using HTTP requests.
6+
7+
## Getting Started
8+
9+
10+
These instructions will help you get the project up and running on your local machine.
11+
12+
13+
### Prerequisites
14+
15+
16+
- 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)
17+
18+
19+
20+
### Installation
21+
22+
23+
24+
1. Clone the repository:
25+
26+
27+
28+
```shell
29+
30+
git clone https://github.com/jeferagudeloc/grpc-http-gateway.git
31+
32+
```
33+
34+
Navigate to the project directory:
35+
36+
37+
38+
```shell
39+
40+
cd grpc-http-gateway
41+
42+
```
43+
44+
Build and run the containers using Docker Compose:
45+
46+
47+
48+
```shell
49+
50+
docker-compose up --build
51+
52+
```
53+
54+
The HTTP gateway service will be accessible at http://localhost:9091.
55+
56+
57+
## Project Structure
58+
59+
The project follows a typical directory structure:
60+
61+
62+
63+
*src/server:* Contains the implementation of the gRPC backend server.
64+
65+
*src/gateway:* Contains the implementation of the HTTP gateway.
66+
67+
*config/data:* Contains the SQL scripts for database initialization.
68+
69+
## Configuration
70+
71+
The project uses Docker Compose for container orchestration. The services and their configurations are defined in the docker-compose.yml file.
72+
73+
74+
***server:*** Builds and runs the gRPC backend server.
75+
76+
***gateway:*** Builds and runs the HTTP gateway service.
77+
78+
***mysql:*** Runs the MySQL database server.
79+
80+
The environment variables for the services can be configured in the docker-compose.yml file.
81+
82+
83+
### Contributing
84+
85+
Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.
86+
87+
88+
89+
### License
90+
91+
This project is licensed under the MIT License.

config/data/data.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- Insert fake data into the "order" table
2+
INSERT INTO http_gateway_grpc.`order` (`id`, `order_number`, `creation_date`, `updation_date`, `status`)
3+
VALUES
4+
('76045a01-93e4-45c9-8670-8ebe780056a2 ', 'ORD001', '2023-05-20', '2023-05-21', 'Pending'),
5+
('8433937f-18a2-4501-ad58-2abc1707aa93 ', 'ORD002', '2023-05-19', '2023-05-20', 'Completed');
6+
7+
-- Insert fake data into the "profile" table
8+
INSERT INTO http_gateway_grpc.`profile` (`id`, `name`, `type`)
9+
VALUES
10+
('1ba3f566-cf9a-4814-99c9-6ceca164c1e2 ', 'Profile 1', 'Type A'),
11+
('553bfd79-f007-4b9f-a9c3-c926c8588881 ', 'Profile 2', 'Type B');
12+
13+
-- Insert fake data into the "user" table
14+
INSERT INTO http_gateway_grpc.`user` (`id`, `name`, `lastname`, `email`, `profile`, `status`, `profile_id`)
15+
VALUES
16+
('64a498d9-09b3-4b0d-98ed-cc796e6457c5 ', 'John', 'Doe', 'john@example.com', 'User Profile', 'Active', '1ba3f566-cf9a-4814-99c9-6ceca164c1e2 '),
17+
('e5a1893f-8ff9-4082-966a-ba6824d8fe50 ', 'Jane', 'Smith', 'jane@example.com', 'Admin Profile', 'Inactive', '553bfd79-f007-4b9f-a9c3-c926c8588881 ');

config/data/schemas.sql

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-- Create a schema
2+
CREATE SCHEMA IF NOT EXISTS http_gateway_grpc;
3+
4+
-- Create the "order" table in the schema
5+
CREATE TABLE http_gateway_grpc.`order` (
6+
`id` VARCHAR(50) PRIMARY KEY,
7+
`order_number` VARCHAR(255) NOT NULL,
8+
`creation_date` DATE,
9+
`updation_date` DATE,
10+
`status` VARCHAR(50)
11+
);
12+
13+
-- Create the "profile" table in the schema
14+
CREATE TABLE http_gateway_grpc.`profile` (
15+
`id` VARCHAR(50) PRIMARY KEY,
16+
`name` VARCHAR(50) NOT NULL,
17+
`type` VARCHAR(50)
18+
);
19+
20+
-- Create the "user" table in the schema
21+
CREATE TABLE http_gateway_grpc.`user` (
22+
`id` VARCHAR(50) PRIMARY KEY,
23+
`name` VARCHAR(50) NOT NULL,
24+
`lastname` VARCHAR(50) NOT NULL,
25+
`email` VARCHAR(255) NOT NULL,
26+
`profile` VARCHAR(50),
27+
`status` VARCHAR(50),
28+
`profile_id` VARCHAR(50),
29+
FOREIGN KEY (`profile_id`) REFERENCES http_gateway_grpc.`profile` (`id`)
30+
);

docker-compose.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ services:
77
container_name: server
88
volumes:
99
- ./src/server:/usr/src/app/
10+
depends_on:
11+
- mysql
12+
restart: on-failure
13+
environment:
14+
- MYSQL_HOST=mysql
15+
- MYSQL_DATABASE=http_gateway_grpc
16+
- MYSQL_PORT=3306
17+
- MYSQL_USER=root
18+
- MYSQL_PASSWORD=root
1019
networks:
1120
- bridge
1221
gateway:
@@ -20,7 +29,21 @@ services:
2029
- server
2130
restart: on-failure
2231
volumes:
23-
- ./src/client:/usr/src/app/
32+
- ./src/gateway:/usr/src/app/
33+
networks:
34+
- bridge
35+
mysql:
36+
image: mysql
37+
command: --default-authentication-plugin=mysql_native_password
38+
hostname: mysql
39+
environment:
40+
MYSQL_ROOT_PASSWORD: root
41+
container_name: mysql
42+
ports:
43+
- "3306:3306"
44+
volumes:
45+
- "./config/data/schemas.sql:/docker-entrypoint-initdb.d/1.sql"
46+
- "./config/data/data.sql:/docker-entrypoint-initdb.d/2.sql"
2447
networks:
2548
- bridge
2649
networks:

src/gateway/README.md

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package action
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"net/http"
7+
8+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/api/response"
9+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger"
10+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logging"
11+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/usecase"
12+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/domain"
13+
)
14+
15+
type CreateOrderAction struct {
16+
uc usecase.CreateOrderUseCase
17+
log logger.Logger
18+
}
19+
20+
func NewCreateOrderAction(uc usecase.CreateOrderUseCase, log logger.Logger) CreateOrderAction {
21+
return CreateOrderAction{
22+
uc: uc,
23+
log: log,
24+
}
25+
}
26+
27+
func (fova CreateOrderAction) Execute(w http.ResponseWriter, r *http.Request) {
28+
const logKey = "save_device_token"
29+
30+
body, err := ioutil.ReadAll(r.Body)
31+
if err != nil {
32+
http.Error(w, "Unable to read request body", http.StatusBadRequest)
33+
return
34+
}
35+
36+
var dt domain.OrderRequest
37+
err = json.Unmarshal(body, &dt)
38+
if err != nil {
39+
http.Error(w, "Unable to parse JSON data", http.StatusBadRequest)
40+
return
41+
}
42+
43+
output, err := fova.uc.Execute(r.Context(), dt)
44+
if err != nil {
45+
switch err {
46+
default:
47+
logging.NewError(
48+
fova.log,
49+
err,
50+
logKey,
51+
http.StatusInternalServerError,
52+
).Log("error saving device token")
53+
54+
response.NewError(err, http.StatusInternalServerError).Send(w)
55+
return
56+
}
57+
}
58+
logging.NewInfo(fova.log, logKey, http.StatusOK).Log("device token has been saved")
59+
60+
response.NewSuccess(output, http.StatusOK).Send(w)
61+
}

src/gateway/application/adapter/api/action/health_check.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ import "net/http"
55
func HealthCheck(w http.ResponseWriter, _ *http.Request) {
66
w.WriteHeader(http.StatusOK)
77
}
8-
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package middleware
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"net/http"
7+
"strings"
8+
"time"
9+
10+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logger"
11+
"github.com/jeferagudeloc/grpc-http-gateway/src/gateway/application/adapter/logging"
12+
"github.com/pkg/errors"
13+
"github.com/urfave/negroni"
14+
)
15+
16+
type Logger struct {
17+
log logger.Logger
18+
}
19+
20+
func NewLogger(log logger.Logger) Logger {
21+
return Logger{log: log}
22+
}
23+
24+
func (l Logger) Execute(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
25+
start := time.Now()
26+
27+
const (
28+
logKey = "logger_middleware"
29+
requestKey = "api_request"
30+
responseKey = "api_response"
31+
)
32+
33+
body, err := getRequestPayload(r)
34+
if err != nil {
35+
logging.NewError(
36+
l.log,
37+
err,
38+
logKey,
39+
http.StatusBadRequest,
40+
).Log("error when getting payload")
41+
42+
return
43+
}
44+
45+
l.log.WithFields(logger.Fields{
46+
"key": requestKey,
47+
"payload": body,
48+
"url": r.URL.Path,
49+
"http_method": r.Method,
50+
}).Infof("started handling request")
51+
52+
next.ServeHTTP(w, r)
53+
54+
end := time.Since(start).Seconds()
55+
res := w.(negroni.ResponseWriter)
56+
l.log.WithFields(logger.Fields{
57+
"key": responseKey,
58+
"url": r.URL.Path,
59+
"http_method": r.Method,
60+
"http_status": res.Status(),
61+
"response_time": end,
62+
}).Infof("completed handling request")
63+
}
64+
65+
func getRequestPayload(r *http.Request) (string, error) {
66+
if r.Body == nil {
67+
return "", errors.New("body not defined")
68+
}
69+
70+
payload, err := ioutil.ReadAll(r.Body)
71+
if err != nil {
72+
return "", errors.Wrap(err, "error read body")
73+
}
74+
75+
r.Body = ioutil.NopCloser(bytes.NewBuffer(payload))
76+
77+
return strings.TrimSpace(string(payload)), nil
78+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package response
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
var (
11+
ErrParameterInvalid = errors.New("parameter invalid")
12+
)
13+
14+
type Error struct {
15+
statusCode int
16+
Errors []string `json:"errors"`
17+
}
18+
19+
func NewError(err error, status int) *Error {
20+
return &Error{
21+
statusCode: status,
22+
Errors: []string{err.Error()},
23+
}
24+
}
25+
26+
func NewErrorMessage(messages []string, status int) *Error {
27+
return &Error{
28+
statusCode: status,
29+
Errors: messages,
30+
}
31+
}
32+
33+
func (e Error) Send(w http.ResponseWriter) error {
34+
w.Header().Set("Content-Type", "application/json")
35+
w.WriteHeader(e.statusCode)
36+
return json.NewEncoder(w).Encode(e)
37+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package response
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
)
7+
8+
type Success struct {
9+
statusCode int
10+
result interface{}
11+
}
12+
13+
func NewSuccess(result interface{}, status int) Success {
14+
return Success{
15+
statusCode: status,
16+
result: result,
17+
}
18+
}
19+
20+
func (r Success) Send(w http.ResponseWriter) error {
21+
w.Header().Set("Content-Type", "application/json")
22+
w.WriteHeader(r.statusCode)
23+
return json.NewEncoder(w).Encode(r.result)
24+
}

0 commit comments

Comments
 (0)