Skip to content

Commit

Permalink
Merge pull request #1 from gilperopiola/feature/logging
Browse files Browse the repository at this point in the history
Logging added. Much refactored
  • Loading branch information
gilperopiola authored Oct 5, 2023
2 parents cdb3d4d + 039e483 commit 3962665
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 43 deletions.
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Go's best practices and standards are followed most of the time, using technique
- [Prerequisites](#prerequisites)
- [Installation and Usage](#installation-and-usage)
- [Project Architecture](#project-architecture)
- [Request Flow Overview](#request-flow-overview)
- [Contributing and License](#contributing-and-license)

## Prerequisites
Expand Down Expand Up @@ -65,6 +66,56 @@ This will start the server and you'll hopefully be able to hit the endpoints.
| **pkg > transport** | Sets up routes and manages the transport layer. |
| **pkg > utils** | Houses utility functions used across the project. |
## Request Flow Overview
1. **Entry Point**:
All requests start at `router.go`, where the URL is matched.
2. **Token Validation**:
For private endpoints, the request is routed through `token_validation.go` for authentication.
3. **Endpoint Matching**:
The corresponding function in `transport_endpoints.go` is invoked based on the matched URL.
4. **Request Handling**:
This function then calls `HandleRequest` from `transport.go`.
5. **Request Typification & Validation**:
Inside `transport.go`, the appropriate function from `transport_requests.go` is called. This step involves typifying and validating the incoming request.
6. **Service Layer Invocation**:
Upon successful validation, `HandleRequest` invokes the corresponding method in `service.go`.
7. **Service Implementation**:
The actual implementation of these methods resides in `service_xxx.go`, organized by domain.
8. **Entity to Model Conversion**:
The service layer operates using **entities**. If needed, the `codec` is used to convert these entities into **models** before interacting with the `repository` layer.
9. **Repository Layer**:
Here, the matching `repository_xxx.go` file is invoked. This layer handles database operations and returns data or errors as required.
10. **Backtracking & Response Handling**:
The system then retraces its steps. If the repository layer returned a model, it's converted back to an entity in `service_xxx.go`. This file also handles any errors or continues execution if there are none.

11. **Finalizing Response**:

The process returns to `HandleRequest` in `transport.go`, where the service's response is processed. If there's an error, it's mapped using `errors_mapper.go`.
12. **Sending HTTP Response**:
Finally, the HTTP response is sent back to the client.
## Contributing and License
### Contributing
Expand Down
17 changes: 13 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/gilperopiola/go-rest-example/pkg/repository"
"github.com/gilperopiola/go-rest-example/pkg/service"
"github.com/gilperopiola/go-rest-example/pkg/transport"

"github.com/sirupsen/logrus"
)

func main() {
Expand All @@ -22,31 +24,38 @@ func main() {
// Load configuration settings
config = config.NewConfig()

// Initialize logger
logger = logrus.New()

// Initialize authentication module
auth = auth.NewAuth(config.JWT.SECRET, config.JWT.SESSION_DURATION_DAYS)

// Setup codec for encoding and decoding
codec = codec.NewCodec()

// Establish database connection
database = repository.NewDatabase(config.DATABASE)
database = repository.NewDatabase(config.DATABASE, logger)

// Initialize repository with the database connection
repository = repository.NewRepository(database)

// Setup the main service with dependencies
service = service.NewService(repository, auth, codec, config, service.ErrorsMapper{})
service = service.NewService(repository, auth, codec, config, service.NewErrorsMapper())

// Setup endpoints & transport layer with dependencies
endpoints = transport.NewTransport(service, codec, transport.ErrorsMapper{})
endpoints = transport.NewTransport(service, codec, transport.NewErrorsMapper(logger))

// Initialize the router with the endpoints
router = transport.NewRouter(endpoints, config, auth)
router = transport.NewRouter(endpoints, config, auth, logger)
)

// Defer closing open connections
defer database.Close()

// Set log format and level
logger.SetFormatter(&logrus.JSONFormatter{})
logger.SetLevel(logrus.InfoLevel)

// Start server
log.Println("About to run server on port " + config.PORT)

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
Expand Down Expand Up @@ -407,6 +409,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var (
defaultDatabasePort = "3306"
defaultDatabaseSchema = "go-rest-example-db"
defaultDatabasePurge = false
defaultDatabaseDebug = true
defaultDatabaseDebug = false

defaultJWTSecret = "a0#3ndl2"
defaultJWTSessionDurationDays = 14
Expand Down
6 changes: 6 additions & 0 deletions pkg/entities/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import (
"fmt"
)

// IMPORTANT!
//
// If you add a new error, make sure to add it to the errorsMapToHTTPCode map in pkg/transport/errors_mapper.go

var (

// Generic

ErrUnauthorized = errors.New("unauthorized")
ErrBindingRequest = errors.New("error binding request")
ErrAllFieldsRequired = errors.New("all fields required")
ErrNilError = errors.New("unexpected behavior, nil error")
ErrUnknown = errors.New("unknown")

// Signup - Login - Users in general

Expand Down
23 changes: 17 additions & 6 deletions pkg/repository/database.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
package repository

import (
"log"
"os"
"time"

"github.com/gilperopiola/go-rest-example/pkg/config"
"github.com/gilperopiola/go-rest-example/pkg/models"

_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"github.com/sirupsen/logrus"
)

type Database struct {
DB *gorm.DB
}

type DatabaseProvider interface {
Setup(config config.DatabaseConfig)
Setup(config config.DatabaseConfig, logger *logrus.Logger)
Purge()
Migrate()
Close()
}

func NewDatabase(config config.DatabaseConfig) Database {
func NewDatabase(config config.DatabaseConfig, logger *logrus.Logger) Database {
var database Database
database.Setup(config)
database.Setup(config, logger)
return database
}

/* ------------------- */

func (database *Database) Setup(config config.DatabaseConfig) {
func (database *Database) Setup(config config.DatabaseConfig, logger *logrus.Logger) {

// Create connection
var err error
if database.DB, err = gorm.Open(config.TYPE, config.GetConnectionString()); err != nil {
log.Fatalf("error connecting to database: %v", err)
logger.Fatalf("error connecting to database: %v", err)
os.Exit(1)
}

// Set connection pool limits
database.DB.DB().SetMaxIdleConns(10)
database.DB.DB().SetMaxOpenConns(100)
database.DB.DB().SetConnMaxLifetime(time.Hour)

// Flags
if config.DEBUG {
database.DB.LogMode(true)
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/repository/repository_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func buildNonDeletedQuery(query string, onlyNonDeleted bool) string {
// CreateUser creates a user on the database. Id, username and email are unique
func (r *Repository) CreateUser(user models.User) (models.User, error) {
if err := r.Database.DB.Create(&user).Error; err != nil {
return models.User{}, utils.JoinErrors(ErrCreatingUser, err)
return models.User{}, utils.JoinErrors(err, ErrCreatingUser)
}

return user, nil
Expand All @@ -30,7 +30,7 @@ func (r *Repository) CreateUser(user models.User) (models.User, error) {
// UpdateUser updates the user on the database, skipping fields that are empty
func (r *Repository) UpdateUser(user models.User) (models.User, error) {
if err := r.Database.DB.Model(&user).Update(&user).Error; err != nil {
return models.User{}, utils.JoinErrors(ErrUpdatingUser, err)
return models.User{}, utils.JoinErrors(err, ErrUpdatingUser)
}

return user, nil
Expand Down Expand Up @@ -58,9 +58,9 @@ func (r *Repository) GetUser(user models.User, onlyNonDeleted bool) (models.User
err := r.Database.DB.Where(query, user.ID, user.Username, user.Email).First(&user).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return models.User{}, utils.JoinErrors(ErrGettingUser, err)
return models.User{}, utils.JoinErrors(err, ErrGettingUser)
}
return models.User{}, utils.JoinErrors(ErrUnknown, err)
return models.User{}, utils.JoinErrors(err, ErrUnknown)
}

return user, nil
Expand All @@ -83,7 +83,7 @@ func (r *Repository) DeleteUser(id int) (user models.User, err error) {
// Then, mark the user as deleted and save it
user.Deleted = true
if _, err := r.UpdateUser(user); err != nil {
return models.User{}, utils.JoinErrors(ErrUpdatingUser, err)
return models.User{}, utils.JoinErrors(err, ErrUpdatingUser)
}

return user, nil
Expand Down
13 changes: 13 additions & 0 deletions pkg/service/errors_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ type errorsMapperInterface interface {

type ErrorsMapper struct{}

func NewErrorsMapper() ErrorsMapper {
return ErrorsMapper{}
}

func (e ErrorsMapper) Map(err error) error {

// If we're here we shouldn't have a nil error
if err == nil {
return entities.ErrNilError
}

// Signup, Login & Users
if errors.Is(err, repository.ErrCreatingUser) {
return utils.JoinErrors(entities.ErrCreatingUser, err)
Expand All @@ -25,6 +34,10 @@ func (e ErrorsMapper) Map(err error) error {
return utils.JoinErrors(entities.ErrUserNotFound, err)
}

if errors.Is(err, repository.ErrUserAlreadyDeleted) {
return utils.JoinErrors(entities.ErrUserNotFound, err)
}

if errors.Is(err, repository.ErrUnknown) {
return utils.JoinErrors(entities.ErrUserNotFound, err)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/service/service_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (s *Service) UpdateUser(updateUserRequest entities.UpdateUserRequest) (enti
func (s *Service) DeleteUser(deleteUserRequest entities.DeleteUserRequest) (entities.DeleteUserResponse, error) {

// Set the user's Deleted field to true
// This returns an error if the user is already deleted
userModel, err := s.Repository.DeleteUser(deleteUserRequest.ID)
if err != nil {
return entities.DeleteUserResponse{}, s.ErrorsMapper.Map(err)
Expand Down
Loading

0 comments on commit 3962665

Please sign in to comment.