feat: Implement cascading delete constraints for maintenance group entities and enhance service methods for role-user relationships
Deploy Application / deploy (push) Successful in 19s Details

This commit is contained in:
Habib Fatkhul Rohman 2025-10-25 21:00:03 +07:00
parent 28d07ea2b0
commit f9c5dc3f63
7 changed files with 179 additions and 36 deletions

View File

@ -7,10 +7,12 @@ type M_MaintenanceGroup struct {
Code string `gorm:"type:varchar(50);uniqueIndex;not null" json:"code"` Code string `gorm:"type:varchar(50);uniqueIndex;not null" json:"code"`
Name string `gorm:"type:varchar(100);not null" json:"name"` Name string `gorm:"type:varchar(100);not null" json:"name"`
Description string `gorm:"type:text" json:"description"` Description string `gorm:"type:text" json:"description"`
ClientID uuid.UUID `gorm:"type:uuid;not null;index" json:"client_id"` // ClientID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;" json:"client_id"`
// Client M_Client `gorm:"foreignKey:ClientID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
ClientID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;" json:"client_id"`
Client M_Client `gorm:"foreignKey:ClientID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Client M_Client `gorm:"foreignKey:ClientID;references:ID"`
MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"`
FullAuditTrail FullAuditTrail
} }

View File

@ -4,13 +4,12 @@ import "github.com/google/uuid"
type M_MaintenanceGroupRole struct { type M_MaintenanceGroupRole struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"` ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"`
MaintenanceGroupID uuid.UUID `gorm:"type:uuid;not null;index"` MaintenanceGroupID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
RoleID uuid.UUID `gorm:"type:uuid;not null;index"` RoleID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Level *int `gorm:"type:int;not null;index"` Level *int `gorm:"type:int;not null;index"`
MaintenanceGroup M_MaintenanceGroup `gorm:"foreignKey:MaintenanceGroupID;references:ID"` MaintenanceGroup M_MaintenanceGroup `gorm:"foreignKey:MaintenanceGroupID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
MaintenanceGroupRoleUsers []M_MaintenanceGroupRoleUser `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` MaintenanceGroupRoleUsers []M_MaintenanceGroupRoleUser `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE,onUpdate:CASCADE;"`
Role M_Role `gorm:"foreignKey:RoleID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` Role M_Role `gorm:"foreignKey:RoleID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
FullAuditTrail FullAuditTrail
} }

View File

@ -4,11 +4,11 @@ import "github.com/google/uuid"
type M_MaintenanceGroupRoleUser struct { type M_MaintenanceGroupRoleUser struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"` ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"`
MaintenanceGroupRoleID uuid.UUID `gorm:"type:uuid;not null;index" json:"maintenance_group_role_id"` MaintenanceGroupRoleID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,onUpdate:CASCADE;" json:"maintenance_group_role_id"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"` UserID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,onUpdate:CASCADE;" json:"user_id"`
MaintenanceGroupRole M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` MaintenanceGroupRole M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE,onUpdate:CASCADE;"`
User M_User `gorm:"foreignKey:UserID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` User M_User `gorm:"foreignKey:UserID;references:ID;constraint:OnDelete:CASCADE,onUpdate:CASCADE;"`
FullAuditTrail FullAuditTrail
} }

View File

@ -17,8 +17,8 @@ type M_Role struct {
UserRoles []M_User_Role `gorm:"foreignKey:RoleID;references:ID" json:"user_roles"` UserRoles []M_User_Role `gorm:"foreignKey:RoleID;references:ID" json:"user_roles"`
Users []M_User `gorm:"many2many:m_user_roles;" json:"users"` Users []M_User `gorm:"many2many:m_user_roles;" json:"users"`
Permissions []M_Permissions `gorm:"many2many:m_role_permissions;joinForeignKey:RoleID;JoinReferences:PermissionID" json:"permissions"` Permissions []M_Permissions `gorm:"many2many:m_role_permissions;joinForeignKey:RoleID;JoinReferences:PermissionID" json:"permissions"`
MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:RoleID;references:ID" json:"maintenance_group_roles"` MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:RoleID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;" json:"maintenance_group_roles"`
FullAuditTrail FullAuditTrail
} }

View File

@ -4,9 +4,11 @@ import (
"net/http" "net/http"
"github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/dto" "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/service" "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/service"
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants" "github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils" "github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"github.com/Caknoooo/go-pagination"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/samber/do" "github.com/samber/do"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -150,12 +152,37 @@ func (c *maintenanceGroupController) GetById(ctx *gin.Context) {
// @Failure 400 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{}
// @Router /maintenance-groups [get] // @Router /maintenance-groups [get]
func (c *maintenanceGroupController) GetAll(ctx *gin.Context) { func (c *maintenanceGroupController) GetAll(ctx *gin.Context) {
// result, err := c.maintGroupService.GetAll(ctx.Request.Context()) clientId := ctx.MustGet("client_id").(string)
// if err != nil { var filter = &query.MaintenanceGroupFilter{
// res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_LIST_MG, err.Error(), nil) Name: ctx.Query("name"),
// ctx.JSON(http.StatusBadRequest, res) Code: ctx.Query("code"),
// return Description: ctx.Query("description"),
// } ClientID: clientId,
// res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_LIST_MG, result) Includes: ctx.QueryArray("includes"),
// ctx.JSON(http.StatusOK, res) }
filter.BindPagination(ctx)
ctx.ShouldBindQuery(filter)
logrus.Infof("pagination: page=%d, page_size=%d", filter.Pagination.Page, filter.Pagination.PerPage)
logrus.Info("1")
groups, total, err := pagination.PaginatedQueryWithIncludableAndOptions[query.M_MaintenanceGroup](
c.db,
filter,
pagination.PaginatedQueryOptions{
EnableSoftDelete: true,
// Dialect: pagination.MySQL, // atau PostgreSQL jika perlu
},
)
logrus.Info("total:", total, " len groups:", len(groups))
logrus.Info("2")
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_LIST_MG, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
logrus.Info("3")
// groupResponses := dto.ToMaintGroupResponses(groups)
paginationResponse := pagination.CalculatePagination(filter.Pagination, total)
response := pagination.NewPaginatedResponse(http.StatusOK, dto.MESSAGE_SUCCESS_GET_LIST_MG, groups, paginationResponse)
ctx.JSON(http.StatusOK, response)
} }

View File

@ -1 +1,84 @@
package query package query
import (
"github.com/Caknoooo/go-pagination"
"gorm.io/gorm"
)
type M_MaintenanceGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
TotalMaintenanceGroupRole int `json:"total_maintenance_group_role"`
}
type MaintenanceGroupFilter struct {
pagination.BaseFilter
Name string `form:"name"` // tambahkan ini
Code string `form:"code"` // tambahkan ini
Description string `form:"description"` // tambahkan ini
ClientID string `form:"client_id"` // tambahkan ini
Includes []string `form:"includes"` // tambahkan ini
}
func (f *MaintenanceGroupFilter) ApplyFilters(query *gorm.DB) *gorm.DB {
// Apply your filters here
if f.Name != "" {
query = query.Where("name ILIKE ?", "%"+f.Name+"%")
}
if f.ClientID != "" {
query = query.Where("client_id = ?", f.ClientID)
}
// Manual preload untuk roles dengan field terbatas
// for _, include := range f.Includes {
// if include == "Roles" {
// query = query.Preload("Roles", func(db *gorm.DB) *gorm.DB {
// return db.Select("id", "name") // Hanya ambil id dan name
// })
// }
// }
// Hitung total maintenance group role per group
query = query.Select("m_maintenance_groups.*, " +
"(SELECT COUNT(*) FROM m_maintenance_group_roles mgr WHERE mgr.maintenance_group_id = m_maintenance_groups.id AND mgr.deleted_at IS NULL) as total_maintenance_group_role")
return query
}
func (f *MaintenanceGroupFilter) GetTableName() string {
return "m_maintenance_groups"
}
func (f *MaintenanceGroupFilter) GetSearchFields() []string {
return []string{"name"}
}
func (f *MaintenanceGroupFilter) GetDefaultSort() string {
return "id asc"
}
func (f *MaintenanceGroupFilter) GetIncludes() []string {
return f.Includes
}
func (f *MaintenanceGroupFilter) GetPagination() pagination.PaginationRequest {
return f.Pagination
}
func (f *MaintenanceGroupFilter) Validate() {
var validIncludes []string
allowedIncludes := f.GetAllowedIncludes()
for _, include := range f.Includes {
if allowedIncludes[include] {
validIncludes = append(validIncludes, include)
}
}
f.Includes = validIncludes
}
func (f *MaintenanceGroupFilter) GetAllowedIncludes() map[string]bool {
return map[string]bool{
"Client": true,
}
}

View File

@ -12,7 +12,7 @@ import (
) )
type MaintenanceGroupService interface { type MaintenanceGroupService interface {
Create(ctx context.Context, maintGroup dto.MaintGroupCreateRequest) (dto.MaintGroupCreateRequest, error) Create(ctx context.Context, maintGroup dto.MaintGroupCreateRequest) (dto.MaintGroupResponse, error)
GetById(ctx context.Context, maintGroupId string) (dto.MaintGroupResponse, error) GetById(ctx context.Context, maintGroupId string) (dto.MaintGroupResponse, error)
GetByName(ctx context.Context, name string) (dto.MaintGroupResponse, error) GetByName(ctx context.Context, name string) (dto.MaintGroupResponse, error)
GetByCode(ctx context.Context, code string) (dto.MaintGroupResponse, error) GetByCode(ctx context.Context, code string) (dto.MaintGroupResponse, error)
@ -29,7 +29,7 @@ type maintenanceGroupService struct {
} }
// Create implements MaintenanceGroupService. // Create implements MaintenanceGroupService.
func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroupCreateRequest) (dto.MaintGroupCreateRequest, error) { func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroupCreateRequest) (dto.MaintGroupResponse, error) {
tx := m.db.Begin() tx := m.db.Begin()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -40,7 +40,7 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup
clientUUID, err := uuid.Parse(req.ClientID) clientUUID, err := uuid.Parse(req.ClientID)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
maintGroup := entities.M_MaintenanceGroup{ maintGroup := entities.M_MaintenanceGroup{
@ -52,14 +52,14 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup
createdGroup, err := m.maintGroupRepo.Create(ctx, tx, maintGroup) createdGroup, err := m.maintGroupRepo.Create(ctx, tx, maintGroup)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
for _, roleReq := range req.MaintenanceGroupRoles { for _, roleReq := range req.MaintenanceGroupRoles {
roleUUID, err := uuid.Parse(roleReq.RoleID) roleUUID, err := uuid.Parse(roleReq.RoleID)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
role := entities.M_MaintenanceGroupRole{ role := entities.M_MaintenanceGroupRole{
MaintenanceGroupID: createdGroup.ID, MaintenanceGroupID: createdGroup.ID,
@ -69,13 +69,13 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup
createdRole, err := m.maintGroupRoleRepo.Create(ctx, tx, role) createdRole, err := m.maintGroupRoleRepo.Create(ctx, tx, role)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
for _, userReq := range roleReq.MaintenanceGroupRoleUsers { for _, userReq := range roleReq.MaintenanceGroupRoleUsers {
userUUID, err := uuid.Parse(userReq.UserID) userUUID, err := uuid.Parse(userReq.UserID)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
user := entities.M_MaintenanceGroupRoleUser{ user := entities.M_MaintenanceGroupRoleUser{
MaintenanceGroupRoleID: createdRole.ID, MaintenanceGroupRoleID: createdRole.ID,
@ -84,7 +84,7 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup
_, err = m.maintGroupRoleUserRepo.Create(ctx, tx, user) _, err = m.maintGroupRoleUserRepo.Create(ctx, tx, user)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
} }
} }
@ -92,9 +92,14 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup
err = tx.Commit().Error err = tx.Commit().Error
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return dto.MaintGroupCreateRequest{}, err return dto.MaintGroupResponse{}, err
} }
return req, nil
result, err := m.GetById(ctx, createdGroup.ID.String())
if err != nil {
return dto.MaintGroupResponse{}, err
}
return result, nil
} }
// Delete implements MaintenanceGroupService. // Delete implements MaintenanceGroupService.
@ -105,11 +110,38 @@ func (m *maintenanceGroupService) Delete(ctx context.Context, maintGroupId strin
tx.Rollback() tx.Rollback()
} }
}() }()
err := m.maintGroupRepo.Delete(ctx, tx, maintGroupId)
// 1. Ambil semua role dari group
roles, err := m.maintGroupRoleRepo.FindByMaintGroupId(ctx, tx, maintGroupId)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return err return err
} }
// 2. Hapus semua user dari setiap role
for _, role := range roles {
err := m.maintGroupRoleUserRepo.DeleteByMaintGroupRoleID(ctx, tx, role.ID.String())
if err != nil {
tx.Rollback()
return err
}
}
// 3. Hapus semua role dari group
err = m.maintGroupRoleRepo.DeleteByMaintGroupId(ctx, tx, maintGroupId)
if err != nil {
tx.Rollback()
return err
}
// 4. Hapus group
err = m.maintGroupRepo.Delete(ctx, tx, maintGroupId)
if err != nil {
tx.Rollback()
return err
}
// 5. Commit
err = tx.Commit().Error err = tx.Commit().Error
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()