881 lines
27 KiB
Go
881 lines
27 KiB
Go
package permission
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"service/pkg/errors"
|
|
"service/pkg/logger"
|
|
"service/pkg/utils/query"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
// Import pages entity untuk referensi
|
|
"service/internal/infrastructure/cache"
|
|
rolePages "service/internal/master/role/pages"
|
|
)
|
|
|
|
type Service interface {
|
|
GetList(ctx context.Context, limit, offset int, sorts []string, activeFilter *bool) (map[string]interface{}, error)
|
|
GetDetail(ctx context.Context, id int64) (*RolPermissionResponse, error)
|
|
Search(ctx context.Context, filters map[string]interface{}, sorts []string, limit, offset int) (map[string]interface{}, error)
|
|
Create(ctx context.Context, req RolPermissionRequest) (*RolPermissionResponse, error)
|
|
Update(ctx context.Context, id int64, req RolPermissionRequest) (*RolPermissionResponse, error)
|
|
Delete(ctx context.Context, id int64) error
|
|
GetByRoleAndGroup(ctx context.Context, roleKeycloak string, groupKeycloak []string, fkRolPagesId int64) (*RolPermissionResponse, error)
|
|
GetByRoleAndPages(ctx context.Context, roleKeycloak string, fkRolPagesIds []int64) ([]*RolPermissionResponse, error)
|
|
GetRolePermissionTree(ctx context.Context, roleKeycloak string, groupKeycloak []string, activeOnly *bool) (*RolePermissionTreeResponse, error)
|
|
GetRolePermissionTreeRole(ctx context.Context, roleKeycloak string, activeOnly *bool) (*RolePermissionTreeResponse, error)
|
|
GetRolePagesList(ctx context.Context, roleKeycloak string, limit, offset int, activeOnly *bool) (map[string]interface{}, error)
|
|
}
|
|
|
|
type service struct {
|
|
cmdRepo CommandRepository
|
|
queryRepo QueryRepository
|
|
cache *cache.Manager
|
|
}
|
|
|
|
func NewService(cmdRepo CommandRepository, queryRepo QueryRepository, pagesRepo rolePages.QueryRepository, cacheManager *cache.Manager) Service {
|
|
return &service{cmdRepo: cmdRepo, queryRepo: queryRepo, cache: cacheManager}
|
|
}
|
|
|
|
func (s *service) GetList(ctx context.Context, limit, offset int, sorts []string, activeFilter *bool) (map[string]interface{}, error) {
|
|
filters := map[string]interface{}{}
|
|
if activeFilter != nil {
|
|
filters["active"] = *activeFilter
|
|
}
|
|
|
|
// Gunakan kapabilitas method Search agar filter dynamic & cache ikut ter-handle dengan efisien
|
|
return s.Search(ctx, filters, sorts, limit, offset)
|
|
}
|
|
|
|
func (s *service) GetDetail(ctx context.Context, id int64) (*RolPermissionResponse, error) {
|
|
if id <= 0 {
|
|
return nil, errors.NewValidationError().Message("Invalid ID").Metadata("id", id).Build()
|
|
}
|
|
|
|
entity, err := s.queryRepo.FindByID(ctx, id)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve RolPermission detail").Cause(err).Build()
|
|
}
|
|
if entity == nil {
|
|
return nil, errors.NotFoundError().Message("RolPermission not found").Metadata("id", id).Build()
|
|
}
|
|
|
|
return mapEntityToResponse(entity), nil
|
|
}
|
|
|
|
func (s *service) Search(ctx context.Context, filters map[string]interface{}, sorts []string, limit, offset int) (map[string]interface{}, error) {
|
|
if limit == 0 {
|
|
limit = 10
|
|
} else if limit < -1 || limit > 100 {
|
|
limit = 10
|
|
}
|
|
if offset < 0 {
|
|
offset = 0
|
|
}
|
|
|
|
// --- 1. Implementasi Caching (Check) ---
|
|
filterBytes, _ := json.Marshal(filters)
|
|
sortBytes, _ := json.Marshal(sorts)
|
|
// Membuat string gabungan dari filter, sort, dan pagination, lalu di-hash dengan SHA-256
|
|
hashInput := fmt.Sprintf("%s|%s|%d|%d", string(filterBytes), string(sortBytes), limit, offset)
|
|
hash := sha256.Sum256([]byte(hashInput))
|
|
cacheKey := fmt.Sprintf("role_permission_search_v2:%s", hex.EncodeToString(hash[:]))
|
|
|
|
if s.cache != nil {
|
|
var strData string
|
|
if err := s.cache.Get(ctx, cacheKey, &strData); err == nil && strData != "" {
|
|
var cachedData struct {
|
|
Data []*RolPermissionResponse `json:"data"`
|
|
Total int64 `json:"total"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
}
|
|
if err := json.Unmarshal([]byte(strData), &cachedData); err == nil {
|
|
logger.Default().Debug("Cache hit for Role Permission Search", logger.String("key", cacheKey))
|
|
return map[string]interface{}{
|
|
"data": cachedData.Data,
|
|
"total": cachedData.Total,
|
|
"limit": cachedData.Limit,
|
|
"offset": cachedData.Offset,
|
|
}, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Konversi sorts string ke SortField
|
|
var sortFields []query.SortField
|
|
for _, sort := range sorts {
|
|
if sort == "" {
|
|
continue
|
|
}
|
|
|
|
// Default ASC, tandai dengan - untuk DESC
|
|
order := "ASC"
|
|
column := sort
|
|
if strings.HasPrefix(sort, "-") {
|
|
order = "DESC"
|
|
column = strings.TrimPrefix(sort, "-")
|
|
} else if strings.HasPrefix(sort, "+") {
|
|
column = strings.TrimPrefix(sort, "+")
|
|
}
|
|
|
|
// Validasi kolom yang diizinkan (lowercase untuk mapping)
|
|
allowedColumns := map[string]bool{
|
|
"id": true,
|
|
"create": true,
|
|
"read": true,
|
|
"update": true,
|
|
"disable": true,
|
|
"delete": true,
|
|
"active": true,
|
|
"fk_rol_pages_id": true,
|
|
"role_keycloak": true,
|
|
"group_keycloak": true,
|
|
"created_at": true,
|
|
"role_master_name": true,
|
|
}
|
|
|
|
if allowedColumns[column] {
|
|
sortFields = append(sortFields, query.SortField{
|
|
Column: column,
|
|
Order: order,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Jika tidak ada sort yang valid, gunakan default
|
|
if len(sortFields) == 0 {
|
|
sortFields = []query.SortField{query.CreateAscSort("id")}
|
|
}
|
|
|
|
entities, total, err := s.queryRepo.Search(ctx, filters, sortFields, limit, offset)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to search RolPermissions").Cause(err).Build()
|
|
}
|
|
|
|
responses := make([]*RolPermissionResponse, len(entities))
|
|
for i, entity := range entities {
|
|
responses[i] = mapEntityToResponse(&entity)
|
|
}
|
|
|
|
responseMap := map[string]interface{}{
|
|
"data": responses, "total": total, "limit": limit, "offset": offset,
|
|
}
|
|
|
|
// --- 2. Implementasi Caching (Set) ---
|
|
if s.cache != nil {
|
|
if bytes, err := json.Marshal(responseMap); err == nil {
|
|
// TTL 5 menit untuk data search
|
|
_ = s.cache.Set(ctx, cacheKey, string(bytes), 5*time.Minute)
|
|
}
|
|
}
|
|
|
|
return responseMap, nil
|
|
}
|
|
|
|
func (s *service) Create(ctx context.Context, req RolPermissionRequest) (*RolPermissionResponse, error) {
|
|
// Validasi input
|
|
if req.FkRolPagesId <= 0 {
|
|
return nil, errors.NewValidationError().Message("Page ID is required").Metadata("fk_rol_pages_id", req.FkRolPagesId).Build()
|
|
}
|
|
if req.RoleKeycloak == "" && req.GroupKeycloak == "" {
|
|
return nil, errors.NewValidationError().Message("Role or Group must be provided").Build()
|
|
}
|
|
|
|
entity := mapRequestToEntity(req)
|
|
if err := s.cmdRepo.Create(ctx, entity); err != nil {
|
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
|
return nil, errors.AlreadyExistsError().Message("RolPermission with this identifier already exists").Cause(err).Build()
|
|
}
|
|
return nil, errors.InternalError().Message("Failed to create RolPermission").Cause(err).Build()
|
|
}
|
|
// Re-fetch to get the complete created entity
|
|
createdEntity, err := s.queryRepo.FindByID(ctx, entity.Id)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve newly created RolPermission").Cause(err).Build()
|
|
}
|
|
|
|
// Invalidate cache setelah create permission baru
|
|
if s.cache != nil {
|
|
// Perhatian: Jika cache.Manager Anda tidak mendukung wildcard Delete secara native,
|
|
// Anda mungkin perlu memanggil metode s.cache.DeleteByPrefix atau menghapus key spesifik.
|
|
cacheKeyPattern := fmt.Sprintf("permission_tree:%s:*", req.RoleKeycloak)
|
|
_ = s.cache.Delete(ctx, cacheKeyPattern)
|
|
// Hapus juga cache list agar data terbaru langsung tampil
|
|
_ = s.cache.Delete(ctx, "role_permission_list:*")
|
|
// Hapus juga cache search
|
|
_ = s.cache.Delete(ctx, "role_permission_search_v2:*")
|
|
}
|
|
|
|
return mapEntityToResponse(createdEntity), nil
|
|
}
|
|
|
|
func (s *service) Update(ctx context.Context, id int64, req RolPermissionRequest) (*RolPermissionResponse, error) {
|
|
if id <= 0 {
|
|
return nil, errors.NewValidationError().Message("Invalid ID").Metadata("id", id).Build()
|
|
}
|
|
|
|
// Cek apakah record ada
|
|
existing, err := s.queryRepo.FindByID(ctx, id)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve RolPermission").Cause(err).Build()
|
|
}
|
|
if existing == nil {
|
|
return nil, errors.NotFoundError().Message("RolPermission not found").Metadata("id", id).Build()
|
|
}
|
|
|
|
// Simpan role lama untuk invalidasi cache
|
|
oldRoleKeycloak := existing.RoleKeycloak
|
|
|
|
// Update entity dengan data dari request
|
|
// existing.Create = req.Create
|
|
// existing.Read = req.Read
|
|
// existing.Update = req.Update
|
|
// existing.Disable = req.Disable
|
|
// existing.Delete = req.Delete
|
|
// existing.Active = req.Active
|
|
// existing.FkRolPagesId = req.FkRolPagesId
|
|
// existing.RoleKeycloak = req.RoleKeycloak
|
|
// existing.GroupKeycloak = req.GroupKeycloak
|
|
|
|
if err := s.cmdRepo.Update(ctx, existing); err != nil {
|
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
|
return nil, errors.AlreadyExistsError().Message("RolPermission with this identifier already exists").Cause(err).Build()
|
|
}
|
|
return nil, errors.InternalError().Message("Failed to update RolPermission").Cause(err).Build()
|
|
}
|
|
|
|
// Invalidate cache
|
|
if s.cache != nil {
|
|
// Invalidate cache untuk role lama dan baru jika berbeda
|
|
if oldRoleKeycloak != "" {
|
|
_ = s.cache.Delete(ctx, fmt.Sprintf("permission_tree:%s:*", oldRoleKeycloak))
|
|
}
|
|
if existing.RoleKeycloak != "" && existing.RoleKeycloak != oldRoleKeycloak {
|
|
_ = s.cache.Delete(ctx, fmt.Sprintf("permission_tree:%s:*", existing.RoleKeycloak))
|
|
}
|
|
// Invalidate cache list dan search
|
|
_ = s.cache.Delete(ctx, "role_permission_list:*")
|
|
_ = s.cache.Delete(ctx, "role_permission_search_v2:*")
|
|
}
|
|
|
|
return mapEntityToResponse(existing), nil
|
|
}
|
|
|
|
func (s *service) Delete(ctx context.Context, id int64) error {
|
|
if id <= 0 {
|
|
return errors.NewValidationError().Message("Invalid ID").Metadata("id", id).Build()
|
|
}
|
|
|
|
// Cek apakah record ada untuk mendapatkan roleKeycloak sebelum dihapus
|
|
existing, err := s.queryRepo.FindByID(ctx, id)
|
|
if err != nil {
|
|
return errors.InternalError().Message("Failed to retrieve RolPermission before deletion").Cause(err).Build()
|
|
}
|
|
if existing == nil {
|
|
// Jika sudah tidak ada, anggap operasi berhasil (idempotent)
|
|
return nil
|
|
}
|
|
|
|
if err := s.cmdRepo.Delete(ctx, id); err != nil {
|
|
return errors.InternalError().Message("Failed to delete RolPermission").Cause(err).Build()
|
|
}
|
|
|
|
// Invalidate cache
|
|
if s.cache != nil {
|
|
if existing.RoleKeycloak != "" {
|
|
_ = s.cache.Delete(ctx, fmt.Sprintf("permission_tree:%s:*", existing.RoleKeycloak))
|
|
}
|
|
// Invalidate cache list dan search
|
|
_ = s.cache.Delete(ctx, "role_permission_list:*")
|
|
_ = s.cache.Delete(ctx, "role_permission_search_v2:*")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *service) GetByRoleAndGroup(ctx context.Context, roleKeycloak string, groupKeycloak []string, fkRolPagesId int64) (*RolPermissionResponse, error) {
|
|
if roleKeycloak == "" {
|
|
return nil, errors.NewValidationError().Message("Role must be provided").Build()
|
|
}
|
|
if fkRolPagesId <= 0 {
|
|
return nil, errors.NewValidationError().Message("Invalid page ID").Build()
|
|
}
|
|
|
|
entity, err := s.queryRepo.FindByRoleAndGroup(ctx, roleKeycloak, groupKeycloak)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve permission by role and group").Cause(err).Build()
|
|
}
|
|
// Cek jika tidak ada permission yang ditemukan
|
|
if len(entity) == 0 {
|
|
return nil, errors.NotFoundError().Message("Permission not found for role and group").Build()
|
|
}
|
|
|
|
// Untuk saat ini, kita kembalikan data pertama yang ditemukan.
|
|
// Anda mungkin perlu logika tambahan jika ada lebih dari satu hasil.
|
|
response := mapEntityToResponse(entity[0])
|
|
return response, nil
|
|
}
|
|
|
|
// Helper function to build page access tree
|
|
func buildPageAccessTree(pages []rolePages.RolPages, permissionMap map[int64]*RolPermission) []*PageAccess {
|
|
pageMap := make(map[int64]*PageAccess)
|
|
var roots []*PageAccess
|
|
|
|
// Convert all pages to PageAccess
|
|
for i := 0; i < len(pages); i++ {
|
|
page := &pages[i]
|
|
access := &PageAccess{
|
|
Id: page.Id,
|
|
Name: page.Name,
|
|
Icon: page.Icon,
|
|
URL: page.URL,
|
|
Level: page.Level,
|
|
Sort: page.Sort,
|
|
Active: page.Active,
|
|
}
|
|
|
|
// Add permission if exists
|
|
if perm, exists := permissionMap[page.Id]; exists {
|
|
access.Permission = &PermissionDetail{
|
|
Create: perm.Create,
|
|
Read: perm.Read,
|
|
Update: perm.Update,
|
|
Delete: perm.Delete,
|
|
Disable: perm.Disable,
|
|
}
|
|
access.Group = perm.GroupKeycloak
|
|
}
|
|
|
|
pageMap[page.Id] = access
|
|
}
|
|
|
|
// Build parent-child relationships
|
|
for i := range pages {
|
|
page := &pages[i]
|
|
if page.Parent == nil {
|
|
roots = append(roots, pageMap[page.Id])
|
|
} else {
|
|
if parent, exists := pageMap[*page.Parent]; exists {
|
|
parent.Children = append(parent.Children, pageMap[page.Id])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort children by sort order
|
|
for _, access := range pageMap {
|
|
if len(access.Children) > 0 {
|
|
sortPageAccessChildren(access.Children)
|
|
}
|
|
}
|
|
|
|
// Sort roots by sort order
|
|
sortPageAccessChildren(roots)
|
|
|
|
return roots
|
|
}
|
|
|
|
func sortPageAccessChildren(children []*PageAccess) {
|
|
for i := 0; i < len(children); i++ {
|
|
for j := i + 1; j < len(children); j++ {
|
|
if children[i].Sort > children[j].Sort {
|
|
children[i], children[j] = children[j], children[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// convertPageAccessToAccessTree converts []*PageAccess to []*AccessTreeItem
|
|
func convertPageAccessToAccessTree(pageAccess []*PageAccess) []*AccessTreeItem {
|
|
result := make([]*AccessTreeItem, len(pageAccess))
|
|
for i, access := range pageAccess {
|
|
result[i] = &AccessTreeItem{
|
|
ID: access.Id,
|
|
Name: access.Name,
|
|
Icon: access.Icon,
|
|
URL: access.URL,
|
|
Group: access.Group,
|
|
Level: int(access.Level),
|
|
Sort: int(access.Sort),
|
|
Active: access.Active,
|
|
Children: convertPageAccessToAccessTree(access.Children),
|
|
}
|
|
|
|
// Convert permission if exists
|
|
if access.Permission != nil {
|
|
result[i].Permission = &PermissionResponse{
|
|
Id: access.Id,
|
|
Create: access.Permission.Create,
|
|
Read: access.Permission.Read,
|
|
Update: access.Permission.Update,
|
|
Delete: access.Permission.Delete,
|
|
Disable: access.Permission.Disable,
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// buildPermissionTreeHierarchy builds permission tree hierarchy from pages and permissions
|
|
func buildPermissionTreeHierarchy(pages []rolePages.RolPages, permissionMap map[int64]*RolPermission) []*PermissionAccessItem {
|
|
pageMap := make(map[int64]*PermissionAccessItem)
|
|
var roots []*PermissionAccessItem
|
|
|
|
// Convert all pages to PermissionAccessItem
|
|
for i := 0; i < len(pages); i++ {
|
|
page := &pages[i]
|
|
access := &PermissionAccessItem{
|
|
Id: page.Id,
|
|
Name: page.Name,
|
|
Icon: page.Icon,
|
|
URL: page.URL,
|
|
Level: page.Level,
|
|
Sort: page.Sort,
|
|
Active: page.Active,
|
|
}
|
|
|
|
// Add permission if exists
|
|
if perm, exists := permissionMap[page.Id]; exists {
|
|
access.Permission = &PermissionDetail{
|
|
Create: perm.Create,
|
|
Read: perm.Read,
|
|
Update: perm.Update,
|
|
Delete: perm.Delete,
|
|
Disable: perm.Disable,
|
|
}
|
|
}
|
|
|
|
pageMap[page.Id] = access
|
|
}
|
|
|
|
// Build parent-child relationships
|
|
for i := 0; i < len(pages); i++ {
|
|
page := &pages[i]
|
|
if page.Parent == nil {
|
|
roots = append(roots, pageMap[page.Id])
|
|
} else {
|
|
if parent, exists := pageMap[*page.Parent]; exists {
|
|
parent.Children = append(parent.Children, pageMap[page.Id])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort children by sort order
|
|
for _, access := range pageMap {
|
|
if len(access.Children) > 0 {
|
|
sortPermissionAccessChildren(access.Children)
|
|
}
|
|
}
|
|
|
|
// Sort roots by sort order
|
|
sortPermissionAccessChildren(roots)
|
|
|
|
return roots
|
|
}
|
|
|
|
func sortPermissionAccessChildren(children []*PermissionAccessItem) {
|
|
for i := 0; i < len(children); i++ {
|
|
for j := i + 1; j < len(children); j++ {
|
|
if children[i].Sort > children[j].Sort {
|
|
children[i], children[j] = children[j], children[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *service) GetByRoleAndPages(ctx context.Context, roleKeycloak string, fkRolPagesIds []int64) ([]*RolPermissionResponse, error) {
|
|
if roleKeycloak == "" {
|
|
return nil, errors.NewValidationError().Message("Role must be provided").Build()
|
|
}
|
|
if len(fkRolPagesIds) == 0 {
|
|
return nil, errors.NewValidationError().Message("Page IDs must be provided").Build()
|
|
}
|
|
|
|
entities, err := s.queryRepo.FindByRoleAndPages(ctx, roleKeycloak, fkRolPagesIds)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve permissions by role and pages").Cause(err).Build()
|
|
}
|
|
|
|
responses := make([]*RolPermissionResponse, len(entities))
|
|
for i, entity := range entities {
|
|
responses[i] = mapEntityToResponse(entity)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (s *service) GetRolePermissionTree(ctx context.Context, roleKeycloak string, groupKeycloak []string, activeOnly *bool) (*RolePermissionTreeResponse, error) {
|
|
if roleKeycloak == "" && len(groupKeycloak) == 0 {
|
|
return nil, errors.NewValidationError().Message("Role or group must be provided").Build()
|
|
}
|
|
|
|
// --- 1. Implementasi Caching (Check) ---
|
|
var activeStr string
|
|
if activeOnly != nil {
|
|
activeStr = fmt.Sprintf("%v", *activeOnly)
|
|
} else {
|
|
activeStr = "all"
|
|
}
|
|
|
|
cacheKey := fmt.Sprintf("permission_tree:%s:%s:%s", roleKeycloak, strings.Join(groupKeycloak, "_"), activeStr)
|
|
|
|
if s.cache != nil {
|
|
var strData string
|
|
if err := s.cache.Get(ctx, cacheKey, &strData); err == nil && strData != "" {
|
|
var cachedResponse RolePermissionTreeResponse
|
|
if err := json.Unmarshal([]byte(strData), &cachedResponse); err == nil {
|
|
logger.Default().Debug("Cache hit for Role Permission Tree", logger.String("key", cacheKey))
|
|
return &cachedResponse, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gunakan query JOIN yang lebih efisien
|
|
permissionsWithPages, err := s.queryRepo.FindPermissionsWithPages(ctx, roleKeycloak, groupKeycloak, activeOnly)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve permissions with pages").Cause(err).Build()
|
|
}
|
|
|
|
// Jika tidak ada hasil, return empty
|
|
if len(permissionsWithPages) == 0 {
|
|
return &RolePermissionTreeResponse{
|
|
Success: true,
|
|
Data: RolePermissionData{
|
|
Role: roleKeycloak,
|
|
// Group: groupKeycloak,
|
|
Access: []*AccessTreeItem{},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Konversi hasil JOIN ke struct pages dan permissions
|
|
var pages []rolePages.RolPages
|
|
pageMap := make(map[int64]bool)
|
|
permissionMap := make(map[int64]*RolPermission)
|
|
|
|
for _, permPage := range permissionsWithPages {
|
|
// Buat atau dapatkan page
|
|
if !pageMap[permPage.FkRolPagesId] {
|
|
page := rolePages.RolPages{
|
|
Id: permPage.FkRolPagesId,
|
|
Name: permPage.PageName,
|
|
URL: permPage.PageUrl,
|
|
Level: int16(permPage.PageLevel),
|
|
Sort: int16(permPage.PageSort),
|
|
Active: permPage.PageActive,
|
|
Icon: permPage.PageIcon,
|
|
}
|
|
if permPage.PageParentId != nil {
|
|
page.Parent = permPage.PageParentId
|
|
}
|
|
pages = append(pages, page)
|
|
pageMap[permPage.FkRolPagesId] = true
|
|
}
|
|
|
|
// Buat permission
|
|
permission := &RolPermission{
|
|
Id: permPage.Id,
|
|
Create: permPage.Create,
|
|
Read: permPage.Read,
|
|
Update: permPage.Update,
|
|
Disable: permPage.Disable,
|
|
Delete: permPage.Delete,
|
|
Active: permPage.Active,
|
|
FkRolPagesId: permPage.FkRolPagesId,
|
|
RoleKeycloak: permPage.RoleKeycloak,
|
|
GroupKeycloak: permPage.GroupKeycloak,
|
|
CreatedAt: permPage.CreatedAt,
|
|
UpdatedAt: permPage.UpdatedAt,
|
|
}
|
|
permissionMap[permPage.FkRolPagesId] = permission
|
|
}
|
|
|
|
// Build tree dengan pages yang sudah diurutkan
|
|
access := buildPageAccessTree(pages, permissionMap)
|
|
|
|
// Convert to proper response format
|
|
accessTree := convertPageAccessToAccessTree(access)
|
|
|
|
// Ambil group_keycloak yang sebenarnya dari data permission (jika ada)
|
|
var actualGroup []string
|
|
if len(permissionsWithPages) > 0 {
|
|
// Kumpulkan semua group_keycloak yang unik
|
|
groupMap := make(map[string]bool)
|
|
for _, permPage := range permissionsWithPages {
|
|
if permPage.GroupKeycloak != "" {
|
|
groupMap[permPage.GroupKeycloak] = true
|
|
}
|
|
}
|
|
// Konversi map ke slice
|
|
for group := range groupMap {
|
|
actualGroup = append(actualGroup, group)
|
|
}
|
|
}
|
|
|
|
response := &RolePermissionTreeResponse{
|
|
Success: true,
|
|
Data: RolePermissionData{
|
|
Role: roleKeycloak,
|
|
// Group: actualGroup,
|
|
Access: accessTree,
|
|
},
|
|
}
|
|
|
|
// --- 2. Implementasi Caching (Set) ---
|
|
if s.cache != nil {
|
|
if bytes, err := json.Marshal(response); err == nil {
|
|
// Simpan ke cache dengan TTL 1 jam (Dapat disesuaikan)
|
|
_ = s.cache.Set(ctx, cacheKey, string(bytes), 1*time.Hour)
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *service) GetRolePagesList(ctx context.Context, roleKeycloak string, limit, offset int, activeOnly *bool) (map[string]interface{}, error) {
|
|
// 1. Inisiasi Limit & Offset standar
|
|
if limit == 0 {
|
|
limit = 10
|
|
} else if limit < -1 || limit > 100 {
|
|
limit = 10
|
|
}
|
|
if offset < 0 {
|
|
offset = 0
|
|
}
|
|
|
|
// 2. Fetch dataAll: semua role pages aktif beserta permission mapping (query FindActivePagesWithPermission)
|
|
isActive := true
|
|
if activeOnly != nil {
|
|
isActive = *activeOnly
|
|
}
|
|
dataAllRecords, err := s.queryRepo.FindActivePagesWithPermission(ctx, isActive)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve active pages for dataAll").Cause(err).Build()
|
|
}
|
|
|
|
var allPages []rolePages.RolPages
|
|
allPageMap := make(map[int64]bool)
|
|
allPermissionMap := make(map[int64]*RolPermission)
|
|
for _, permPage := range dataAllRecords {
|
|
if !allPageMap[permPage.FkRolPagesId] {
|
|
page := rolePages.RolPages{
|
|
Id: permPage.FkRolPagesId,
|
|
Name: permPage.PageName,
|
|
URL: permPage.PageUrl,
|
|
Level: int16(permPage.PageLevel),
|
|
Sort: int16(permPage.PageSort),
|
|
Active: permPage.PageActive,
|
|
Icon: permPage.PageIcon,
|
|
}
|
|
if permPage.PageParentId != nil {
|
|
page.Parent = permPage.PageParentId
|
|
}
|
|
allPages = append(allPages, page)
|
|
allPageMap[permPage.FkRolPagesId] = true
|
|
}
|
|
allPermissionMap[permPage.FkRolPagesId] = &RolPermission{
|
|
Id: permPage.Id,
|
|
Create: permPage.Create,
|
|
Read: permPage.Read,
|
|
Update: permPage.Update,
|
|
Delete: permPage.Delete,
|
|
Disable: permPage.Disable,
|
|
GroupKeycloak: permPage.GroupKeycloak,
|
|
RoleKeycloak: permPage.RoleKeycloak,
|
|
}
|
|
}
|
|
accessAll := buildPageAccessTree(allPages, allPermissionMap)
|
|
accessTreeAll := convertPageAccessToAccessTree(accessAll)
|
|
|
|
// 3. Fetch seluruh role permission berserta info pages-nya (dengan filter roleKeycloak spesifik)
|
|
permissionsWithPages, err := s.queryRepo.FindPermissionsWithRole(ctx, roleKeycloak, activeOnly)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve permissions with pages").Cause(err).Build()
|
|
}
|
|
|
|
var pages []rolePages.RolPages
|
|
pageMap := make(map[int64]bool)
|
|
permissionMap := make(map[int64]*RolPermission)
|
|
|
|
for _, permPage := range permissionsWithPages {
|
|
if !pageMap[permPage.FkRolPagesId] {
|
|
page := rolePages.RolPages{
|
|
Id: permPage.FkRolPagesId,
|
|
Name: permPage.PageName,
|
|
URL: permPage.PageUrl,
|
|
Level: int16(permPage.PageLevel),
|
|
Sort: int16(permPage.PageSort),
|
|
Active: permPage.PageActive,
|
|
Icon: permPage.PageIcon,
|
|
}
|
|
if permPage.PageParentId != nil {
|
|
page.Parent = permPage.PageParentId
|
|
}
|
|
pages = append(pages, page)
|
|
pageMap[permPage.FkRolPagesId] = true
|
|
}
|
|
|
|
permissionMap[permPage.FkRolPagesId] = &RolPermission{
|
|
Id: permPage.Id,
|
|
Create: permPage.Create,
|
|
Read: permPage.Read,
|
|
Update: permPage.Update,
|
|
Delete: permPage.Delete,
|
|
Disable: permPage.Disable,
|
|
GroupKeycloak: permPage.GroupKeycloak,
|
|
RoleKeycloak: permPage.RoleKeycloak,
|
|
}
|
|
}
|
|
|
|
// Build tree menggunakan helper yang telah ada
|
|
access := buildPageAccessTree(pages, permissionMap)
|
|
accessTree := convertPageAccessToAccessTree(access)
|
|
|
|
data := []*RolePageAccessResponse{
|
|
{
|
|
RoleID: roleKeycloak,
|
|
DataRole: accessTree,
|
|
DataAll: accessTreeAll,
|
|
},
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"data": data,
|
|
"total": int64(1),
|
|
"limit": limit,
|
|
"offset": offset,
|
|
}, nil
|
|
}
|
|
|
|
func (s *service) GetRolePermissionTreeRole(ctx context.Context, roleKeycloak string, activeOnly *bool) (*RolePermissionTreeResponse, error) {
|
|
|
|
// --- 1. Implementasi Caching (Check) ---
|
|
var activeStr string
|
|
if activeOnly != nil {
|
|
activeStr = fmt.Sprintf("%v", *activeOnly)
|
|
} else {
|
|
activeStr = "all"
|
|
}
|
|
|
|
cacheKey := fmt.Sprintf("permission_tree:%s:%s", roleKeycloak, activeStr)
|
|
|
|
if s.cache != nil {
|
|
var strData string
|
|
if err := s.cache.Get(ctx, cacheKey, &strData); err == nil && strData != "" {
|
|
var cachedResponse RolePermissionTreeResponse
|
|
if err := json.Unmarshal([]byte(strData), &cachedResponse); err == nil {
|
|
logger.Default().Debug("Cache hit for Role Permission Tree", logger.String("key", cacheKey))
|
|
return &cachedResponse, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gunakan query JOIN yang lebih efisien
|
|
// permissionsWithPages, err := s.queryRepo.FindPermissionsWithPages(ctx, roleKeycloak, groupKeycloak, activeOnly)
|
|
permissionsWithPages, err := s.queryRepo.FindPermissionsWithRole(ctx, roleKeycloak, activeOnly)
|
|
if err != nil {
|
|
return nil, errors.InternalError().Message("Failed to retrieve permissions with pages").Cause(err).Build()
|
|
}
|
|
|
|
// Jika tidak ada hasil, return empty
|
|
if len(permissionsWithPages) == 0 {
|
|
return &RolePermissionTreeResponse{
|
|
Success: true,
|
|
Data: RolePermissionData{
|
|
Role: roleKeycloak,
|
|
// Group: groupKeycloak,
|
|
Access: []*AccessTreeItem{},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Konversi hasil JOIN ke struct pages dan permissions
|
|
var pages []rolePages.RolPages
|
|
pageMap := make(map[int64]bool)
|
|
permissionMap := make(map[int64]*RolPermission)
|
|
|
|
for _, permPage := range permissionsWithPages {
|
|
// Buat atau dapatkan page
|
|
if !pageMap[permPage.FkRolPagesId] {
|
|
page := rolePages.RolPages{
|
|
Id: permPage.FkRolPagesId,
|
|
Name: permPage.PageName,
|
|
URL: permPage.PageUrl,
|
|
Level: int16(permPage.PageLevel),
|
|
Sort: int16(permPage.PageSort),
|
|
Active: permPage.PageActive,
|
|
Icon: permPage.PageIcon,
|
|
}
|
|
if permPage.PageParentId != nil {
|
|
page.Parent = permPage.PageParentId
|
|
}
|
|
pages = append(pages, page)
|
|
pageMap[permPage.FkRolPagesId] = true
|
|
}
|
|
|
|
// Buat permission
|
|
permission := &RolPermission{
|
|
Id: permPage.Id,
|
|
Create: permPage.Create,
|
|
Read: permPage.Read,
|
|
Update: permPage.Update,
|
|
Disable: permPage.Disable,
|
|
Delete: permPage.Delete,
|
|
Active: permPage.Active,
|
|
FkRolPagesId: permPage.FkRolPagesId,
|
|
RoleKeycloak: permPage.RoleKeycloak,
|
|
GroupKeycloak: permPage.GroupKeycloak,
|
|
CreatedAt: permPage.CreatedAt,
|
|
UpdatedAt: permPage.UpdatedAt,
|
|
}
|
|
permissionMap[permPage.FkRolPagesId] = permission
|
|
}
|
|
|
|
// Build tree dengan pages yang sudah diurutkan
|
|
access := buildPageAccessTree(pages, permissionMap)
|
|
|
|
// Convert to proper response format
|
|
accessTree := convertPageAccessToAccessTree(access)
|
|
|
|
// Ambil group_keycloak yang sebenarnya dari data permission (jika ada)
|
|
var actualGroup []string
|
|
if len(permissionsWithPages) > 0 {
|
|
// Kumpulkan semua group_keycloak yang unik
|
|
groupMap := make(map[string]bool)
|
|
for _, permPage := range permissionsWithPages {
|
|
if permPage.GroupKeycloak != "" {
|
|
groupMap[permPage.GroupKeycloak] = true
|
|
}
|
|
}
|
|
// Konversi map ke slice
|
|
for group := range groupMap {
|
|
actualGroup = append(actualGroup, group)
|
|
}
|
|
}
|
|
|
|
response := &RolePermissionTreeResponse{
|
|
Success: true,
|
|
Data: RolePermissionData{
|
|
Role: roleKeycloak,
|
|
// Group: actualGroup,
|
|
Access: accessTree,
|
|
},
|
|
}
|
|
|
|
// --- 2. Implementasi Caching (Set) ---
|
|
if s.cache != nil {
|
|
if bytes, err := json.Marshal(response); err == nil {
|
|
// Simpan ke cache dengan TTL 1 jam (Dapat disesuaikan)
|
|
_ = s.cache.Set(ctx, cacheKey, string(bytes), 1*time.Hour)
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|