first commit
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package pages
|
||||
|
||||
// RolPagesRequest represents the request payload for RolPages
|
||||
type RolPagesRequest struct {
|
||||
Name string `json:"name" validate:"required,max=20"`
|
||||
Icon string `json:"icon" validate:"max=100"`
|
||||
URL string `json:"url" validate:"required"`
|
||||
Level int16 `json:"level" validate:"required,min=0"`
|
||||
Sort int16 `json:"sort" validate:"required,min=0"`
|
||||
Parent *int64 `json:"parent"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
// RolPagesResponse represents the response payload for RolPages
|
||||
type RolPagesResponse struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url"`
|
||||
Level int16 `json:"level"`
|
||||
Sort int16 `json:"sort"`
|
||||
Parent *int64 `json:"parent,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
Children []*RolPagesResponse `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// RolPagesTreeResponse represents hierarchical menu structure
|
||||
type RolPagesTreeResponse struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url"`
|
||||
Level int16 `json:"level"`
|
||||
Sort int16 `json:"sort"`
|
||||
Active bool `json:"active"`
|
||||
Children []*RolPagesTreeResponse `json:"children,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RolPages entity represents the rol_pages table in the database
|
||||
type RolPages struct {
|
||||
Id int64 `json:"id" db:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Icon string `json:"icon" db:"icon"`
|
||||
URL string `json:"url" db:"url"`
|
||||
Level int16 `json:"level" db:"level"`
|
||||
Sort int16 `json:"sort" db:"sort"`
|
||||
Parent *int64 `json:"parent" db:"parent"`
|
||||
Active bool `json:"active" db:"active"`
|
||||
CreatedAt *time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at" db:"updated_at"`
|
||||
|
||||
// Relasi untuk hirarki menu
|
||||
Children []RolPages `json:"children" gorm:"-"`
|
||||
ParentRef *RolPages `json:"parent_ref,omitempty" gorm:"-" db:"-"`
|
||||
}
|
||||
|
||||
// TableName specifies the table name for RolPages
|
||||
func (RolPages) TableName() string {
|
||||
return "role_access.rol_pages"
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package pages
|
||||
|
||||
// mapRequestToEntity converts RolPagesRequest to *RolPages entity
|
||||
func mapRequestToEntity(req RolPagesRequest) *RolPages {
|
||||
return &RolPages{
|
||||
Name: req.Name,
|
||||
Icon: req.Icon,
|
||||
URL: req.URL,
|
||||
Level: req.Level,
|
||||
Sort: req.Sort,
|
||||
Parent: req.Parent,
|
||||
Active: req.Active,
|
||||
}
|
||||
}
|
||||
|
||||
// mapEntityToResponse converts *RolPages entity to *RolPagesResponse DTO
|
||||
func mapEntityToResponse(e *RolPages) *RolPagesResponse {
|
||||
return &RolPagesResponse{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
Icon: e.Icon,
|
||||
URL: e.URL,
|
||||
Level: e.Level,
|
||||
Sort: e.Sort,
|
||||
Parent: e.Parent,
|
||||
Active: e.Active,
|
||||
}
|
||||
}
|
||||
|
||||
// mapEntityToTreeResponse converts *RolPages entity to *RolPagesTreeResponse DTO
|
||||
func mapEntityToTreeResponse(e *RolPages) *RolPagesTreeResponse {
|
||||
return &RolPagesTreeResponse{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
Icon: e.Icon,
|
||||
URL: e.URL,
|
||||
Level: e.Level,
|
||||
Sort: e.Sort,
|
||||
Active: e.Active,
|
||||
}
|
||||
}
|
||||
|
||||
// buildTreeHierarchy membangun struktur hirarki dari flat list
|
||||
func buildTreeHierarchy(pages []*RolPages) []*RolPagesTreeResponse {
|
||||
pageMap := make(map[int64]*RolPagesTreeResponse)
|
||||
var roots []*RolPagesTreeResponse
|
||||
|
||||
// Convert semua pages ke TreeResponse dan buat map
|
||||
for _, page := range pages {
|
||||
treeResponse := mapEntityToTreeResponse(page)
|
||||
pageMap[page.Id] = treeResponse
|
||||
}
|
||||
|
||||
// Bangun relasi parent-child
|
||||
for _, page := range pages {
|
||||
if page.Parent == nil {
|
||||
// Ini adalah root node
|
||||
roots = append(roots, pageMap[page.Id])
|
||||
} else {
|
||||
// Ini adalah child node
|
||||
if parent, exists := pageMap[*page.Parent]; exists {
|
||||
parent.Children = append(parent.Children, pageMap[page.Id])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort children berdasarkan sort order
|
||||
for _, page := range pageMap {
|
||||
if len(page.Children) > 0 {
|
||||
sortChildren(page.Children)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort root nodes berdasarkan sort order
|
||||
sortChildren(roots)
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
// sortChildren mengurutkan children berdasarkan field Sort
|
||||
func sortChildren(children []*RolPagesTreeResponse) {
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"service/internal/infrastructure/database"
|
||||
"service/pkg/utils/query"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CommandRepository menangani operasi Write (Create, Update, Delete) ke Master DB
|
||||
type CommandRepository interface {
|
||||
Create(ctx context.Context, entity *RolPages) error
|
||||
Update(ctx context.Context, entity *RolPages) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
// QueryRepository menangani operasi Read (Find, Search) ke Read-Replica DB
|
||||
type QueryRepository interface {
|
||||
FindByID(ctx context.Context, id int64) (*RolPages, error)
|
||||
GetChildren(ctx context.Context, parentID int64) ([]RolPages, error)
|
||||
Search(ctx context.Context, filters map[string]interface{}, sorts []query.SortField, limit, offset int) ([]RolPages, int64, error)
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
dbManager database.Service
|
||||
dbName string
|
||||
qb query.QueryBuilder
|
||||
dbType query.DBType
|
||||
allowedColumnsMap map[string]bool
|
||||
}
|
||||
|
||||
// Konstruktor Baru CQRS
|
||||
func NewCommandRepository(dbManager database.Service, dbName string) CommandRepository {
|
||||
return NewRepository(dbManager, dbName)
|
||||
}
|
||||
|
||||
func NewQueryRepository(dbManager database.Service, dbName string) QueryRepository {
|
||||
return NewRepository(dbManager, dbName)
|
||||
}
|
||||
func NewRepository(dbManager database.Service, dbName string) *repository {
|
||||
dbType := query.DBTypePostgreSQL
|
||||
allowedColumns := []string{
|
||||
"id",
|
||||
"name",
|
||||
"icon",
|
||||
"url",
|
||||
"level",
|
||||
"sort",
|
||||
"parent",
|
||||
"active",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
}
|
||||
qb := query.NewSQLQueryBuilder(dbType).
|
||||
SetSecurityOptions(true, 1000).
|
||||
SetQueryLogging(true).
|
||||
SetQueryTimeout(30).
|
||||
SetAllowedColumns(allowedColumns)
|
||||
return &repository{dbManager: dbManager, dbName: dbName, qb: qb, dbType: dbType, allowedColumnsMap: createAllowedColumnsMap(allowedColumns)}
|
||||
}
|
||||
|
||||
// Helper function untuk membuat map dari allowed columns
|
||||
func createAllowedColumnsMap(columns []string) map[string]bool {
|
||||
result := make(map[string]bool)
|
||||
for _, col := range columns {
|
||||
result[col] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// isColumnAllowed memvalidasi apakah kolom diizinkan untuk digunakan
|
||||
func (r *repository) isColumnAllowed(column string) bool {
|
||||
return r.allowedColumnsMap[column]
|
||||
}
|
||||
|
||||
// Helper mengambil koneksi Master dari DB Manager
|
||||
func (r *repository) getWriteGormDB() (*gorm.DB, error) {
|
||||
return r.dbManager.GetGormDB(r.dbName)
|
||||
}
|
||||
|
||||
// Helper mengambil koneksi Read-Replica dari DB Manager
|
||||
func (r *repository) getReadSQLXDB() (*sqlx.DB, error) {
|
||||
sqlDB, err := r.dbManager.GetReadDB(r.dbName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get read db: %w", err)
|
||||
}
|
||||
return sqlx.NewDb(sqlDB, "pgx"), nil
|
||||
}
|
||||
|
||||
// --- Implementasi Write Model ---
|
||||
func (r *repository) Create(ctx context.Context, entity *RolPages) error {
|
||||
db, err := r.getWriteGormDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.WithContext(ctx).Table(entity.TableName()).Create(entity).Error
|
||||
}
|
||||
|
||||
func (r *repository) Update(ctx context.Context, entity *RolPages) error {
|
||||
db, err := r.getWriteGormDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.WithContext(ctx).Table(entity.TableName()).Save(entity).Error
|
||||
}
|
||||
|
||||
func (r *repository) Delete(ctx context.Context, id int64) error {
|
||||
db, err := r.getWriteGormDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.WithContext(ctx).Delete(&RolPages{}, id).Error
|
||||
}
|
||||
|
||||
// --- Implementasi Read Model ---
|
||||
func (r *repository) FindByID(ctx context.Context, id int64) (*RolPages, error) {
|
||||
sqlxDB, err := r.getReadSQLXDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := query.DynamicQuery{
|
||||
From: "role_access.rol_pages",
|
||||
Filters: []query.FilterGroup{{
|
||||
Filters: []query.DynamicFilter{query.CreateEqualFilter("id", id)},
|
||||
}},
|
||||
Limit: 1,
|
||||
}
|
||||
|
||||
var entity RolPages
|
||||
if err := r.qb.ExecuteQueryRow(ctx, sqlxDB, q, &entity); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to fetch RolPages by ID: %w", err)
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
func (r *repository) GetChildren(ctx context.Context, parentID int64) ([]RolPages, error) {
|
||||
sqlxDB, err := r.getReadSQLXDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := query.DynamicQuery{
|
||||
From: "role_access.rol_pages",
|
||||
Filters: []query.FilterGroup{{
|
||||
Filters: []query.DynamicFilter{query.CreateEqualFilter("parent", parentID)},
|
||||
}},
|
||||
Sort: []query.SortField{query.CreateAscSort("sort")},
|
||||
}
|
||||
|
||||
var entities []RolPages
|
||||
if err := r.qb.ExecuteQuery(ctx, sqlxDB, q, &entities); err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch RolPages children: %w", err)
|
||||
}
|
||||
return entities, nil
|
||||
}
|
||||
|
||||
func (r *repository) Search(ctx context.Context, filters map[string]interface{}, sorts []query.SortField, limit, offset int) ([]RolPages, int64, error) {
|
||||
sqlxDB, err := r.getReadSQLXDB()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var dynamicFilters []query.DynamicFilter
|
||||
for k, v := range filters {
|
||||
// Normalisasi nama kolom menjadi lowercase agar sesuai dengan allowedColumns
|
||||
colName := strings.ToLower(k)
|
||||
if !r.isColumnAllowed(colName) {
|
||||
continue
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
if val != "" {
|
||||
// Gunakan OpILike hanya untuk kolom yang bertipe teks (string)
|
||||
if colName == "name" || colName == "icon" || colName == "url" {
|
||||
dynamicFilters = append(dynamicFilters, query.CreateFilter(colName, query.OpILike, "%"+val+"%"))
|
||||
} else {
|
||||
if boolVal, err := strconv.ParseBool(val); err == nil && (val == "true" || val == "false" || val == "1" || val == "0") {
|
||||
dynamicFilters = append(dynamicFilters, query.CreateEqualFilter(colName, boolVal))
|
||||
} else if intVal, err := strconv.Atoi(val); err == nil {
|
||||
dynamicFilters = append(dynamicFilters, query.CreateEqualFilter(colName, intVal))
|
||||
} else {
|
||||
dynamicFilters = append(dynamicFilters, query.CreateEqualFilter(colName, val))
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
dynamicFilters = append(dynamicFilters, query.CreateEqualFilter(colName, val))
|
||||
}
|
||||
}
|
||||
|
||||
if len(sorts) == 0 {
|
||||
sorts = []query.SortField{query.CreateAscSort("id")}
|
||||
}
|
||||
|
||||
q := query.DynamicQuery{
|
||||
From: "role_access.rol_pages",
|
||||
Fields: []query.SelectField{{Expression: "*"}},
|
||||
Filters: []query.FilterGroup{{Filters: dynamicFilters}},
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Sort: sorts,
|
||||
}
|
||||
|
||||
var entities []RolPages
|
||||
if err := r.qb.ExecuteQuery(ctx, sqlxDB, q, &entities); err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to execute search query: %w", err)
|
||||
}
|
||||
|
||||
count, err := r.qb.ExecuteCount(ctx, sqlxDB, q)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to execute search count query: %w", err)
|
||||
}
|
||||
|
||||
return entities, count, nil
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"service/internal/infrastructure/cache"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/logger"
|
||||
"service/pkg/utils/query"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
GetList(ctx context.Context, limit, offset int, sorts []string, activeFilter *bool) (map[string]interface{}, error)
|
||||
GetDetail(ctx context.Context, id int64) (*RolPagesResponse, error)
|
||||
Search(ctx context.Context, filters map[string]interface{}, sorts []string, limit, offset int) (map[string]interface{}, error)
|
||||
Create(ctx context.Context, req RolPagesRequest) (*RolPagesResponse, error)
|
||||
Update(ctx context.Context, id int64, req RolPagesRequest) (*RolPagesResponse, error)
|
||||
Delete(ctx context.Context, id int64) error
|
||||
GetTree(ctx context.Context, activeFilter *bool) ([]*RolPagesTreeResponse, error)
|
||||
GetTreeByLevel(ctx context.Context, level int16, activeFilter *bool) ([]*RolPagesTreeResponse, error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
cmdRepo CommandRepository
|
||||
queryRepo QueryRepository
|
||||
cache *cache.Manager
|
||||
}
|
||||
|
||||
func NewService(cmdRepo CommandRepository, queryRepo 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
|
||||
}
|
||||
return s.Search(ctx, filters, sorts, limit, offset)
|
||||
}
|
||||
|
||||
func (s *service) GetDetail(ctx context.Context, id int64) (*RolPagesResponse, 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 RolPages detail").Cause(err).Build()
|
||||
}
|
||||
if entity == nil {
|
||||
return nil, errors.NotFoundError().Message("RolPages not found").Metadata("id", id).Build()
|
||||
}
|
||||
|
||||
response := mapEntityToResponse(entity)
|
||||
|
||||
// Ambil children jika ada
|
||||
children, err := s.queryRepo.GetChildren(ctx, id)
|
||||
if err == nil && len(children) > 0 {
|
||||
response.Children = make([]*RolPagesResponse, len(children))
|
||||
for i := range children {
|
||||
response.Children[i] = mapEntityToResponse(&children[i])
|
||||
}
|
||||
}
|
||||
|
||||
return response, 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)
|
||||
hashInput := fmt.Sprintf("%s|%s|%d|%d", string(filterBytes), string(sortBytes), limit, offset)
|
||||
hash := sha256.Sum256([]byte(hashInput))
|
||||
cacheKey := fmt.Sprintf("role_pages_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 []*RolPagesResponse `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 Pages 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,
|
||||
"name": true,
|
||||
"level": true,
|
||||
"sort": true,
|
||||
"active": true,
|
||||
"parent": true,
|
||||
"icon": true,
|
||||
"url": true,
|
||||
"created_at": true,
|
||||
"updated_at": 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 RolPagess").Cause(err).Build()
|
||||
}
|
||||
|
||||
responses := make([]*RolPagesResponse, len(entities))
|
||||
for i := range entities {
|
||||
responses[i] = mapEntityToResponse(&entities[i])
|
||||
}
|
||||
|
||||
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 {
|
||||
_ = s.cache.Set(ctx, cacheKey, string(bytes), 5*time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
return responseMap, nil
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, req RolPagesRequest) (*RolPagesResponse, error) {
|
||||
// TODO: Add validation here if needed
|
||||
entity := mapRequestToEntity(req)
|
||||
if err := s.cmdRepo.Create(ctx, entity); err != nil {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return nil, errors.AlreadyExistsError().Message("RolPages with this identifier already exists").Cause(err).Build()
|
||||
}
|
||||
return nil, errors.InternalError().Message("Failed to create RolPages").Cause(err).Build()
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
_ = s.cache.Delete(ctx, "role_pages_search_v2:*")
|
||||
}
|
||||
return mapEntityToResponse(entity), nil
|
||||
}
|
||||
|
||||
func (s *service) GetTree(ctx context.Context, activeFilter *bool) ([]*RolPagesTreeResponse, error) {
|
||||
filters := map[string]interface{}{}
|
||||
if activeFilter != nil {
|
||||
filters["active"] = *activeFilter
|
||||
}
|
||||
|
||||
pages, _, err := s.queryRepo.Search(ctx, filters, []query.SortField{query.CreateAscSort("sort"), query.CreateAscSort("id")}, -1, 0)
|
||||
if err != nil {
|
||||
return nil, errors.InternalError().Message("Failed to retrieve pages").Cause(err).Build()
|
||||
}
|
||||
|
||||
// Konversi []RolPages ke []*RolPages
|
||||
pagePointers := make([]*RolPages, len(pages))
|
||||
for i := range pages {
|
||||
pagePointers[i] = &pages[i]
|
||||
}
|
||||
|
||||
return buildTreeHierarchy(pagePointers), nil
|
||||
}
|
||||
|
||||
func (s *service) GetTreeByLevel(ctx context.Context, level int16, activeFilter *bool) ([]*RolPagesTreeResponse, error) {
|
||||
filters := map[string]interface{}{"level": level}
|
||||
if activeFilter != nil {
|
||||
filters["active"] = *activeFilter
|
||||
}
|
||||
|
||||
pages, _, err := s.queryRepo.Search(ctx, filters, []query.SortField{query.CreateAscSort("sort"), query.CreateAscSort("id")}, -1, 0)
|
||||
if err != nil {
|
||||
return nil, errors.InternalError().Message("Failed to retrieve pages by level").Cause(err).Build()
|
||||
}
|
||||
|
||||
// Konversi []RolPages ke []*RolPages
|
||||
pagePointers := make([]*RolPages, len(pages))
|
||||
for i := range pages {
|
||||
pagePointers[i] = &pages[i]
|
||||
}
|
||||
|
||||
return buildTreeHierarchy(pagePointers), nil
|
||||
}
|
||||
func (s *service) Update(ctx context.Context, id int64, req RolPagesRequest) (*RolPagesResponse, 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 RolPages").Cause(err).Build()
|
||||
}
|
||||
if existing == nil {
|
||||
return nil, errors.NotFoundError().Message("RolPages not found").Metadata("id", id).Build()
|
||||
}
|
||||
|
||||
// Validasi parent jika berubah dan tidak boleh circular reference
|
||||
if req.Parent != nil && *req.Parent != id {
|
||||
if *req.Parent == id {
|
||||
return nil, errors.NewValidationError().Message("Cannot set self as parent").Build()
|
||||
}
|
||||
|
||||
parent, err := s.queryRepo.FindByID(ctx, *req.Parent)
|
||||
if err != nil {
|
||||
return nil, errors.InternalError().Message("Failed to validate parent").Cause(err).Build()
|
||||
}
|
||||
if parent == nil {
|
||||
return nil, errors.NotFoundError().Message("Parent page not found").Metadata("parent_id", *req.Parent).Build()
|
||||
}
|
||||
}
|
||||
|
||||
// Update entity
|
||||
existing.Name = req.Name
|
||||
existing.Icon = req.Icon
|
||||
existing.URL = req.URL
|
||||
existing.Level = req.Level
|
||||
existing.Sort = req.Sort
|
||||
existing.Parent = req.Parent
|
||||
existing.Active = req.Active
|
||||
|
||||
if err := s.cmdRepo.Update(ctx, existing); err != nil {
|
||||
return nil, errors.InternalError().Message("Failed to update RolPages").Cause(err).Build()
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
_ = s.cache.Delete(ctx, "role_pages_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 memiliki children
|
||||
children, err := s.queryRepo.GetChildren(ctx, id)
|
||||
if err != nil {
|
||||
return errors.InternalError().Message("Failed to check children").Cause(err).Build()
|
||||
}
|
||||
if len(children) > 0 {
|
||||
return errors.NewValidationError().Message("Cannot delete page with children").Metadata("children_count", len(children)).Build()
|
||||
}
|
||||
|
||||
if err := s.cmdRepo.Delete(ctx, id); err != nil {
|
||||
return errors.InternalError().Message("Failed to delete RolPages").Cause(err).Build()
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
_ = s.cache.Delete(ctx, "role_pages_search_v2:*")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user