package application
import (
"context"
"server/api/domain/model"
"server/api/infrastracture/persistence"
"server/config"
)
type application struct {
*ApplicationBundle
}
type ApplicationBundle struct {
ServerConfig *config.ServerConfig
Repository persistence.RepositoryInterface
BookRepository persistence.BookRepositoryInterface
}
type ApplicationInterface interface {
// Book
GetBook(ctx context.Context, uid string) (*model.Book, error)
GetBooks(ctx context.Context) ([]*model.Book, error)
CreateBook(ctx context.Context, req *CreateBookRequest) (*model.Book, error)
UpdateBook(ctx context.Context, req *UpdateBookRequest) (*model.Book, error)
DeleteBook(ctx context.Context, uid string) error
}
func NewApplication(bdl *ApplicationBundle) ApplicationInterface {
return &application{bdl}
}
package application
import (
"context"
"server/api/domain/model"
"golang.org/x/xerrors"
)
type CreateBookRequest struct {
Name string `json:"name"`
}
type UpdateBookRequest struct {
UUID string `json:"uuid"`
Name string `json:"name"`
}
func (a *application) GetBook(ctx context.Context, uid string) (*model.Book, error) {
return a.BookRepository.FindByUUID(ctx, a.Repository, uid)
}
func (a *application) GetBooks(ctx context.Context) ([]*model.Book, error) {
return a.BookRepository.FindAll(ctx, a.Repository)
}
func (a *application) CreateBook(ctx context.Context, req *CreateBookRequest) (*model.Book, error) {
book := &model.Book{
Name: req.Name,
}
if err := book.SetUUID(); err != nil {
return nil, err
}
return a.BookRepository.Create(ctx, a.Repository, book)
}
func (a *application) UpdateBook(ctx context.Context, req *UpdateBookRequest) (*model.Book, error) {
book, err := a.BookRepository.FindByUUID(ctx, a.Repository, req.UUID)
if err != nil {
return nil, err
}
if book == nil {
return nil, xerrors.New(model.NotFoundUUIDMsg)
}
book.UpdateBookName(req.Name)
return a.BookRepository.Update(ctx, a.Repository, book)
}
func (a *application) DeleteBook(ctx context.Context, uid string) error {
book, err := a.BookRepository.FindByUUID(ctx, a.Repository, uid)
if err != nil {
return err
}
if book == nil {
return xerrors.New(model.NotFoundUUIDMsg)
}
return a.BookRepository.Delete(ctx, a.Repository, book)
}
package model
import (
"time"
"gorm.io/gorm"
)
type Book struct {
ID int `json:"id" gorm:"primaryKey"`
UUID string `json:"uuid" gorm:"column:uuid"`
Name string `json:"name" gorm:"column:name"`
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"`
UpdatedAt time.Time `json:"updatedAt" gorm:"column:updated_at"`
DeletedAt gorm.DeletedAt `json:"deletedAt" gorm:"column:deleted_at"`
}
func (b *Book) SetUUID() error {
uid, err := GetUUID()
if err != nil {
return err
}
b.UUID = uid
return nil
}
func (b *Book) UpdateBookName(name string) {
b.Name = name
}
package model
import "github.com/google/uuid"
var fakeUUID string
func GetUUID() (string, error) {
if fakeUUID == "" {
uid, err := uuid.NewRandom()
if err != nil {
return "", err
}
return uid.String(), nil
}
return fakeUUID, nil
}
func SetFakeUUID(uid string) {
fakeUUID = uid
}
package handler
import (
"net/http"
"server/api/application"
"server/api/handler/request"
"github.com/labstack/echo/v4"
)
func (h *Handler) GetBook(ec echo.Context) error {
var req request.GetBookRequest
if err := ec.Bind(&req); err != nil {
return h.NewErrorResponse(ec, err)
}
ctx := h.GetCtx(ec)
res, err := h.Application.GetBook(ctx, req.UUID)
if err != nil {
return h.NewErrorResponse(ec, err)
}
return ec.JSON(http.StatusOK, res)
}
func (h *Handler) GetBooks(ec echo.Context) error {
ctx := h.GetCtx(ec)
res, err := h.Application.GetBooks(ctx)
if err != nil {
return h.NewErrorResponse(ec, err)
}
return ec.JSON(http.StatusOK, res)
}
func (h *Handler) CreateBook(ec echo.Context) error {
var req request.CreateBookRequest
if err := ec.Bind(&req); err != nil {
return h.NewErrorResponse(ec, err)
}
ctx := h.GetCtx(ec)
res, err := h.Application.CreateBook(ctx, &application.CreateBookRequest{
Name: req.Name,
})
if err != nil {
return h.NewErrorResponse(ec, err)
}
return ec.JSON(http.StatusCreated, res)
}
func (h *Handler) UpdateBook(ec echo.Context) error {
var req request.UpdateBookRequest
if err := ec.Bind(&req); err != nil {
return h.NewErrorResponse(ec, err)
}
ctx := h.GetCtx(ec)
res, err := h.Application.UpdateBook(ctx, &application.UpdateBookRequest{
Name: req.Name,
UUID: req.UUID,
})
if err != nil {
return h.NewErrorResponse(ec, err)
}
return ec.JSON(http.StatusOK, res)
}
func (h *Handler) DeleteBook(ec echo.Context) error {
var req request.DeleteBookRequest
if err := ec.Bind(&req); err != nil {
return h.NewErrorResponse(ec, err)
}
ctx := h.GetCtx(ec)
if err := h.Application.DeleteBook(ctx, req.UUID); err != nil {
return h.NewErrorResponse(ec, err)
}
return ec.NoContent(http.StatusNoContent)
}
package handler
import (
"net/http"
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
)
type ErrorResponse struct {
Errors []string `json:"errors"`
}
func (h *Handler) NewErrorResponse(ec echo.Context, err error) error {
var errres ErrorResponse
switch err := err.(type) {
case validator.ValidationErrors:
errs := err
for _, err := range errs {
errres.Errors = append(errres.Errors, h.I18n.EmbedT(err.Tag(), err.Field(), err.Param()))
}
default:
errres.Errors = append(errres.Errors, h.I18n.T(err.Error()))
}
return ec.JSON(http.StatusBadRequest, errres)
}
package handler
import (
"context"
"server/api/application"
"server/api/client/i18n"
"github.com/labstack/echo/v4"
)
type Handler struct {
Application application.ApplicationInterface
I18n i18n.I18nClientInterface
}
func NewHandler(
app application.ApplicationInterface,
i18n i18n.I18nClientInterface,
) *Handler {
return &Handler{
Application: app,
I18n: i18n,
}
}
func (h *Handler) AssignRoutes(e *echo.Echo) {
v1g := e.Group("v1")
{
v1bg := v1g.Group("/books")
{
v1bg.POST("", h.CreateBook)
v1bg.GET("", h.GetBooks)
v1bg.GET("/:uuid", h.GetBook)
v1bg.PUT("/:uuid", h.UpdateBook)
v1bg.DELETE("/:uuid", h.DeleteBook)
}
}
}
func (h *Handler) GetCtx(ec echo.Context) context.Context {
return ec.Request().Context()
}
package persistence
import (
"context"
"errors"
"server/api/domain/model"
"gorm.io/gorm"
)
type BookRepositoryInterface interface {
FindAll(ctx context.Context, ri RepositoryInterface) ([]*model.Book, error)
FindByUUID(context.Context, RepositoryInterface, string) (*model.Book, error)
Create(context.Context, RepositoryInterface, *model.Book) (*model.Book, error)
Update(context.Context, RepositoryInterface, *model.Book) (*model.Book, error)
Delete(context.Context, RepositoryInterface, *model.Book) error
}
type bookRepository struct{}
func NewBookRepository() BookRepositoryInterface {
return &bookRepository{}
}
func (br *bookRepository) FindAll(ctx context.Context, ri RepositoryInterface) ([]*model.Book, error) {
db, err := getDBConnection(ri)
if err != nil {
return nil, err
}
var book []*model.Book
if err := db.Find(&book).Error; err != nil {
return nil, err
}
return book, nil
}
func (br *bookRepository) FindByUUID(ctx context.Context, ri RepositoryInterface, uid string) (*model.Book, error) {
db, err := getDBConnection(ri)
if err != nil {
return nil, err
}
var book model.Book
if err := db.First(&book, "uuid = ?", uid).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &book, nil
}
func (br *bookRepository) Create(ctx context.Context, ri RepositoryInterface, book *model.Book) (*model.Book, error) {
db, err := getDBConnection(ri)
if err != nil {
return nil, err
}
if err := db.Create(book).Error; err != nil {
return nil, err
}
return book, nil
}
func (br *bookRepository) Update(ctx context.Context, ri RepositoryInterface, book *model.Book) (*model.Book, error) {
db, err := getDBConnection(ri)
if err != nil {
return nil, err
}
if err := db.Where(book.ID).Updates(book).Error; err != nil {
return nil, err
}
return book, nil
}
func (br *bookRepository) Delete(ctx context.Context, ri RepositoryInterface, book *model.Book) error {
db, err := getDBConnection(ri)
if err != nil {
return err
}
return db.Delete(book).Error
}
package persistence
import (
"server/config"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type RepositoryInterface interface {
GetDB() *gorm.DB
}
type persistenceInfo struct {
db *gorm.DB
}
func Connect(cfg *config.ServerConfig) (RepositoryInterface, error) {
conn, err := gorm.Open(mysql.Open(cfg.DataSource), &gorm.Config{})
if err != nil {
return nil, err
}
return SetConnection(conn), nil
}
func SetConnection(db *gorm.DB) RepositoryInterface {
return &persistenceInfo{db: db}
}
func (re *persistenceInfo) GetDB() *gorm.DB {
return re.db
}
func getDBConnection(ri RepositoryInterface) (*gorm.DB, error) {
return ri.GetDB(), nil
}
package config
import "github.com/caarlos0/env"
type ServerConfig struct {
Port string `json:"PORT,omitempty" env:"PORT,required"`
DriverName string `json:"DRIVER,omitempty" env:"DRIVER,required"`
DataSource string `json:"DATASOURCE,omitempty" env:"DATASOURCE,required"`
}
func LoadEnvConfig() (*ServerConfig, error) {
var cfg ServerConfig
if err := env.Parse(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}