Add User Partner and Product

This commit is contained in:
aditya.siregar 2024-06-03 14:40:50 +07:00
parent 67f1dbc850
commit 8a23e72230
46 changed files with 1366 additions and 213 deletions

View File

@ -30,6 +30,3 @@ deploy_to_staging:
- kubectl apply -f k8s/staging/ingress.yaml - kubectl apply -f k8s/staging/ingress.yaml
only: only:
- main - main
# tes bintang 4

View File

@ -1,9 +1,9 @@
PROJECT_NAME = "furtuna-backend" PROJECT_NAME = "furtuna-backend"
DB_USERNAME := furtuna_admin DB_USERNAME := fortuna_admin
DB_PASSWORD := Z4G827t9428QFQ%5ESZXW%2343dB%25%214Bmh80 DB_PASSWORD := Z4G827t9428QFQ%5ESZXW%2343dB%25%214Bmh80
DB_HOST := 103.96.146.124 DB_HOST := 103.96.146.124
DB_PORT := 1960 DB_PORT := 1960
DB_NAME := furtuna-staging DB_NAME := fortuna-staging
DB_URL = postgres://$(DB_USERNAME):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable DB_URL = postgres://$(DB_USERNAME):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable

1
furtuna-frontend Submodule

@ -0,0 +1 @@
Subproject commit 19af799abdd523522fad579044d55f5d764f87a3

View File

@ -72,6 +72,9 @@ func (s *ServiceException) MapErrorsToHTTPCode() int {
case errInvalidLogin: case errInvalidLogin:
return http.StatusBadRequest return http.StatusBadRequest
case errUserIsNotFound:
return http.StatusBadRequest
default: default:
return http.StatusInternalServerError return http.StatusInternalServerError
} }
@ -89,6 +92,9 @@ func (s *ServiceException) MapErrorsToCode() Code {
case errBadRequest: case errBadRequest:
return BadRequest return BadRequest
case errUserIsNotFound:
return BadRequest
default: default:
return ServerError return ServerError
} }

View File

@ -13,6 +13,7 @@ type Context interface {
RequestedBy() int64 RequestedBy() int64
IsSuperAdmin() bool IsSuperAdmin() bool
GetPartnerID() *int64
} }
type MyContextImpl struct { type MyContextImpl struct {
@ -20,7 +21,7 @@ type MyContextImpl struct {
requestedBy int64 requestedBy int64
requestID string requestID string
branchID int64 partnerID int64
roleID int roleID int
} }
@ -32,11 +33,18 @@ func (m *MyContextImpl) IsSuperAdmin() bool {
return m.roleID == int(role.SuperAdmin) return m.roleID == int(role.SuperAdmin)
} }
func (m *MyContextImpl) GetPartnerID() *int64 {
if m.partnerID != 0 {
return &m.partnerID
}
return nil
}
func NewMyContext(parent context.Context, claims *entity.JWTAuthClaims) (*MyContextImpl, error) { func NewMyContext(parent context.Context, claims *entity.JWTAuthClaims) (*MyContextImpl, error) {
return &MyContextImpl{ return &MyContextImpl{
Context: parent, Context: parent,
requestedBy: claims.UserID, requestedBy: claims.UserID,
branchID: claims.BranchID, partnerID: claims.PartnerID,
roleID: claims.Role, roleID: claims.Role,
}, nil }, nil
} }

View File

@ -4,6 +4,8 @@ type Role int64
const ( const (
SuperAdmin Role = 1 SuperAdmin Role = 1
BranchAdmin Role = 2 Admin Role = 2
CasheerAdmin Role = 3 PartnerAdmin Role = 3
SiteAdmin Role = 4
Casheer Role = 5
) )

View File

@ -3,8 +3,8 @@ package studio
type StudioStatus string type StudioStatus string
const ( const (
Active StudioStatus = "Active" Active StudioStatus = "active"
Inactive StudioStatus = "Inactive" Inactive StudioStatus = "inactive"
) )
func (b StudioStatus) toString() string { func (b StudioStatus) toString() string {

View File

@ -24,8 +24,9 @@ type UserDB struct {
NIK string `gorm:"column:nik" json:"nik"` NIK string `gorm:"column:nik" json:"nik"`
RoleID int64 `gorm:"column:role_id" json:"role_id"` RoleID int64 `gorm:"column:role_id" json:"role_id"`
RoleName string `gorm:"column:role_name" json:"role_name"` RoleName string `gorm:"column:role_name" json:"role_name"`
BranchID *int64 `gorm:"column:partner_id" json:"partner_id"` PartnerID *int64 `gorm:"column:partner_id" json:"partner_id"`
BranchName string `gorm:"column:partner_name" json:"partner_name"` PartnerName string `gorm:"column:partner_name" json:"partner_name"`
PartnerStatus string `gorm:"column:partner_status" json:"partner_status"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"` DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"`
@ -47,8 +48,8 @@ func (u *UserDB) ToUser() *User {
UpdatedAt: u.UpdatedAt, UpdatedAt: u.UpdatedAt,
RoleID: role.Role(u.RoleID), RoleID: role.Role(u.RoleID),
RoleName: u.RoleName, RoleName: u.RoleName,
PartnerID: u.BranchID, PartnerID: u.PartnerID,
BranchName: u.BranchName, PartnerName: u.PartnerName,
} }
return userEntity return userEntity
@ -63,7 +64,7 @@ func (u *UserDB) ToUserRoleDB() *UserRoleDB {
ID: 0, ID: 0,
UserID: u.ID, UserID: u.ID,
RoleID: u.RoleID, RoleID: u.RoleID,
PartnerID: u.BranchID, PartnerID: u.PartnerID,
CreatedAt: u.CreatedAt, CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt, UpdatedAt: u.UpdatedAt,
} }
@ -81,8 +82,9 @@ func (u *UserDB) ToUserAuthenticate(signedToken string) *AuthenticateUser {
Name: u.Name, Name: u.Name,
RoleID: role.Role(u.RoleID), RoleID: role.Role(u.RoleID),
RoleName: u.RoleName, RoleName: u.RoleName,
BranchID: u.BranchID, PartnerID: u.PartnerID,
BranchName: u.BranchName, PartnerName: u.PartnerName,
PartnerStatus: u.PartnerStatus,
} }
} }
@ -116,7 +118,7 @@ func (u *UserDB) ToUpdatedUser(req User) error {
} }
if *req.PartnerID > 0 { if *req.PartnerID > 0 {
u.BranchID = req.PartnerID u.PartnerID = req.PartnerID
} }
if req.RoleID > 0 { if req.RoleID > 0 {

View File

@ -7,6 +7,6 @@ type JWTAuthClaims struct {
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
Role int `json:"role"` Role int `json:"role"`
BranchID int64 `json:"branch_id"` PartnerID int64 `json:"partner_id"`
jwt.StandardClaims jwt.StandardClaims
} }

View File

@ -1,19 +1,42 @@
package entity package entity
import ( import (
"furtuna-be/internal/constants/role"
"time" "time"
) )
type CreatePartnerRequest struct {
Name string `json:"name" validate:"required"`
Address string `json:"address"`
Username string `json:"username" validate:"required"`
FullName string `json:"full_name"`
Email string `json:"email"`
Password string `json:"password" validate:"required"`
NIK string `json:"nik"`
PhoneNumber string `json:"phone_number"`
BankName string `json:"bank_name"`
BankAccountNumber string `json:"bank_account_number"`
BankAccountHolderName string `json:"bank_account_holder_name"`
}
type Partner struct { type Partner struct {
ID int64 ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
Name string Name string `gorm:"type:varchar(255);not null;column:name"`
Status string Status string `gorm:"type:varchar(50);column:status"`
Address string LicenseExpiredDate *time.Time `gorm:"type:date;column:license_expired_date"`
CreatedAt time.Time Address string `gorm:"type:varchar(255);column:address"`
UpdatedAt time.Time BankName string `gorm:"type:varchar(255);column:bank_name"`
DeletedAt *time.Time BankAccountNumber string `gorm:"type:varchar(50);column:bank_account_number"`
CreatedBy int64 BankAccountHolderName string `gorm:"type:varchar(255);column:bank_account_holder_name"`
UpdatedBy int64 CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
CreatedBy int64 `gorm:"type:int;column:created_by"`
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
}
func (Partner) TableName() string {
return "partners"
} }
type PartnerSearch struct { type PartnerSearch struct {
@ -80,3 +103,46 @@ func (o *PartnerDB) SetDeleted(updatedBy int64) {
o.DeletedAt = &currentTime o.DeletedAt = &currentTime
o.UpdatedBy = updatedBy o.UpdatedBy = updatedBy
} }
func (c *CreatePartnerRequest) ToUserAdmin(partnerID int64) *User {
return &User{
Name: c.FullName,
Password: c.Password,
Email: c.Email,
NIK: c.NIK,
Status: "active",
RoleID: role.PartnerAdmin,
PartnerID: &partnerID,
}
}
func (e *CreatePartnerRequest) ToPartnerDB(createdBy int64) *PartnerDB {
twoDays := 48 * time.Hour
licenseExpiredDate := time.Now().Add(twoDays)
return &PartnerDB{
Partner: Partner{
Name: e.Name,
Status: "inactive",
Address: e.Address,
CreatedBy: createdBy,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
BankAccountHolderName: e.BankAccountHolderName,
BankAccountNumber: e.BankAccountNumber,
BankName: e.BankName,
LicenseExpiredDate: &licenseExpiredDate,
},
}
}
func (e *CreatePartnerRequest) ToWallet(partnerID int64) *Wallet {
return &Wallet{
PartnerID: partnerID,
Balance: 0,
Currency: "IDR",
Status: "active",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}

View File

@ -6,20 +6,25 @@ import (
) )
type Product struct { type Product struct {
ID int64 ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
Name string PartnerID int64 `gorm:"type:int;column:partner_id"`
Type product.ProductType SiteID int64 `gorm:"type:int;column:site_id"`
Price float64 Name string `gorm:"type:varchar(255);not null;column:name"`
Status product.ProductStatus Type string `gorm:"type:varchar;column:type"`
Description string Price float64 `gorm:"type:decimal;column:price"`
Image string IsWeekendTicket bool `gorm:"type:bool;column:is_weekend_ticket"`
BranchID int64 IsSeasonTicket bool `gorm:"type:bool;column:is_season_ticket"`
StockQty int64 Status string `gorm:"type:varchar;column:status"`
CreatedAt time.Time Description string `gorm:"type:varchar(255);not null;column:description"`
UpdatedAt time.Time CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
DeletedAt *time.Time UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
CreatedBy int64 DeletedAt *time.Time `gorm:"column:deleted_at"`
UpdatedBy int64 CreatedBy int64 `gorm:"type:int;column:created_by"`
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
}
func (Product) TableName() string {
return "products"
} }
type ProductSearch struct { type ProductSearch struct {
@ -56,9 +61,7 @@ func (e *ProductDB) ToProduct() *Product {
Price: e.Price, Price: e.Price,
Status: e.Status, Status: e.Status,
Description: e.Description, Description: e.Description,
Image: e.Image, PartnerID: e.PartnerID,
BranchID: e.BranchID,
StockQty: e.StockQty,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
DeletedAt: e.DeletedAt, DeletedAt: e.DeletedAt,
@ -97,14 +100,6 @@ func (o *ProductDB) ToUpdatedProduct(updatedby int64, req Product) {
if req.Description != "" { if req.Description != "" {
o.Description = req.Description o.Description = req.Description
} }
if req.Image != "" {
o.Image = req.Image
}
if req.StockQty > 0 {
o.StockQty = req.StockQty
}
} }
func (o *ProductDB) SetDeleted(updatedby int64) { func (o *ProductDB) SetDeleted(updatedby int64) {

145
internal/entity/sites.go Normal file
View File

@ -0,0 +1,145 @@
package entity
import (
"time"
)
type Site struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
Name string `gorm:"type:varchar(255);not null;column:name"`
PartnerID int64 `gorm:"type:int;column:partner_id"`
Image string `gorm:"type:varchar;column:image"`
Address string `gorm:"type:varchar;column:address"`
LocationLink string `gorm:"type:varchar;column:location_link"`
Description string `gorm:"type:varchar;column:description"`
Highlight string `gorm:"type:varchar;column:highlight"`
ContactPerson string `gorm:"type:varchar;column:contact_person"`
TnC string `gorm:"type:varchar;column:tnc"`
AdditionalInfo string `gorm:"type:varchar;column:additional_info"`
Status string `gorm:"type:varchar;column:status"`
IsSeasonTicket bool `gorm:"type:bool;column:is_season_ticket"`
IsDiscountActive bool `gorm:"type:bool;column:is_discount_active"`
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
CreatedBy int64 `gorm:"type:int;column:created_by"`
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
Products []Product `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"`
}
type SiteSearch struct {
Search string
Name string
Limit int
Offset int
}
type SiteList []*SiteDB
type SiteDB struct {
Site
}
func (s *Site) ToSiteDB() *SiteDB {
return &SiteDB{
Site: *s,
}
}
func (SiteDB) TableName() string {
return "sites"
}
func (e *SiteDB) ToSite() *Site {
return &Site{
ID: e.ID,
Name: e.Name,
PartnerID: e.PartnerID,
Image: e.Image,
Address: e.Address,
LocationLink: e.LocationLink,
Description: e.Description,
Highlight: e.Highlight,
ContactPerson: e.ContactPerson,
TnC: e.TnC,
AdditionalInfo: e.AdditionalInfo,
Status: e.Status,
IsSeasonTicket: e.IsSeasonTicket,
IsDiscountActive: e.IsDiscountActive,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
DeletedAt: e.DeletedAt,
CreatedBy: e.CreatedBy,
UpdatedBy: e.UpdatedBy,
}
}
func (s *SiteList) ToSiteList() []*Site {
var sites []*Site
for _, site := range *s {
sites = append(sites, site.ToSite())
}
return sites
}
func (o *SiteDB) ToUpdatedSite(updatedBy int64, req Site) {
o.UpdatedBy = updatedBy
if req.Name != "" {
o.Name = req.Name
}
if req.PartnerID != 0 {
o.PartnerID = req.PartnerID
}
if req.Image != "" {
o.Image = req.Image
}
if req.Address != "" {
o.Address = req.Address
}
if req.LocationLink != "" {
o.LocationLink = req.LocationLink
}
if req.Description != "" {
o.Description = req.Description
}
if req.Highlight != "" {
o.Highlight = req.Highlight
}
if req.ContactPerson != "" {
o.ContactPerson = req.ContactPerson
}
if req.TnC != "" {
o.TnC = req.TnC
}
if req.AdditionalInfo != "" {
o.AdditionalInfo = req.AdditionalInfo
}
if req.Status != "" {
o.Status = req.Status
}
if req.IsSeasonTicket {
o.IsSeasonTicket = req.IsSeasonTicket
}
if req.IsDiscountActive {
o.IsDiscountActive = req.IsDiscountActive
}
}
func (o *SiteDB) SetDeleted(updatedBy int64) {
currentTime := time.Now()
o.DeletedAt = &currentTime
o.UpdatedBy = updatedBy
}

View File

@ -21,7 +21,7 @@ type User struct {
RoleID role.Role RoleID role.Role
RoleName string RoleName string
PartnerID *int64 PartnerID *int64
BranchName string PartnerName string
} }
type AuthenticateUser struct { type AuthenticateUser struct {
@ -29,8 +29,9 @@ type AuthenticateUser struct {
Name string Name string
RoleID role.Role RoleID role.Role
RoleName string RoleName string
BranchID *int64 PartnerID *int64
BranchName string PartnerName string
PartnerStatus string
} }
type UserRoleDB struct { type UserRoleDB struct {
@ -53,7 +54,7 @@ func (u *User) ToUserDB(createdBy int64) (*UserDB, error) {
return nil, err return nil, err
} }
if u.RoleID == role.BranchAdmin && u.PartnerID == nil { if u.RoleID == role.Admin && u.PartnerID == nil {
return nil, errors.New("invalid request") return nil, errors.New("invalid request")
} }
@ -62,7 +63,7 @@ func (u *User) ToUserDB(createdBy int64) (*UserDB, error) {
Email: u.Email, Email: u.Email,
Password: hashedPassword, Password: hashedPassword,
RoleID: int64(u.RoleID), RoleID: int64(u.RoleID),
BranchID: u.PartnerID, PartnerID: u.PartnerID,
Status: userstatus.Active, Status: userstatus.Active,
CreatedBy: createdBy, CreatedBy: createdBy,
}, nil }, nil

17
internal/entity/wallet.go Normal file
View File

@ -0,0 +1,17 @@
package entity
import "time"
type Wallet struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
PartnerID int64 `gorm:"type:int;not null;column:partner_id"`
Balance float64 `gorm:"type:decimal(18,2);not null;default:0.00;column:balance"`
Currency string `gorm:"type:varchar(3);not null;column:currency"`
Status string `gorm:"type:varchar(50);column:status"`
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
}
func (Wallet) TableName() string {
return "wallets"
}

View File

@ -51,18 +51,19 @@ func (h *AuthHandler) AuthLogin(c *gin.Context) {
return return
} }
var branch *response.Branch var partner *response.Partner
if authUser.RoleID != role.SuperAdmin { if authUser.RoleID != role.SuperAdmin {
branch = &response.Branch{ partner = &response.Partner{
ID: authUser.BranchID, ID: authUser.PartnerID,
Name: authUser.BranchName, Name: authUser.PartnerName,
Status: authUser.PartnerStatus,
} }
} }
resp := response.LoginResponse{ resp := response.LoginResponse{
Token: authUser.Token, Token: authUser.Token,
Branch: branch, Partner: partner,
Name: authUser.Name, Name: authUser.Name,
Role: response.Role{ Role: response.Role{
ID: int64(authUser.RoleID), ID: int64(authUser.RoleID),

View File

@ -51,7 +51,7 @@ func NewHandler(service services.Partner) *Handler {
func (h *Handler) Create(c *gin.Context) { func (h *Handler) Create(c *gin.Context) {
ctx := request.GetMyContext(c) ctx := request.GetMyContext(c)
var req request.Partner var req request.CreatePartnerRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest) response.ErrorWrapper(c, errors.ErrorBadRequest)
return return

View File

@ -246,9 +246,6 @@ func (h *Handler) toProductResponse(resp *entity.Product) response.Product {
Price: resp.Price, Price: resp.Price,
Status: resp.Status, Status: resp.Status,
Description: resp.Description, Description: resp.Description,
Image: resp.Image,
BranchID: resp.BranchID,
StockQty: resp.StockQty,
CreatedAt: resp.CreatedAt.Format(time.RFC3339), CreatedAt: resp.CreatedAt.Format(time.RFC3339),
UpdatedAt: resp.CreatedAt.Format(time.RFC3339), UpdatedAt: resp.CreatedAt.Format(time.RFC3339),
} }

View File

@ -0,0 +1,299 @@
package site
import (
"furtuna-be/internal/common/errors"
"furtuna-be/internal/entity"
"furtuna-be/internal/handlers/request"
"furtuna-be/internal/handlers/response"
"furtuna-be/internal/services"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
type Handler struct {
service services.Site
}
func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
route := group.Group("/site")
route.POST("/", jwt, h.Create)
route.GET("/list", jwt, h.GetAll)
route.PUT("/:id", jwt, h.Update)
route.GET("/:id", jwt, h.GetByID)
route.DELETE("/:id", jwt, h.Delete)
}
func NewHandler(service services.Site) *Handler {
return &Handler{
service: service,
}
}
// Create handles the creation of a new Site.
// @Summary Create a new Site
// @Description Create a new Site based on the provided data.
// @Accept json
// @Produce json
// @Param Authorization header string true "JWT token"
// @Param req body request.Site true "New Site details"
// @Success 200 {object} response.BaseResponse{data=response.Site} "Site created successfully"
// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request"
// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized"
// @Router /api/v1/site [post]
// @Tags Site APIs
func (h *Handler) Create(c *gin.Context) {
ctx := request.GetMyContext(c)
var req request.Site
if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
if !ctx.IsSuperAdmin() {
req.PartnerID = ctx.GetPartnerID()
}
validate := validator.New()
if err := validate.Struct(req); err != nil {
response.ErrorWrapper(c, err)
return
}
res, err := h.service.Create(ctx, req.ToEntity(ctx.RequestedBy()))
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: h.toSiteResponse(res),
})
}
// Update handles the update of an existing Site.
// @Summary Update an existing Site
// @Description Update the details of an existing Site based on the provided ID.
// @Accept json
// @Produce json
// @Param Authorization header string true "JWT token"
// @Param id path int64 true "Site ID to update"
// @Param req body request.Site true "Updated Site details"
// @Success 200 {object} response.BaseResponse{data=response.Site} "Site updated successfully"
// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request"
// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized"
// @Router /api/v1/site/{id} [put]
// @Tags Site APIs
func (h *Handler) Update(c *gin.Context) {
ctx := request.GetMyContext(c)
id := c.Param("id")
SiteID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
var req request.Site
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
}
updatedSite, err := h.service.Update(ctx, SiteID, req.ToEntity(ctx.RequestedBy()))
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: h.toSiteResponse(updatedSite),
})
}
// GetAll retrieves a list of Sites.
// @Summary Get a list of Sites
// @Description Get a paginated list of Sites based on query parameters.
// @Accept json
// @Produce json
// @Param Authorization header string true "JWT token"
// @Param Limit query int false "Number of items to retrieve (default 10)"
// @Param Offset query int false "Offset for pagination (default 0)"
// @Success 200 {object} response.BaseResponse{data=response.SiteList} "List of Sites"
// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request"
// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized"
// @Router /api/v1/site/list [get]
// @Tags Site APIs
func (h *Handler) GetAll(c *gin.Context) {
var req request.SiteParam
if err := c.ShouldBindQuery(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
Sites, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity())
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: h.toSiteResponseList(Sites, int64(total), req),
})
}
// Delete handles the deletion of a Site by ID.
// @Summary Delete a Site by ID
// @Description Delete a Site based on the provided ID.
// @Accept json
// @Produce json
// @Param Authorization header string true "JWT token"
// @Param id path int64 true "Site ID to delete"
// @Success 200 {object} response.BaseResponse "Site deleted successfully"
// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request"
// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized"
// @Router /api/v1/site/{id} [delete]
// @Tags Site APIs
func (h *Handler) Delete(c *gin.Context) {
ctx := request.GetMyContext(c)
id := c.Param("id")
// Parse the ID into a uint
SiteID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
err = h.service.Delete(ctx, SiteID)
if err != nil {
c.JSON(http.StatusInternalServerError, response.BaseResponse{
Success: false,
Status: http.StatusInternalServerError,
Message: err.Error(),
Data: nil,
})
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: nil,
})
}
// GetByID retrieves details of a specific Site by ID.
// @Summary Get details of a Site by ID
// @Description Get details of a Site based on the provided ID.
// @Accept json
// @Produce json
// @Param Authorization header string true "JWT token"
// @Param id path int64 true "Site ID to retrieve"
// @Success 200 {object} response.BaseResponse{data=response.Site} "Site details"
// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request"
// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized"
// @Router /api/v1/site/{id} [get]
// @Tags Site APIs
func (h *Handler) GetByID(c *gin.Context) {
id := c.Param("id")
// Parse the ID into a uint
SiteID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
res, err := h.service.GetByID(c.Request.Context(), SiteID)
if err != nil {
c.JSON(http.StatusInternalServerError, response.BaseResponse{
Success: false,
Status: http.StatusInternalServerError,
Message: err.Error(),
Data: nil,
})
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: h.toSiteResponse(res),
})
}
func (h *Handler) toSiteResponse(resp *entity.Site) response.Site {
return response.Site{
ID: &resp.ID,
Name: resp.Name,
PartnerID: resp.PartnerID,
Image: resp.Image,
Address: resp.Address,
LocationLink: resp.LocationLink,
Description: resp.Description,
Highlight: resp.Highlight,
ContactPerson: resp.ContactPerson,
TnC: resp.TnC,
AdditionalInfo: resp.AdditionalInfo,
Status: resp.Status,
IsSeasonTicket: resp.IsSeasonTicket,
IsDiscountActive: resp.IsDiscountActive,
CreatedAt: resp.CreatedAt.Format(time.RFC3339),
UpdatedAt: resp.UpdatedAt.Format(time.RFC3339),
}
}
func (h *Handler) toSiteResponseList(resp []*entity.Site, total int64, req request.SiteParam) response.SiteList {
var Sites []response.Site
for _, b := range resp {
Sites = append(Sites, h.toSiteResponse(b))
}
return response.SiteList{
Sites: Sites,
Total: total,
Limit: req.Limit,
Offset: req.Offset,
}
}
func (h *Handler) toProductResponseList(products []entity.Product) []response.Product {
var res []response.Product
for _, product := range products {
res = append(res, response.Product{
ID: product.ID,
PartnerID: product.PartnerID,
SiteID: product.SiteID,
Name: product.Name,
Type: product.Type,
Price: product.Price,
IsWeekendTicket: product.IsWeekendTicket,
IsSeasonTicket: product.IsSeasonTicket,
Status: product.Status,
Description: product.Description,
CreatedAt: product.CreatedAt.Format(time.RFC3339),
UpdatedAt: product.UpdatedAt.Format(time.RFC3339),
})
}
return res
}

View File

@ -56,13 +56,15 @@ func (h *Handler) Create(c *gin.Context) {
return return
} }
req.PartnerID = ctx.GetPartnerID()
if err := req.Validate(); err != nil { if err := req.Validate(); err != nil {
response.ErrorWrapper(c, errors.ErrorInvalidRequest) response.ErrorWrapper(c, errors.ErrorInvalidRequest)
return return
} }
res, err := h.service.Create(ctx, req.ToEntity()) ctx.IsSuperAdmin()
res, err := h.service.Create(ctx, req.ToEntity())
if err != nil { if err != nil {
response.ErrorWrapper(c, err) response.ErrorWrapper(c, err)
return return
@ -267,7 +269,7 @@ func (h *Handler) toUserResponse(resp *entity.User) response.User {
RoleID: int64(resp.RoleID), RoleID: int64(resp.RoleID),
RoleName: resp.RoleName, RoleName: resp.RoleName,
PartnerID: resp.PartnerID, PartnerID: resp.PartnerID,
BranchName: resp.BranchName, PartnerName: resp.PartnerName,
CreatedAt: resp.CreatedAt.Format(time.RFC3339), CreatedAt: resp.CreatedAt.Format(time.RFC3339),
UpdatedAt: resp.CreatedAt.Format(time.RFC3339), UpdatedAt: resp.CreatedAt.Format(time.RFC3339),
} }

View File

@ -26,6 +26,36 @@ type Partner struct {
Status string `json:"status"` Status string `json:"status"`
} }
type CreatePartnerRequest struct {
Name string `json:"name" validate:"required"`
Address string `json:"address"`
Username string `json:"username" validate:"required"`
FullName string `json:"full_name"`
Email string `json:"email"`
Password string `json:"password" validate:"required"`
NIK string `json:"nik"`
PhoneNumber string `json:"phone_number"`
BankName string `json:"bank_name"`
BankAccountNumber string `json:"bank_account_number"`
BankAccountHolderName string `json:"bank_account_holder_name"`
}
func (e *CreatePartnerRequest) ToEntity() *entity.CreatePartnerRequest {
return &entity.CreatePartnerRequest{
Name: e.Name,
Address: e.Address,
Username: e.Username,
FullName: e.FullName,
Email: e.Email,
Password: e.Password,
NIK: e.NIK,
PhoneNumber: e.PhoneNumber,
BankName: e.BankName,
BankAccountNumber: e.BankAccountNumber,
BankAccountHolderName: e.BankAccountHolderName,
}
}
func (e *Partner) ToEntity() *entity.Partner { func (e *Partner) ToEntity() *entity.Partner {
return &entity.Partner{ return &entity.Partner{
Name: e.Name, Name: e.Name,

View File

@ -28,14 +28,15 @@ func (p *ProductParam) ToEntity() entity.ProductSearch {
} }
type Product struct { type Product struct {
ID int64 `json:"id,omitempty"`
PartnerID int64 `json:"partner_id"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Type product.ProductType `json:"type" validate:"required"` Type string `json:"type"`
Price float64 `json:"price" validate:"required"` Price float64 `json:"price" validate:"required"`
Status product.ProductStatus `json:"status" validate:"required"` IsWeekendTicket bool `json:"is_weekend_ticket"`
Description string `json:"description" ` IsSeasonTicket bool `json:"is_season_ticket"`
Image string `json:"image" ` Status string `json:"status"`
BranchID int64 `json:"branch_id" validate:"required"` Description string `json:"description" validate:"required"`
StockQty int64 `json:"stock_qty" `
} }
func (e *Product) ToEntity() *entity.Product { func (e *Product) ToEntity() *entity.Product {
@ -45,8 +46,5 @@ func (e *Product) ToEntity() *entity.Product {
Price: e.Price, Price: e.Price,
Status: e.Status, Status: e.Status,
Description: e.Description, Description: e.Description,
Image: e.Image,
BranchID: e.BranchID,
StockQty: e.StockQty,
} }
} }

View File

@ -0,0 +1,75 @@
package request
import (
"furtuna-be/internal/entity"
)
type Site struct {
ID int64 `json:"id"`
Name string `json:"name" validate:"required"`
PartnerID *int64 `json:"partner_id"`
Image string `json:"image"`
Address string `json:"address"`
LocationLink string `json:"location_link"`
Description string `json:"description"`
Highlight string `json:"highlight"`
ContactPerson string `json:"contact_person"`
TnC string `json:"tnc"`
AdditionalInfo string `json:"additional_info"`
Status string `json:"status"`
IsSeasonTicket bool `json:"is_season_ticket"`
IsDiscountActive bool `json:"is_discount_active"`
Products []Product `json:"products"`
}
func (r *Site) ToEntity(createdBy int64) *entity.Site {
var products []entity.Product
for _, p := range r.Products {
products = append(products, entity.Product{
ID: p.ID,
PartnerID: *r.PartnerID,
Name: p.Name,
Type: p.Type,
Price: p.Price,
IsWeekendTicket: p.IsWeekendTicket,
IsSeasonTicket: p.IsSeasonTicket,
Status: p.Status,
Description: p.Description,
CreatedBy: createdBy,
})
}
return &entity.Site{
ID: r.ID,
Name: r.Name,
PartnerID: *r.PartnerID,
Image: r.Image,
Address: r.Address,
LocationLink: r.LocationLink,
Description: r.Description,
Highlight: r.Highlight,
ContactPerson: r.ContactPerson,
TnC: r.TnC,
AdditionalInfo: r.AdditionalInfo,
Status: r.Status,
IsSeasonTicket: r.IsSeasonTicket,
IsDiscountActive: r.IsDiscountActive,
Products: products,
}
}
type SiteParam struct {
Search string `form:"search"`
Name string `form:"name"`
Limit int `form:"limit,default=10"`
Offset int `form:"offset,default=0"`
}
func (r *SiteParam) ToEntity() entity.SiteSearch {
return entity.SiteSearch{
Search: r.Search,
Name: r.Name,
Limit: r.Limit,
Offset: r.Offset,
}
}

View File

@ -4,7 +4,7 @@ type LoginResponse struct {
Token string `json:"token"` Token string `json:"token"`
Name string `json:"name"` Name string `json:"name"`
Role Role `json:"role"` Role Role `json:"role"`
Branch *Branch `json:"branch"` Partner *Partner `json:"partner"`
} }
type Role struct { type Role struct {

View File

@ -4,9 +4,9 @@ type Partner struct {
ID *int64 `json:"id"` ID *int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Status string `json:"status"` Status string `json:"status"`
Address string `json:"address"` Address string `json:"address,omitempty"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at,omitempty"`
} }
type PartnerList struct { type PartnerList struct {

View File

@ -1,17 +1,16 @@
package response package response
import "furtuna-be/internal/constants/product"
type Product struct { type Product struct {
ID int64 `json:"id"` ID int64 `json:"id"`
PartnerID int64 `json:"partner_id"`
SiteID int64 `json:"site_id"`
Name string `json:"name"` Name string `json:"name"`
Type product.ProductType `json:"type"` Type string `json:"type"`
Price float64 `json:"price"` Price float64 `json:"price"`
Status product.ProductStatus `json:"status"` IsWeekendTicket bool `json:"is_weekend_ticket"`
IsSeasonTicket bool `json:"is_season_ticket"`
Status string `json:"status"`
Description string `json:"description"` Description string `json:"description"`
Image string `json:"image" `
BranchID int64 `json:"branch_id"`
StockQty int64 `json:"stock_qty"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
} }

View File

@ -0,0 +1,27 @@
package response
type Site struct {
ID *int64 `json:"id"`
Name string `json:"name"`
PartnerID int64 `json:"partner_id"`
Image string `json:"image"`
Address string `json:"address"`
LocationLink string `json:"location_link"`
Description string `json:"description"`
Highlight string `json:"highlight"`
ContactPerson string `json:"contact_person"`
TnC string `json:"tnc"`
AdditionalInfo string `json:"additional_info"`
Status string `json:"status"`
IsSeasonTicket bool `json:"is_season_ticket"`
IsDiscountActive bool `json:"is_discount_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type SiteList struct {
Sites []Site `json:"sites"`
Total int64 `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}

View File

@ -8,7 +8,7 @@ type User struct {
RoleID int64 `json:"role_id"` RoleID int64 `json:"role_id"`
RoleName string `json:"role_name"` RoleName string `json:"role_name"`
PartnerID *int64 `json:"partner_id"` PartnerID *int64 `json:"partner_id"`
BranchName string `json:"partner_name"` PartnerName string `json:"partner_name"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
@ -27,13 +26,17 @@ func (r *AuthRepository) CheckExistsUserAccount(ctx context.Context, email strin
err := r.db. err := r.db.
Table("users"). Table("users").
Select("users.*, user_roles.role_id, user_roles.partner_id, roles.role_name, partners.name as partner_name"). Select("users.*, user_roles.role_id, user_roles.partner_id, roles.role_name, partners.name as partner_name, partners.status as partner_status").
Where("users.email = ?", email). Where("users.email = ?", email).
Joins("left join user_roles on users.id = user_roles.user_id"). Joins("left join user_roles on users.id = user_roles.user_id").
Joins("left join roles on user_roles.role_id = roles.role_id"). Joins("left join roles on user_roles.role_id = roles.role_id").
Joins("left join partners on user_roles.partner_id = partners.id"). Joins("left join partners on user_roles.partner_id = partners.id").
First(&user).Error First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("user with email %s does not exist", email) // or use a custom error type return nil, fmt.Errorf("user with email %s does not exist", email) // or use a custom error type

View File

@ -44,9 +44,9 @@ func (c *CryptoImpl) ValidateWT(tokenString string) (*jwt.Token, error) {
} }
func (c *CryptoImpl) GenerateJWT(user *entity.User) (string, error) { func (c *CryptoImpl) GenerateJWT(user *entity.User) (string, error) {
branchID := int64(0) partnerID := int64(0)
if user.PartnerID != nil { if user.PartnerID != nil {
branchID = *user.PartnerID partnerID = *user.PartnerID
} }
claims := &entity.JWTAuthClaims{ claims := &entity.JWTAuthClaims{
@ -60,7 +60,7 @@ func (c *CryptoImpl) GenerateJWT(user *entity.User) (string, error) {
Name: user.Name, Name: user.Name,
Email: user.Email, Email: user.Email,
Role: int(user.RoleID), Role: int(user.RoleID),
BranchID: branchID, PartnerID: partnerID,
} }
token, err := jwt. token, err := jwt.

View File

@ -28,6 +28,15 @@ func (b *PartnerRepository) Create(ctx context.Context, Partner *entity.PartnerD
return Partner, nil return Partner, nil
} }
func (b *PartnerRepository) CreateWithTx(ctx context.Context, tx *gorm.DB, Partner *entity.PartnerDB) (*entity.PartnerDB, error) {
err := tx.Create(Partner).Error
if err != nil {
logger.ContextLogger(ctx).Error("error when create Partner", zap.Error(err))
return nil, err
}
return Partner, nil
}
func (b *PartnerRepository) Update(ctx context.Context, Partner *entity.PartnerDB) (*entity.PartnerDB, error) { func (b *PartnerRepository) Update(ctx context.Context, Partner *entity.PartnerDB) (*entity.PartnerDB, error) {
if err := b.db.Save(Partner).Error; err != nil { if err := b.db.Save(Partner).Error; err != nil {
logger.ContextLogger(ctx).Error("error when update Partner", zap.Error(err)) logger.ContextLogger(ctx).Error("error when update Partner", zap.Error(err))

View File

@ -2,14 +2,17 @@ package repository
import ( import (
"context" "context"
"database/sql"
"furtuna-be/internal/repository/branches" "furtuna-be/internal/repository/branches"
"furtuna-be/internal/repository/orders" "furtuna-be/internal/repository/orders"
"furtuna-be/internal/repository/oss" "furtuna-be/internal/repository/oss"
"furtuna-be/internal/repository/partners" "furtuna-be/internal/repository/partners"
"furtuna-be/internal/repository/products" "furtuna-be/internal/repository/products"
"furtuna-be/internal/repository/sites"
"furtuna-be/internal/repository/studios" "furtuna-be/internal/repository/studios"
"furtuna-be/internal/repository/trx"
"furtuna-be/internal/repository/users" "furtuna-be/internal/repository/users"
repository "furtuna-be/internal/repository/wallet"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"gorm.io/gorm" "gorm.io/gorm"
@ -31,6 +34,9 @@ type RepoManagerImpl struct {
Order Order Order Order
OSS OSSRepository OSS OSSRepository
Partner PartnerRepository Partner PartnerRepository
Site SiteRepository
Trx TransactionManager
Wallet WalletRepository
} }
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
@ -45,6 +51,9 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
Order: orders.NewOrderRepository(db), Order: orders.NewOrderRepository(db),
OSS: oss.NewOssRepositoryImpl(cfg.OSSConfig), OSS: oss.NewOssRepositoryImpl(cfg.OSSConfig),
Partner: partners.NewPartnerRepository(db), Partner: partners.NewPartnerRepository(db),
Site: sites.NewSiteRepository(db),
Trx: trx.NewGormTransactionManager(db),
Wallet: repository.NewWalletRepository(db),
} }
} }
@ -69,6 +78,7 @@ type Crypto interface {
type User interface { type User interface {
Create(ctx context.Context, user *entity.UserDB) (*entity.UserDB, error) Create(ctx context.Context, user *entity.UserDB) (*entity.UserDB, error)
CreateWithTx(ctx context.Context, tx *gorm.DB, user *entity.UserDB) (*entity.UserDB, error)
GetAllUsers(ctx context.Context, req entity.UserSearch) (entity.UserList, int, error) GetAllUsers(ctx context.Context, req entity.UserSearch) (entity.UserList, int, error)
GetUserByID(ctx context.Context, id int64) (*entity.UserDB, error) GetUserByID(ctx context.Context, id int64) (*entity.UserDB, error)
GetUserByEmail(ctx context.Context, email string) (*entity.UserDB, error) GetUserByEmail(ctx context.Context, email string) (*entity.UserDB, error)
@ -115,8 +125,28 @@ type OSSRepository interface {
type PartnerRepository interface { type PartnerRepository interface {
Create(ctx context.Context, branch *entity.PartnerDB) (*entity.PartnerDB, error) Create(ctx context.Context, branch *entity.PartnerDB) (*entity.PartnerDB, error)
CreateWithTx(ctx context.Context, tx *gorm.DB, Partner *entity.PartnerDB) (*entity.PartnerDB, error)
Update(ctx context.Context, branch *entity.PartnerDB) (*entity.PartnerDB, error) Update(ctx context.Context, branch *entity.PartnerDB) (*entity.PartnerDB, error)
GetByID(ctx context.Context, id int64) (*entity.PartnerDB, error) GetByID(ctx context.Context, id int64) (*entity.PartnerDB, error)
GetAll(ctx context.Context, req entity.PartnerSearch) (entity.PartnerList, int, error) GetAll(ctx context.Context, req entity.PartnerSearch) (entity.PartnerList, int, error)
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
} }
type SiteRepository interface {
Upsert(ctx context.Context, site *entity.Site) (*entity.Site, error)
Create(ctx context.Context, branch *entity.SiteDB) (*entity.SiteDB, error)
Update(ctx context.Context, branch *entity.SiteDB) (*entity.SiteDB, error)
GetByID(ctx context.Context, id int64) (*entity.SiteDB, error)
GetAll(ctx context.Context, req entity.SiteSearch) (entity.SiteList, int, error)
Delete(ctx context.Context, id int64) error
}
type TransactionManager interface {
Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error)
Commit(session *gorm.DB) *gorm.DB
Rollback(session *gorm.DB) *gorm.DB
}
type WalletRepository interface {
Create(ctx context.Context, tx *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error)
}

View File

@ -0,0 +1,131 @@
package sites
import (
"context"
"furtuna-be/internal/common/logger"
"furtuna-be/internal/entity"
"go.uber.org/zap"
"gorm.io/gorm"
)
type SiteRepository struct {
db *gorm.DB
}
func NewSiteRepository(db *gorm.DB) *SiteRepository {
return &SiteRepository{
db: db,
}
}
func (r *SiteRepository) Upsert(ctx context.Context, site *entity.Site) (*entity.Site, error) {
err := r.db.Transaction(func(tx *gorm.DB) error {
if site.ID != 0 {
// Update site
if err := tx.Save(site).Error; err != nil {
return err
}
} else {
// Create new site
if err := tx.Create(site).Error; err != nil {
return err
}
}
if len(site.Products) > 0 {
for i := range site.Products {
site.Products[i].SiteID = site.ID
if site.Products[i].ID != 0 {
// Update existing product
if err := tx.Save(&site.Products[i]).Error; err != nil {
return err
}
} else {
// Create new product
if err := tx.Create(&site.Products[i]).Error; err != nil {
return err
}
}
}
}
return nil
})
if err != nil {
logger.ContextLogger(ctx).Error("error when upserting site", zap.Error(err))
return nil, err
}
return site, nil
}
func (r *SiteRepository) Create(ctx context.Context, site *entity.SiteDB) (*entity.SiteDB, error) {
err := r.db.Create(site).Error
if err != nil {
logger.ContextLogger(ctx).Error("error when creating site", zap.Error(err))
return nil, err
}
return site, nil
}
func (r *SiteRepository) Update(ctx context.Context, site *entity.SiteDB) (*entity.SiteDB, error) {
if err := r.db.Save(site).Error; err != nil {
logger.ContextLogger(ctx).Error("error when updating site", zap.Error(err))
return nil, err
}
return site, nil
}
func (r *SiteRepository) GetByID(ctx context.Context, id int64) (*entity.SiteDB, error) {
site := new(entity.SiteDB)
if err := r.db.First(site, id).Error; err != nil {
logger.ContextLogger(ctx).Error("error when getting site by ID", zap.Error(err))
return nil, err
}
return site, nil
}
func (r *SiteRepository) GetAll(ctx context.Context, req entity.SiteSearch) (entity.SiteList, int, error) {
var sites []*entity.SiteDB
var total int64
query := r.db
query = query.Where("deleted_at IS NULL")
if req.Search != "" {
query = query.Where("name ILIKE ?", "%"+req.Search+"%")
}
if req.Name != "" {
query = query.Where("name ILIKE ?", "%"+req.Name+"%")
}
if req.Limit > 0 {
query = query.Limit(req.Limit)
}
if req.Offset > 0 {
query = query.Offset(req.Offset)
}
if err := query.Find(&sites).Error; err != nil {
logger.ContextLogger(ctx).Error("error when getting all sites", zap.Error(err))
return nil, 0, err
}
if err := r.db.Model(&entity.SiteDB{}).Where(query).Count(&total).Error; err != nil {
logger.ContextLogger(ctx).Error("error when counting sites", zap.Error(err))
return nil, 0, err
}
return sites, int(total), nil
}
func (r *SiteRepository) Delete(ctx context.Context, id int64) error {
site := new(entity.SiteDB)
site.ID = id
if err := r.db.Delete(site).Error; err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,33 @@
package trx
import (
"context"
"database/sql"
"gorm.io/gorm"
)
type GormTransactionManager struct {
db *gorm.DB
}
func NewGormTransactionManager(db *gorm.DB) *GormTransactionManager {
return &GormTransactionManager{
db: db,
}
}
func (tm *GormTransactionManager) Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error) {
tx := tm.db.Begin(opts...)
if tx.Error != nil {
return nil, tx.Error
}
return tx, nil
}
func (tm *GormTransactionManager) Commit(tx *gorm.DB) *gorm.DB {
return tx.Commit()
}
func (tm *GormTransactionManager) Rollback(tx *gorm.DB) *gorm.DB {
return tx.Rollback()
}

View File

@ -51,6 +51,29 @@ func (r *UserRepository) Create(ctx context.Context, user *entity.UserDB) (*enti
return user, nil return user, nil
} }
func (r *UserRepository) CreateWithTx(ctx context.Context, tx *gorm.DB, user *entity.UserDB) (*entity.UserDB, error) {
user.ID = 0
if err := tx.Select("name", "email", "password", "status", "created_by", "nik", "user_type", "phone_number").Create(user).Error; err != nil {
logError(ctx, "creating user", err)
return nil, err
}
if err := tx.First(user, user.ID).Error; err != nil {
tx.Rollback()
logError(ctx, "retrieving user", err)
return nil, err
}
userRole := user.ToUserRoleDB()
if err := tx.Create(userRole).Error; err != nil {
tx.Rollback()
logError(ctx, "creating user role", err)
return nil, err
}
return user, nil
}
func (b *UserRepository) GetAllUsers(ctx context.Context, req entity.UserSearch) (entity.UserList, int, error) { func (b *UserRepository) GetAllUsers(ctx context.Context, req entity.UserSearch) (entity.UserList, int, error) {
var users []*entity.UserDB var users []*entity.UserDB
var total int64 var total int64

View File

@ -0,0 +1,65 @@
package repository
import (
"context"
"furtuna-be/internal/common/logger"
"furtuna-be/internal/entity"
"go.uber.org/zap"
"gorm.io/gorm"
)
type WalletRepository struct {
db *gorm.DB
}
func NewWalletRepository(db *gorm.DB) *WalletRepository {
return &WalletRepository{
db: db,
}
}
func (r *WalletRepository) Create(ctx context.Context, tx *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error) {
err := tx.Create(wallet).Error
if err != nil {
logger.ContextLogger(ctx).Error("error when creating wallet", zap.Error(err))
return nil, err
}
return wallet, nil
}
func (r *WalletRepository) Update(ctx context.Context, wallet *entity.Wallet) (*entity.Wallet, error) {
if err := r.db.Save(wallet).Error; err != nil {
logger.ContextLogger(ctx).Error("error when updating wallet", zap.Error(err))
return nil, err
}
return wallet, nil
}
func (r *WalletRepository) GetByID(ctx context.Context, id int64) (*entity.Wallet, error) {
wallet := new(entity.Wallet)
if err := r.db.First(wallet, id).Error; err != nil {
logger.ContextLogger(ctx).Error("error when getting wallet by id", zap.Error(err))
return nil, err
}
return wallet, nil
}
func (r *WalletRepository) GetAll(ctx context.Context) ([]*entity.Wallet, error) {
var wallets []*entity.Wallet
if err := r.db.Find(&wallets).Error; err != nil {
logger.ContextLogger(ctx).Error("error when getting all wallets", zap.Error(err))
return nil, err
}
return wallets, nil
}
func (r *WalletRepository) Delete(ctx context.Context, id int64) error {
wallet := new(entity.Wallet)
wallet.ID = id
if err := r.db.Delete(wallet).Error; err != nil {
logger.ContextLogger(ctx).Error("error when deleting wallet", zap.Error(err))
return err
}
return nil
}

View File

@ -6,6 +6,7 @@ import (
"furtuna-be/internal/handlers/http/oss" "furtuna-be/internal/handlers/http/oss"
"furtuna-be/internal/handlers/http/partner" "furtuna-be/internal/handlers/http/partner"
"furtuna-be/internal/handlers/http/product" "furtuna-be/internal/handlers/http/product"
site "furtuna-be/internal/handlers/http/sites"
"furtuna-be/internal/handlers/http/studio" "furtuna-be/internal/handlers/http/studio"
"furtuna-be/internal/handlers/http/user" "furtuna-be/internal/handlers/http/user"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
@ -52,6 +53,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
order.NewHandler(serviceManager.OrderSvc), order.NewHandler(serviceManager.OrderSvc),
oss.NewOssHandler(serviceManager.OSSSvc), oss.NewOssHandler(serviceManager.OSSSvc),
partner.NewHandler(serviceManager.PartnerSvc), partner.NewHandler(serviceManager.PartnerSvc),
site.NewHandler(serviceManager.SiteSvc),
} }
for _, handler := range serverRoutes { for _, handler := range serverRoutes {

View File

@ -6,31 +6,68 @@ import (
"furtuna-be/internal/common/mycontext" "furtuna-be/internal/common/mycontext"
"furtuna-be/internal/entity" "furtuna-be/internal/entity"
"furtuna-be/internal/repository" "furtuna-be/internal/repository"
"furtuna-be/internal/services/users"
"go.uber.org/zap" "go.uber.org/zap"
) )
type PartnerService struct { type PartnerService struct {
repo repository.PartnerRepository repo repository.PartnerRepository
trx repository.TransactionManager
userSvc *users.UserService
walletRepo repository.WalletRepository
} }
func NewPartnerService(repo repository.PartnerRepository) *PartnerService { func NewPartnerService(repo repository.PartnerRepository,
userSvc *users.UserService, repoManager repository.TransactionManager,
walletRepo repository.WalletRepository) *PartnerService {
return &PartnerService{ return &PartnerService{
repo: repo, repo: repo,
userSvc: userSvc,
trx: repoManager,
walletRepo: walletRepo,
} }
} }
func (s *PartnerService) Create(ctx mycontext.Context, PartnerReq *entity.Partner) (*entity.Partner, error) { func (s *PartnerService) Create(ctx mycontext.Context, partnerReq *entity.CreatePartnerRequest) (*entity.Partner, error) {
PartnerDB := PartnerReq.ToPartnerDB() var err error
PartnerDB.CreatedBy = ctx.RequestedBy()
PartnerDB, err := s.repo.Create(ctx, PartnerDB) tx, err := s.trx.Begin(ctx)
if err != nil { if err != nil {
logger.ContextLogger(ctx).Error("error when create Partner", zap.Error(err)) logger.ContextLogger(ctx).Error("error when starting transaction", zap.Error(err))
return nil, err return nil, err
} }
return PartnerDB.ToPartner(), nil defer func() {
if r := recover(); r != nil {
s.trx.Rollback(tx)
panic(r)
} else if err != nil {
s.trx.Rollback(tx)
} else {
err = s.trx.Commit(tx).Error
}
}()
// Create Partner
partnerDB := partnerReq.ToPartnerDB(ctx.RequestedBy())
if partnerDB, err = s.repo.CreateWithTx(ctx, tx, partnerDB); err != nil {
logger.ContextLogger(ctx).Error("error when creating partner", zap.Error(err))
return nil, err
}
adminUser := partnerReq.ToUserAdmin(partnerDB.ID)
if adminUser, err = s.userSvc.CreateWithTx(ctx, tx, adminUser); err != nil {
logger.ContextLogger(ctx).Error("error when creating admin user", zap.Error(err))
return nil, err
}
partnerWallet := partnerReq.ToWallet(partnerDB.ID)
if partnerWallet, err = s.walletRepo.Create(ctx, tx, partnerWallet); err != nil {
logger.ContextLogger(ctx).Error("error when creating wallet", zap.Error(err))
return nil, err
}
return partnerDB.ToPartner(), nil
} }
func (s *PartnerService) Update(ctx mycontext.Context, id int64, PartnerReq *entity.Partner) (*entity.Partner, error) { func (s *PartnerService) Update(ctx mycontext.Context, id int64, PartnerReq *entity.Partner) (*entity.Partner, error) {

View File

@ -8,8 +8,10 @@ import (
"furtuna-be/internal/services/oss" "furtuna-be/internal/services/oss"
"furtuna-be/internal/services/partner" "furtuna-be/internal/services/partner"
"furtuna-be/internal/services/product" "furtuna-be/internal/services/product"
"furtuna-be/internal/services/sites"
"furtuna-be/internal/services/studio" "furtuna-be/internal/services/studio"
"furtuna-be/internal/services/users" "furtuna-be/internal/services/users"
"gorm.io/gorm"
"furtuna-be/config" "furtuna-be/config"
"furtuna-be/internal/entity" "furtuna-be/internal/entity"
@ -28,6 +30,7 @@ type ServiceManagerImpl struct {
OrderSvc Order OrderSvc Order
OSSSvc OSSService OSSSvc OSSService
PartnerSvc Partner PartnerSvc Partner
SiteSvc Site
} }
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
@ -40,7 +43,9 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
ProductSvc: product.NewProductService(repo.Product), ProductSvc: product.NewProductService(repo.Product),
OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Studio), OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Studio),
OSSSvc: oss.NewOSSService(repo.OSS), OSSSvc: oss.NewOSSService(repo.OSS),
PartnerSvc: partner.NewPartnerService(repo.Partner), PartnerSvc: partner.NewPartnerService(
repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet),
SiteSvc: site.NewSiteService(repo.Site),
} }
} }
@ -58,6 +63,7 @@ type Event interface {
type User interface { type User interface {
Create(ctx mycontext.Context, userReq *entity.User) (*entity.User, error) Create(ctx mycontext.Context, userReq *entity.User) (*entity.User, error)
CreateWithTx(ctx mycontext.Context, tx *gorm.DB, userReq *entity.User) (*entity.User, error)
GetAll(ctx mycontext.Context, search entity.UserSearch) ([]*entity.User, int, error) GetAll(ctx mycontext.Context, search entity.UserSearch) ([]*entity.User, int, error)
Update(ctx mycontext.Context, id int64, userReq *entity.User) (*entity.User, error) Update(ctx mycontext.Context, id int64, userReq *entity.User) (*entity.User, error)
GetByID(ctx mycontext.Context, id int64) (*entity.User, error) GetByID(ctx mycontext.Context, id int64) (*entity.User, error)
@ -102,9 +108,17 @@ type OSSService interface {
} }
type Partner interface { type Partner interface {
Create(ctx mycontext.Context, branchReq *entity.Partner) (*entity.Partner, error) Create(ctx mycontext.Context, branchReq *entity.CreatePartnerRequest) (*entity.Partner, error)
Update(ctx mycontext.Context, id int64, branchReq *entity.Partner) (*entity.Partner, error) Update(ctx mycontext.Context, id int64, branchReq *entity.Partner) (*entity.Partner, error)
GetByID(ctx context.Context, id int64) (*entity.Partner, error) GetByID(ctx context.Context, id int64) (*entity.Partner, error)
GetAll(ctx context.Context, search entity.PartnerSearch) ([]*entity.Partner, int, error) GetAll(ctx context.Context, search entity.PartnerSearch) ([]*entity.Partner, int, error)
Delete(ctx mycontext.Context, id int64) error Delete(ctx mycontext.Context, id int64) error
} }
type Site interface {
Create(ctx mycontext.Context, branchReq *entity.Site) (*entity.Site, error)
Update(ctx mycontext.Context, id int64, branchReq *entity.Site) (*entity.Site, error)
GetByID(ctx context.Context, id int64) (*entity.Site, error)
GetAll(ctx context.Context, search entity.SiteSearch) ([]*entity.Site, int, error)
Delete(ctx mycontext.Context, id int64) error
}

View File

@ -0,0 +1,89 @@
package site
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 SiteService struct {
repo repository.SiteRepository
}
func NewSiteService(repo repository.SiteRepository) *SiteService {
return &SiteService{
repo: repo,
}
}
func (s *SiteService) Create(ctx mycontext.Context, siteReq *entity.Site) (*entity.Site, error) {
siteDB := siteReq
siteDB.CreatedBy = ctx.RequestedBy()
siteDB, err := s.repo.Upsert(ctx, siteDB)
if err != nil {
logger.ContextLogger(ctx).Error("error when creating site", zap.Error(err))
return nil, err
}
return siteDB, nil
}
func (s *SiteService) Update(ctx mycontext.Context, id int64, siteReq *entity.Site) (*entity.Site, error) {
existingSite, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
existingSite.ToUpdatedSite(ctx.RequestedBy(), *siteReq)
updatedSiteDB, err := s.repo.Update(ctx, existingSite.ToSiteDB())
if err != nil {
logger.ContextLogger(ctx).Error("error when updating site", zap.Error(err))
return nil, err
}
return updatedSiteDB.ToSite(), nil
}
func (s *SiteService) GetByID(ctx context.Context, id int64) (*entity.Site, error) {
siteDB, err := s.repo.GetByID(ctx, id)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting site by ID", zap.Error(err))
return nil, err
}
return siteDB.ToSite(), nil
}
func (s *SiteService) GetAll(ctx context.Context, search entity.SiteSearch) ([]*entity.Site, int, error) {
sites, total, err := s.repo.GetAll(ctx, search)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting all sites", zap.Error(err))
return nil, 0, err
}
return sites.ToSiteList(), total, nil
}
func (s *SiteService) Delete(ctx mycontext.Context, id int64) error {
siteDB, err := s.repo.GetByID(ctx, id)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting site by ID", zap.Error(err))
return err
}
siteDB.SetDeleted(ctx.RequestedBy())
_, err = s.repo.Update(ctx, siteDB)
if err != nil {
logger.ContextLogger(ctx).Error("error when updating site", zap.Error(err))
return err
}
return nil
}

View File

@ -3,6 +3,7 @@ package users
import ( import (
"fmt" "fmt"
"furtuna-be/internal/common/mycontext" "furtuna-be/internal/common/mycontext"
"gorm.io/gorm"
"go.uber.org/zap" "go.uber.org/zap"
@ -48,6 +49,31 @@ func (s *UserService) Create(ctx mycontext.Context, userReq *entity.User) (*enti
return userDB.ToUser(), nil return userDB.ToUser(), nil
} }
func (s *UserService) CreateWithTx(ctx mycontext.Context, tx *gorm.DB, userReq *entity.User) (*entity.User, error) {
//check
userExist, err := s.repo.GetUserByEmail(ctx, userReq.Email)
if err != nil {
return nil, err
}
if userExist != nil {
return nil, fmt.Errorf("Email already exist")
}
userDB, err := userReq.ToUserDB(ctx.RequestedBy())
if err != nil {
return nil, err
}
userDB, err = s.repo.CreateWithTx(ctx, tx, userDB)
if err != nil {
logger.ContextLogger(ctx).Error("error when create user", zap.Error(err))
return nil, err
}
return userDB.ToUser(), nil
}
func (s *UserService) GetAll(ctx mycontext.Context, search entity.UserSearch) ([]*entity.User, int, error) { func (s *UserService) GetAll(ctx mycontext.Context, search entity.UserSearch) ([]*entity.User, int, error) {
users, total, err := s.repo.GetAllUsers(ctx, search) users, total, err := s.repo.GetAllUsers(ctx, search)
@ -82,7 +108,7 @@ func (s *UserService) Update(ctx mycontext.Context, id int64, userReq *entity.Us
return nil, err return nil, err
} }
existingUser.BranchName = branch.Name existingUser.PartnerName = branch.Name
} }
err = existingUser.ToUpdatedUser(*userReq) err = existingUser.ToUpdatedUser(*userReq)

View File

@ -3,4 +3,9 @@ CREATE TABLE roles (
role_name VARCHAR(255) NOT NULL role_name VARCHAR(255) NOT NULL
); );
INSERT INTO roles(role_id, role_name) VALUES (1, 'super_admin'), (2, 'branch_admin'); INSERT INTO roles(role_id, role_name)
VALUES (1, 'super_admin'),
(2, 'admin'),
(3, 'admin_partner'),
(4, 'admin_site'),
(5, 'cashier');

View File

@ -2,11 +2,15 @@ CREATE TABLE partners
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
status varchar, status VARCHAR(50),
Address varchar, license_expired_date DATE,
address VARCHAR(255),
bank_name VARCHAR(255),
bank_account_number VARCHAR(50),
bank_account_holder_name VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER, created_by INTEGER,
updated_by INTEGER, updated_by INTEGER,
deleted_at timestamp deleted_at TIMESTAMP
); );

View File

@ -1,10 +1,13 @@
CREATE TABLE products CREATE TABLE products
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
partner_id INTEGER,
site_id INTEGER, site_id INTEGER,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
type VARCHAR, type VARCHAR,
prices INTEGER[], price decimal,
is_weekend_ticket boolean,
is_season_ticket boolean,
status VARCHAR, status VARCHAR,
description VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_at TIMESTAMP NOT NULL DEFAULT NOW(),

View File

View File

@ -0,0 +1,11 @@
CREATE TABLE public.wallets
(
id SERIAL PRIMARY KEY,
partner_id INTEGER NOT NULL,
balance DECIMAL(18, 2) NOT NULL DEFAULT 0.00,
currency VARCHAR(3) NOT NULL,
status VARCHAR(50),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
FOREIGN KEY (partner_id) REFERENCES partners (id) ON DELETE CASCADE
);