Skip to content
Open
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
75 changes: 75 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.0.8] - 2023-10-17 (CHB-11)

### Added

- Added CORS permissions so local Angular can connect to local Go by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.7] - 2023-10-17 (CHB-9)

### Added

- Added new endpoint to get full list of chatrooms by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.6] - 2023-10-17 (CHB-7)

### Added

- Added new endpoints to validate login details and return a JWT token by [Arturo Vial](https://github.com/ratolibre1)
- Added Auth module by [Arturo Vial](https://github.com/ratolibre1)
- Added functions to find users by email, hash and validate passwords by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.5] - 2023-10-16 (CHB-6)

### Added

- Added new endpoints to enter and leave chatrooms by [Arturo Vial](https://github.com/ratolibre1)
- Added Chatrooms and Users modules by [Arturo Vial](https://github.com/ratolibre1)

### Changed

- Cleaned some English errors in original project by [Arturo Vial](https://github.com/ratolibre1)

### Removed

- Removed some calls to original User package (to be removed in the future) by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.4] - 2023-10-15 (CHB-3)

### Added

- Sending a command-like message gets its own workflow instead of storing it by [Arturo Vial](https://github.com/ratolibre1)

### Changed

- Renamed the module to "chat-hex" by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.3] - 2023-10-15 (CHB-2)

### Added

- Added new endpoint to list messages from a specific chatroom by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.2] - 2023-10-14 (CHB-1)

### Added

- Added Messages folders and endpoint to send a message by [Arturo Vial](https://github.com/ratolibre1)

## [0.0.1] - 2023-10-14

### Added

- Hexagonal Architecture scaffolding forked from [Github](https://github.com/favians/go-hexagonal) by [Arturo Vial](https://github.com/ratolibre1)

### Changed

- Cleaned some stuff, ensured connection with DB by [Arturo Vial](https://github.com/ratolibre1)
63 changes: 4 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,11 @@
# Sample Golang API Server

Sample REST API build using echo server.

The code implementation was inspired by port and adapter pattern or known as [hexagonal](blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example):

- **Business**<br/>Contains all the logic in domain business. Also called this as a service. All the interface of repository needed and the implementation of the service itself will be put here.
- **Modules**<br/>Contains implementation of interfaces that defined at the business (also called as server-side adapters in hexagonal's term)
- **API**<br/>API http handler or controller (also called user-side adapters in hexagonal's term)

# Data initialization

To describe about how port and adapter interaction (separation concerned), this example will have two databases supported. There are MySQL and MongoDB.

MongoDB will become a default databaese in this example. If you want to change into MySQL, update the configuration inside
[config.yaml](https://raw.githubusercontent.com/muhsinshodiq/golang-sample-api/master/config/config.yaml) file.

### MongoDB

Please execute script below to create a new collection called `items` including the index needed
Please execute script below to create the collections required to run the app

```mongodb
db.createCollection('items');
db.items.createIndex({"tags": 1});
db.items.createIndex({"modified_at": 1, "_id": 1});
```

### MySQL

Please execute script below to create `item` and `item_tag` table in your database

```sql
CREATE TABLE `item` (
`id` varchar(24) NOT NULL DEFAULT '',
`name` text NOT NULL,
`description` text NOT NULL,
`created_at` datetime NOT NULL,
`created_by` varchar(50) NOT NULL DEFAULT '',
`modified_at` datetime NOT NULL,
`modified_by` varchar(50) NOT NULL DEFAULT '',
`version` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
KEY `modified_at` (`modified_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `item_tag` (
`item_id` varchar(24) NOT NULL DEFAULT '',
`tag` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`item_id`,`tag`),
KEY `tag` (`tag`),
CONSTRAINT `item_tag_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `item` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
db.createCollection('users');
db.createCollection('messages');
db.createCollection('chatrooms');
```

# How To Run Server
Expand All @@ -59,14 +15,3 @@ Just execute code below in your console
```console
./run.sh
```

# How To Consume The API

There are 4 availables API that ready to use:

- GET `/v1/items/:id`
- GET `/v1/items/[tag-name]`
- POST `/v1/items`
- PUT `/v1/items`

To make it easier please download [Insomnia Core](https://insomnia.rest) app and import [this collection](https://raw.githubusercontent.com/muhsinshodiq/golang-sample-api/master/insomnia.json).
13 changes: 8 additions & 5 deletions api/common/error_business_response.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package common

import (
"go-hexagonal/business"
"chat-hex/business"
"net/http"
)

Expand All @@ -12,6 +12,7 @@ const (
errHasBeenModified errorBusinessResponseCode = "data_has_been modified"
errNotFound errorBusinessResponseCode = "data_not_found"
errInvalidSpec errorBusinessResponseCode = "invalid_spec"
ErrInvalidCommand errorBusinessResponseCode = "invalid_command"
)

//BusinessResponse default payload response
Expand All @@ -21,7 +22,7 @@ type BusinessResponse struct {
Data interface{} `json:"data"`
}

//NewErrorBusinessResponse Response return choosen http status like 400 bad request 422 unprocessable entity, ETC, based on responseCode
//NewErrorBusinessResponse Response return chosen http status like 400 bad request 422 unprocessable entity, ETC, based on responseCode
func NewErrorBusinessResponse(err error) (int, BusinessResponse) {
return errorMapping(err)
}
Expand All @@ -35,8 +36,10 @@ func errorMapping(err error) (int, BusinessResponse) {
return newNotFoundResponse()
case business.ErrInvalidSpec:
return newValidationResponse(err.Error())
case business.ErrInvalidCommand:
return newValidationResponse(err.Error())
case business.ErrHasBeenModified:
return newHasBeedModifiedResponse()
return newHasBeenModifiedResponse()
}
}

Expand All @@ -49,8 +52,8 @@ func newInternalServerErrorResponse() (int, BusinessResponse) {
}
}

//newHasBeedModifiedResponse failed to validate request payload
func newHasBeedModifiedResponse() (int, BusinessResponse) {
//newHasBeenModifiedResponse failed to validate request payload
func newHasBeenModifiedResponse() (int, BusinessResponse) {
return http.StatusBadRequest, BusinessResponse{
errHasBeenModified,
"Data has been modified",
Expand Down
1 change: 0 additions & 1 deletion api/insomnia.json

This file was deleted.

45 changes: 34 additions & 11 deletions api/router.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
package api

import (
"go-hexagonal/api/v1/user"
"chat-hex/api/v1/auth"
"chat-hex/api/v1/chatrooms"
"chat-hex/api/v1/messages"
"chat-hex/api/v1/users"
"chat-hex/middlewares"

echo "github.com/labstack/echo/v4"
)

//RegisterPath Register all V1 API with routing path
func RegisterPath(e *echo.Echo, userController *user.Controller) {
if userController == nil {
panic("user controller cannot be nil")
//RegisterPaths Register all V1 API with routing path
func RegisterPaths(e *echo.Echo, authController *auth.Controller, usersController *users.Controller, chatroomsController *chatrooms.Controller, messagesController *messages.Controller) {
if authController == nil {
panic("auth controller cannot be nil")
}

//user
userV1 := e.Group("v1/users")
userV1.GET("/:id", userController.FindUserByID)
userV1.GET("", userController.FindAllUserWithPagination)
userV1.POST("", userController.InsertUser)
userV1.PUT("/:id", userController.UpdateUser)
if usersController == nil {
panic("users controller cannot be nil")
}

if messagesController == nil {
panic("messages controller cannot be nil")
}

//auth
authV1 := e.Group("v1/auth")
authV1.POST("/token", authController.GenerateToken)

//users
usersV1 := e.Group("v1/users")
usersV1.PATCH("/enter-chatroom", middlewares.Auth(usersController.EnterChatroom, authController.AuthError))
usersV1.PATCH("/leave-chatroom", middlewares.Auth(usersController.LeaveChatroom, authController.AuthError))

//chatrooms
chatroomsV1 := e.Group("v1/chatrooms")
chatroomsV1.GET("", middlewares.Auth(chatroomsController.GetChatrooms, authController.AuthError))

//messages
messagesV1 := e.Group("v1/messages")
messagesV1.POST("", middlewares.Auth(messagesController.InsertMessage, authController.AuthError))
messagesV1.GET("/:chatroom", middlewares.Auth(messagesController.GetMessagesByChatroom, authController.AuthError))

//health check
e.GET("/health", func(c echo.Context) error {
Expand Down
52 changes: 52 additions & 0 deletions api/v1/auth/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package auth

import (
"chat-hex/api/common"
"chat-hex/api/v1/auth/requests"
"chat-hex/business/auth"
"chat-hex/business/users"

"github.com/labstack/echo/v4"
)

type Controller struct {
service auth.Service
usersService users.Service
}

func NewController(service auth.Service, usersService users.Service) *Controller {
return &Controller{
service,
usersService,
}
}

func (controller *Controller) GenerateToken(c echo.Context) error {
generateTokenRequest := new(requests.GenerateTokenRequest)

err := c.Bind(generateTokenRequest)
if err != nil {
return c.JSON(common.NewBadRequestResponse())
}

user, err := controller.usersService.FindUserByEmail(generateTokenRequest.Email)
if err != nil {
return c.JSON(common.NewErrorBusinessResponse(err))
}

err = controller.usersService.CheckPassword(user.Password, generateTokenRequest.Password)
if err != nil {
return c.JSON(common.NewErrorBusinessResponse(err))
}

response, err := controller.service.GenerateJWT(*generateTokenRequest.ToGenerateTokenSpec())
if err != nil {
return c.JSON(common.NewErrorBusinessResponse(err))
}

return c.JSON(common.NewSuccessResponse(response))
}

func (controller *Controller) AuthError(c echo.Context) error {
return c.JSON(common.NewForbiddenResponse())
}
17 changes: 17 additions & 0 deletions api/v1/auth/requests/generate_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package requests

import "chat-hex/business/auth"

type GenerateTokenRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}

func (req *GenerateTokenRequest) ToGenerateTokenSpec() *auth.GenerateTokenSpec {
var generateTokenSpec auth.GenerateTokenSpec

generateTokenSpec.Email = req.Email
generateTokenSpec.Password = req.Password

return &generateTokenSpec
}
30 changes: 30 additions & 0 deletions api/v1/chatrooms/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package chatrooms

import (
"chat-hex/api/common"
"chat-hex/api/v1/chatrooms/responses"
"chat-hex/business/chatrooms"

"github.com/labstack/echo/v4"
)

type Controller struct {
service chatrooms.Service
}

func NewController(service chatrooms.Service) *Controller {
return &Controller{
service,
}
}

func (controller *Controller) GetChatrooms(c echo.Context) error {
chatrooms, err := controller.service.GetChatrooms()
if err != nil {
return c.JSON(common.NewErrorBusinessResponse(err))
}

response := responses.NewGetChatroomsResponse(chatrooms)

return c.JSON(common.NewSuccessResponse(response))
}
Loading