Skip to content

Commit

Permalink
Added HTTP gateway to run with gRPC
Browse files Browse the repository at this point in the history
 > service_gobank.proto // main.go
  • Loading branch information
the-eduardo committed Jul 19, 2024
1 parent a51250e commit 6c1d35f
Show file tree
Hide file tree
Showing 22 changed files with 1,167 additions and 51 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ proto:
rm -f pb/*.go
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative \
proto/*.proto

evans:
evans --port 9090 -r repl
# For server building only
dockerbuild:
docker run --name gobank-main --network bank-network -p 8080:8080 -e GIN_MODE=release -e DB_SOURCE="postgresql://root:secret@gobank_postgres:5432/gobank_db?sslmode=disable" gobank:latest

.PHONY: postgres createdb dropdb migrateup migrateup1 migratedown migratedown1 sqlc test server mock mockery dockerbuild dbdocs dbschema proto
.PHONY: postgres createdb dropdb migrateup migrateup1 migratedown migratedown1 sqlc test server mock mockery dockerbuild dbdocs dbschema proto evans
3 changes: 2 additions & 1 deletion app.env
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

# Environment variables
DB_SOURCE=postgresql://root:secret@localhost:5432/gobank_db
SERVER_ADDRESS=0.0.0.0:8080
HTTP_SERVER_ADDRESS=0.0.0.0:8080
GRPC_SERVER_ADDRESS=0.0.0.0:9090
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
ACCESS_TOKEN_DURATION=20m
REFRESH_TOKEN_DURATION=24h
17 changes: 17 additions & 0 deletions gapi/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gapi

import (
db "github.com/the-eduardo/Go-Bank/db/sqlc"
"github.com/the-eduardo/Go-Bank/pb"
"google.golang.org/protobuf/types/known/timestamppb"
)

func convertUser(user db.User) *pb.User {
return &pb.User{
Username: user.Username,
FullName: user.FullName,
Email: user.Email,
PasswordChangedAt: timestamppb.New(user.PasswordChangedAt.Time),
CreatedAt: timestamppb.New(user.CreatedAt.Time),
}
}
43 changes: 43 additions & 0 deletions gapi/rpc_create_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gapi

import (
"context"
"errors"
"github.com/jackc/pgx/v5/pgconn"
db "github.com/the-eduardo/Go-Bank/db/sqlc"
"github.com/the-eduardo/Go-Bank/pb"
"github.com/the-eduardo/Go-Bank/util"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (server *Server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
hashedPassword, err := util.HashPassword(req.GetPassword())
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to hash password: %s", err)
}
arg := db.CreateUserParams{
Username: req.GetUsername(),
HashedPassword: hashedPassword,
FullName: req.GetFullName(),
Email: req.GetEmail(),
}
user, err := server.store.CreateUser(ctx, arg)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case "23505":
return nil, status.Errorf(codes.AlreadyExists, "user already exists: %s", err)
case "23503":
return nil, status.Errorf(codes.Unavailable, "foreign key violation: %s", err)
}
}
return nil, status.Errorf(codes.Internal, "failed to create user: %s", err)

}
resp := &pb.CreateUserResponse{
User: convertUser(user),
}
return resp, nil
}
73 changes: 73 additions & 0 deletions gapi/rpc_login_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package gapi

import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/the-eduardo/Go-Bank/api"
db "github.com/the-eduardo/Go-Bank/db/sqlc"
"github.com/the-eduardo/Go-Bank/pb"
"github.com/the-eduardo/Go-Bank/util"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)

func (server *Server) LoginUser(ctx context.Context, req *pb.LoginUserRequest) (*pb.LoginUserResponse, error) {

user, err := server.store.GetUser(ctx, req.GetUsername())
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, status.Errorf(codes.NotFound, "user not found")
}
return nil, status.Errorf(codes.Internal, "failed to find user")
}
err = util.CheckPassword(user.HashedPassword, req.Password)
if err != nil {
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return nil, status.Errorf(codes.Unauthenticated, "invalid password")
}
return nil, status.Errorf(codes.Internal, "failed to verify password")
}
accessToken, accessPayload, err := server.tokenMaker.CreateToken(
user.Username,
server.config.AccessTokenDuration,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "cannot create access token")
}
refreshToken, refreshPayload, err := server.tokenMaker.CreateToken(
user.Username,
server.config.RefreshTokenDuration)
if err != nil {
return nil, status.Errorf(codes.Internal, "cannot create refresh token")
}
fixedRefreshPayload, err := api.ConvertGoogleUUIDToPGTypeUUID(refreshPayload.ID)
if err != nil {
return nil, status.Errorf(codes.Internal, "cannot convert refresh payload")
}
fixedRefreshPayloadExpiresAt := api.TimeToPGTimestamptz(refreshPayload.ExpiredAt)
_, err = server.store.CreateSession(ctx, db.CreateSessionParams{
ID: fixedRefreshPayload,
Username: user.Username,
RefreshToken: refreshToken,
UserAgent: "",
ClientIp: "",
IsBlocked: false,
ExpiresAt: fixedRefreshPayloadExpiresAt,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "cannot create session")
}

resp := &pb.LoginUserResponse{
User: convertUser(user),
SessionId: refreshPayload.ID.String(),
AccessToken: accessToken,
RefreshToken: refreshToken,
AccessTokenExpiresAt: timestamppb.New(accessPayload.ExpiredAt),
RefreshTokenExpiresAt: timestamppb.New(refreshPayload.ExpiredAt),
}
return resp, nil
}
33 changes: 33 additions & 0 deletions gapi/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package gapi

import (
"fmt"
db "github.com/the-eduardo/Go-Bank/db/sqlc"
"github.com/the-eduardo/Go-Bank/pb"
"github.com/the-eduardo/Go-Bank/token"
"github.com/the-eduardo/Go-Bank/util"
)

// Server serves gRPC requests
type Server struct {
pb.UnimplementedGoBankServer
config util.Config
tokenMaker token.Maker
store db.Store
}

// NewServer creates a new gRPC server
func NewServer(config util.Config, store db.Store) (*Server, error) {
tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey)
if err != nil {
return nil, fmt.Errorf("cannot create token maker: %w", err)
}

server := &Server{
config: config,
store: store,
tokenMaker: tokenMaker,
}
return server, nil

}
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.22.0
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
github.com/jackc/pgx/v5 v5.6.0
github.com/o1egl/paseto/v2 v2.1.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.25.0
google.golang.org/grpc v1.62.1
google.golang.org/grpc v1.65.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.4.0
google.golang.org/protobuf v1.34.2
)

Expand All @@ -28,7 +30,6 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
Expand Down Expand Up @@ -60,7 +61,8 @@ require (
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
26 changes: 16 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down Expand Up @@ -145,12 +143,20 @@ golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.4.0 h1:9SxA29VM43MF5Z9dQu694wmY5t8E/Gxr7s+RSxiIDmc=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.4.0/go.mod h1:yZOK5zhQMiALmuweVdIVoQPa6eIJyXn2B9g5dJDhqX4=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
76 changes: 75 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ package main

import (
"context"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/the-eduardo/Go-Bank/api"
db "github.com/the-eduardo/Go-Bank/db/sqlc"
"github.com/the-eduardo/Go-Bank/gapi"
"github.com/the-eduardo/Go-Bank/pb"
"github.com/the-eduardo/Go-Bank/util"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"google.golang.org/protobuf/encoding/protojson"
"log"
"net"
"net/http"
)

func main() {
Expand All @@ -23,11 +31,77 @@ func main() {
defer conn.Close()

store := db.NewStore(conn)
go runGatewayServer(config, store)
runGrpcServer(config, store)
}

func runGrpcServer(config util.Config, store db.Store) {
server, err := gapi.NewServer(config, store)
if err != nil {
log.Fatal("cannot create grpc server:", err)
}

grpcServer := grpc.NewServer()
pb.RegisterGoBankServer(grpcServer, server)
reflection.Register(grpcServer)

listener, err := net.Listen("tcp", config.GRPCServerAddress)
if err != nil {
log.Fatal("cannot create grpc server listener:", err)
}
log.Printf("server listening at %s", config.GRPCServerAddress)
err = grpcServer.Serve(listener)
if err != nil {
log.Fatal("cannot start grpc server:", err)
}
}

func runGatewayServer(config util.Config, store db.Store) {
server, err := gapi.NewServer(config, store)
if err != nil {
log.Fatal("cannot create grpc server:", err)
}

// jsonOption enables snake_case on json responses
jsonOption := runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
})

grpcMux := runtime.NewServeMux(jsonOption)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

err = pb.RegisterGoBankHandlerServer(ctx, grpcMux, server)
if err != nil {
log.Fatal("cannot register gateway server:", err)
}

mux := http.NewServeMux()
mux.Handle("/", grpcMux)

listener, err := net.Listen("tcp", config.HTTPServerAddress)
if err != nil {
log.Fatal("cannot create HTTP server listener:", err)
}
log.Printf("HTTP server listening at %s", config.HTTPServerAddress)
err = http.Serve(listener, mux)
if err != nil {
log.Fatal("cannot start HTTP gateway server:", err)
}
}

func runGinServer(config util.Config, store db.Store) {
server, err := api.NewServer(config, store)
if err != nil {
log.Fatal("cannot create server:", err)
}
err = server.Start(config.ServerAddress)
err = server.Start(config.HTTPServerAddress)
if err != nil {
log.Fatal("cannot start server:", err)
}
Expand Down
Loading

0 comments on commit 6c1d35f

Please sign in to comment.