From 8966037024df2118e98d3eaacd3d51180791d49e Mon Sep 17 00:00:00 2001 From: "aditya.siregar" Date: Sun, 28 Jul 2024 13:00:01 +0700 Subject: [PATCH] Add License --- internal/entity/license.go | 78 ++++++++++ internal/handlers/http/license/license.go | 156 +++++++++++++++++++ internal/handlers/request/license.go | 51 ++++++ internal/handlers/response/license.go | 46 ++++++ internal/repository/license/license.go | 66 ++++++++ internal/repository/repository.go | 10 ++ internal/routes/routes.go | 2 + internal/services/license/license.go | 71 +++++++++ internal/services/service.go | 12 +- migrations/000011_add-license-table.down.sql | 1 + migrations/000011_add-license-table.up.sql | 14 ++ 11 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 internal/entity/license.go create mode 100644 internal/handlers/http/license/license.go create mode 100644 internal/handlers/request/license.go create mode 100644 internal/handlers/response/license.go create mode 100644 internal/repository/license/license.go create mode 100644 internal/services/license/license.go create mode 100644 migrations/000011_add-license-table.down.sql create mode 100644 migrations/000011_add-license-table.up.sql diff --git a/internal/entity/license.go b/internal/entity/license.go new file mode 100644 index 0000000..e7301b0 --- /dev/null +++ b/internal/entity/license.go @@ -0,0 +1,78 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type License struct { + ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + PartnerID int64 `gorm:"type:bigint;not null"` + Name string `gorm:"type:varchar(255);not null"` + StartDate time.Time `gorm:"type:date;not null"` + EndDate time.Time `gorm:"type:date;not null"` + RenewalDate *time.Time `gorm:"type:date"` + SerialNumber string `gorm:"type:varchar(255);unique;not null"` + CreatedBy int64 `gorm:"type:bigint;not null"` + UpdatedBy int64 `gorm:"type:bigint;not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` +} + +func (License) TableName() string { + return "licenses" +} + +type LicenseDB struct { + License +} + +func (l *License) ToLicenseDB() *LicenseDB { + return &LicenseDB{ + License: *l, + } +} + +func (LicenseDB) TableName() string { + return "licenses" +} + +func (e *LicenseDB) ToLicense() *License { + return &License{ + ID: e.ID, + PartnerID: e.PartnerID, + Name: e.Name, + StartDate: e.StartDate, + EndDate: e.EndDate, + RenewalDate: e.RenewalDate, + SerialNumber: e.SerialNumber, + CreatedBy: e.CreatedBy, + UpdatedBy: e.UpdatedBy, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + } +} + +func (o *LicenseDB) ToUpdatedLicense(updatedBy int64, req License) { + o.UpdatedBy = updatedBy + + if req.Name != "" { + o.Name = req.Name + } + + if !req.StartDate.IsZero() { + o.StartDate = req.StartDate + } + + if !req.EndDate.IsZero() { + o.EndDate = req.EndDate + } + + if req.RenewalDate != nil { + o.RenewalDate = req.RenewalDate + } + + if req.SerialNumber != "" { + o.SerialNumber = req.SerialNumber + } +} diff --git a/internal/handlers/http/license/license.go b/internal/handlers/http/license/license.go new file mode 100644 index 0000000..505169e --- /dev/null +++ b/internal/handlers/http/license/license.go @@ -0,0 +1,156 @@ +package license + +import ( + "furtuna-be/internal/common/errors" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "net/http" +) + +type Handler struct { + service services.License +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/license") + + route.POST("/", jwt, h.Create) + route.GET("/", jwt, h.GetAll) + route.PUT("/:id", jwt, h.Update) + route.GET("/:id", jwt, h.GetByID) +} + +func NewHandler(service services.License) *Handler { + return &Handler{ + service: service, + } +} + +// Create handles the creation of a new license. +func (h *Handler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.License + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + licenseEntity, err := req.ToEntity() + if err != nil { + response.ErrorWrapper(c, err) + return + } + + res, err := h.service.Create(ctx, licenseEntity) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + var licenseResponse response.License + licenseResponse.FromEntity(res) + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: licenseResponse, + }) +} + +// Update handles the update of an existing license. +func (h *Handler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + licenseID := c.Param("id") + + var req request.License + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + licenseEntity, err := req.ToEntity() + if err != nil { + response.ErrorWrapper(c, err) + return + } + + updatedLicense, err := h.service.Update(ctx, licenseID, licenseEntity) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + var licenseResponse response.License + licenseResponse.FromEntity(updatedLicense) + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: licenseResponse, + }) +} + +// GetAll retrieves a list of licenses. +func (h *Handler) GetAll(c *gin.Context) { + var req request.LicenseParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + licenses, total, err := h.service.GetAll(c.Request.Context(), req.Limit, req.Offset) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + licenseResponses := response.FromEntityList(licenses) + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.LicenseList{Licenses: licenseResponses, Total: total, Limit: req.Limit, Offset: req.Offset}, + }) +} + +// GetByID retrieves details of a specific license by ID. +func (h *Handler) GetByID(c *gin.Context) { + licenseID := c.Param("id") + + res, err := h.service.GetByID(c.Request.Context(), licenseID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + var licenseResponse response.License + licenseResponse.FromEntity(res) + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: licenseResponse, + }) +} diff --git a/internal/handlers/request/license.go b/internal/handlers/request/license.go new file mode 100644 index 0000000..499522c --- /dev/null +++ b/internal/handlers/request/license.go @@ -0,0 +1,51 @@ +package request + +import ( + "furtuna-be/internal/entity" + "time" + + "github.com/google/uuid" +) + +type License struct { + Name string `json:"name" validate:"required"` + StartDate string `json:"start_date" validate:"required"` + EndDate string `json:"end_date" validate:"required"` + RenewalDate string `json:"renewal_date"` + SerialNumber string `json:"serial_number" validate:"required"` + PartnerID int64 `json:"partner_id" validate:"required"` +} + +type LicenseParam struct { + Limit int `form:"limit,default=10"` + Offset int `form:"offset,default=0"` +} + +func (r *License) ToEntity() (*entity.License, error) { + startDate, err := time.Parse("2006-01-02", r.StartDate) + if err != nil { + return nil, err + } + endDate, err := time.Parse("2006-01-02", r.EndDate) + if err != nil { + return nil, err + } + var renewalDate *time.Time + if r.RenewalDate != "" { + parsedRenewalDate, err := time.Parse("2006-01-02", r.RenewalDate) + if err != nil { + return nil, err + } + renewalDate = &parsedRenewalDate + } + + return &entity.License{ + ID: uuid.New(), + Name: r.Name, + StartDate: startDate, + EndDate: endDate, + RenewalDate: renewalDate, + SerialNumber: r.SerialNumber, + PartnerID: r.PartnerID, + }, nil +} diff --git a/internal/handlers/response/license.go b/internal/handlers/response/license.go new file mode 100644 index 0000000..4977991 --- /dev/null +++ b/internal/handlers/response/license.go @@ -0,0 +1,46 @@ +package response + +import ( + "furtuna-be/internal/entity" +) + +type License struct { + ID string `json:"id"` + Name string `json:"name"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + RenewalDate string `json:"renewal_date,omitempty"` + SerialNumber string `json:"serial_number"` + PartnerID int64 `json:"partner_id"` + CreatedBy int64 `json:"created_by"` + UpdatedBy int64 `json:"updated_by"` +} + +type LicenseList struct { + Licenses []License `json:"licenses"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} + +func (r *License) FromEntity(e *entity.License) { + r.ID = e.ID.String() + r.Name = e.Name + r.StartDate = e.StartDate.Format("2006-01-02") + r.EndDate = e.EndDate.Format("2006-01-02") + if e.RenewalDate != nil { + r.RenewalDate = e.RenewalDate.Format("2006-01-02") + } + r.SerialNumber = e.SerialNumber + r.PartnerID = e.PartnerID + r.CreatedBy = e.CreatedBy + r.UpdatedBy = e.UpdatedBy +} + +func FromEntityList(entities []*entity.License) []License { + licenses := make([]License, len(entities)) + for i, e := range entities { + licenses[i].FromEntity(e) + } + return licenses +} diff --git a/internal/repository/license/license.go b/internal/repository/license/license.go new file mode 100644 index 0000000..a4e568b --- /dev/null +++ b/internal/repository/license/license.go @@ -0,0 +1,66 @@ +package license + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + "github.com/google/uuid" + "go.uber.org/zap" + "gorm.io/gorm" +) + +type LicenseRepository struct { + db *gorm.DB +} + +func NewLicenseRepository(db *gorm.DB) *LicenseRepository { + return &LicenseRepository{ + db: db, + } +} + +func (r *LicenseRepository) Create(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error) { + if err := r.db.WithContext(ctx).Create(license).Error; err != nil { + logger.ContextLogger(ctx).Error("error when creating license", zap.Error(err)) + return nil, err + } + return license, nil +} + +func (r *LicenseRepository) Update(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error) { + if err := r.db.WithContext(ctx).Save(license).Error; err != nil { + logger.ContextLogger(ctx).Error("error when updating license", zap.Error(err)) + return nil, err + } + return license, nil +} + +func (r *LicenseRepository) FindByID(ctx context.Context, id string) (*entity.LicenseDB, error) { + licenseID, err := uuid.Parse(id) + if err != nil { + return nil, err + } + + license := new(entity.LicenseDB) + if err := r.db.WithContext(ctx).First(license, licenseID).Error; err != nil { + logger.ContextLogger(ctx).Error("error when finding license by ID", zap.Error(err)) + return nil, err + } + return license, nil +} + +func (r *LicenseRepository) GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error) { + var licenses []*entity.License + var total int64 + + if err := r.db.WithContext(ctx). + Limit(limit). + Offset(offset). + Find(&licenses). + Count(&total). + Error; err != nil { + return nil, 0, err + } + + return licenses, total, nil +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 18ce638..6da5848 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -5,6 +5,7 @@ import ( "database/sql" "furtuna-be/internal/repository/branches" "furtuna-be/internal/repository/brevo" + "furtuna-be/internal/repository/license" mdtrns "furtuna-be/internal/repository/midtrans" "furtuna-be/internal/repository/orders" "furtuna-be/internal/repository/oss" @@ -43,6 +44,7 @@ type RepoManagerImpl struct { Midtrans Midtrans Payment Payment EmailService EmailService + License License } func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { @@ -63,6 +65,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { Midtrans: mdtrns.New(&cfg.Midtrans), Payment: payment.NewPaymentRepository(db), EmailService: brevo.New(&cfg.Brevo), + License: license.NewLicenseRepository(db), } } @@ -183,3 +186,10 @@ type Payment interface { type EmailService interface { SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error } + +type License interface { + Create(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error) + Update(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error) + FindByID(ctx context.Context, id string) (*entity.LicenseDB, error) + GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error) +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 1f00cee..d93da3c 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -2,6 +2,7 @@ package routes import ( "furtuna-be/internal/handlers/http/branch" + "furtuna-be/internal/handlers/http/license" mdtrns "furtuna-be/internal/handlers/http/midtrans" "furtuna-be/internal/handlers/http/order" "furtuna-be/internal/handlers/http/oss" @@ -56,6 +57,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana partner.NewHandler(serviceManager.PartnerSvc), site.NewHandler(serviceManager.SiteSvc), mdtrns.NewHandler(serviceManager.OrderSvc), + license.NewHandler(serviceManager.LicenseSvc), } for _, handler := range serverRoutes { diff --git a/internal/services/license/license.go b/internal/services/license/license.go new file mode 100644 index 0000000..7d94e4f --- /dev/null +++ b/internal/services/license/license.go @@ -0,0 +1,71 @@ +package service + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + + "go.uber.org/zap" +) + +type LicenseService struct { + repo repository.License +} + +func NewLicenseService(repo repository.License) *LicenseService { + return &LicenseService{ + repo: repo, + } +} + +func (s *LicenseService) Create(ctx mycontext.Context, licenseReq *entity.License) (*entity.License, error) { + licenseDB := licenseReq.ToLicenseDB() + licenseDB.CreatedBy = ctx.RequestedBy() + licenseDB.UpdatedBy = ctx.RequestedBy() + + licenseDB, err := s.repo.Create(ctx, licenseDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create license", zap.Error(err)) + return nil, err + } + + return licenseDB.ToLicense(), nil +} + +func (s *LicenseService) Update(ctx mycontext.Context, id string, licenseReq *entity.License) (*entity.License, error) { + existingLicense, err := s.repo.FindByID(ctx, id) + if err != nil { + return nil, err + } + + existingLicense.ToUpdatedLicense(ctx.RequestedBy(), *licenseReq) + + updatedLicenseDB, err := s.repo.Update(ctx, existingLicense) + if err != nil { + logger.ContextLogger(ctx).Error("error when update license", zap.Error(err)) + return nil, err + } + + return updatedLicenseDB.ToLicense(), nil +} + +func (s *LicenseService) GetByID(ctx context.Context, id string) (*entity.License, error) { + licenseDB, err := s.repo.FindByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get license by id", zap.Error(err)) + return nil, err + } + + return licenseDB.ToLicense(), nil +} + +func (s *LicenseService) GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error) { + licenses, total, err := s.repo.GetAll(ctx, limit, offset) + if err != nil { + logger.ContextLogger(ctx).Error("error when getting all licenses", zap.Error(err)) + return nil, 0, err + } + return licenses, total, nil +} diff --git a/internal/services/service.go b/internal/services/service.go index 539f519..a7e4c2c 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -4,6 +4,7 @@ import ( "context" "furtuna-be/internal/common/mycontext" "furtuna-be/internal/services/branch" + service "furtuna-be/internal/services/license" "furtuna-be/internal/services/order" "furtuna-be/internal/services/oss" "furtuna-be/internal/services/partner" @@ -32,6 +33,7 @@ type ServiceManagerImpl struct { OSSSvc OSSService PartnerSvc Partner SiteSvc Site + LicenseSvc License } func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { @@ -46,7 +48,8 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) OSSSvc: oss.NewOSSService(repo.OSS), PartnerSvc: partner.NewPartnerService( repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet), - SiteSvc: site.NewSiteService(repo.Site), + SiteSvc: site.NewSiteService(repo.Site), + LicenseSvc: service.NewLicenseService(repo.License), } } @@ -123,3 +126,10 @@ type Site interface { GetAll(ctx context.Context, search entity.SiteSearch) ([]*entity.Site, int, error) Delete(ctx mycontext.Context, id int64) error } + +type License interface { + Create(ctx mycontext.Context, licenseReq *entity.License) (*entity.License, error) + Update(ctx mycontext.Context, id string, licenseReq *entity.License) (*entity.License, error) + GetByID(ctx context.Context, id string) (*entity.License, error) + GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error) +} diff --git a/migrations/000011_add-license-table.down.sql b/migrations/000011_add-license-table.down.sql new file mode 100644 index 0000000..f5640fa --- /dev/null +++ b/migrations/000011_add-license-table.down.sql @@ -0,0 +1 @@ +DROP TABLE licenses cascade; \ No newline at end of file diff --git a/migrations/000011_add-license-table.up.sql b/migrations/000011_add-license-table.up.sql new file mode 100644 index 0000000..d8c57e4 --- /dev/null +++ b/migrations/000011_add-license-table.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE licenses +( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + partner_id NUMERIC NOT NULL, + name VARCHAR(255) NOT NULL, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + renewal_date DATE, + serial_number VARCHAR(255) UNIQUE NOT NULL, + created_by numeric not null, + updated_by numeric not null, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file