Compare commits
11 Commits
ce2b7b4d18
...
5ad79e75e2
| Author | SHA1 | Date |
|---|---|---|
|
|
5ad79e75e2 | |
|
|
f43d96c621 | |
|
|
575c271d27 | |
|
|
4b814cbdc5 | |
|
|
3a8ebc6715 | |
|
|
bcb6326877 | |
|
|
a98517f4be | |
|
|
1a9ec470b2 | |
|
|
75b8edb124 | |
|
|
ccc3dda29a | |
|
|
9ed22ec6b7 |
|
|
@ -7,39 +7,40 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Articles struct {
|
type Articles struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
Title string `json:"title" gorm:"type:varchar"`
|
Title string `json:"title" gorm:"type:varchar"`
|
||||||
Slug string `json:"slug" gorm:"type:varchar"`
|
Slug string `json:"slug" gorm:"type:varchar"`
|
||||||
Description string `json:"description" gorm:"type:varchar"`
|
Description string `json:"description" gorm:"type:varchar"`
|
||||||
CategoryId int `json:"category_id" gorm:"type:int4"`
|
CategoryId int `json:"category_id" gorm:"type:int4"`
|
||||||
HtmlDescription string `json:"html_description" gorm:"type:varchar"`
|
HtmlDescription string `json:"html_description" gorm:"type:varchar"`
|
||||||
TypeId int `json:"type_id" gorm:"type:int4"`
|
TypeId int `json:"type_id" gorm:"type:int4"`
|
||||||
Tags string `json:"tags" gorm:"type:varchar"`
|
Tags string `json:"tags" gorm:"type:varchar"`
|
||||||
ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"`
|
PublishedFor *string `json:"published_for" gorm:"type:varchar(100)"`
|
||||||
ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"`
|
ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"`
|
||||||
PageUrl *string `json:"page_url" gorm:"type:varchar"`
|
ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"`
|
||||||
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
PageUrl *string `json:"page_url" gorm:"type:varchar"`
|
||||||
AiArticleId *int `json:"ai_article_id" gorm:"type:int4"`
|
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||||
CommentCount *int `json:"comment_count" gorm:"type:int4;default:0"`
|
AiArticleId *int `json:"ai_article_id" gorm:"type:int4"`
|
||||||
ShareCount *int `json:"share_count" gorm:"type:int4;default:0"`
|
CommentCount *int `json:"comment_count" gorm:"type:int4;default:0"`
|
||||||
ViewCount *int `json:"view_count" gorm:"type:int4;default:0"`
|
ShareCount *int `json:"share_count" gorm:"type:int4;default:0"`
|
||||||
StatusId *int `json:"status_id" gorm:"type:int4"`
|
ViewCount *int `json:"view_count" gorm:"type:int4;default:0"`
|
||||||
OldId *uint `json:"old_id" gorm:"type:int4"`
|
StatusId *int `json:"status_id" gorm:"type:int4"`
|
||||||
NeedApprovalFrom *int `json:"need_approval_from" gorm:"type:int4"`
|
OldId *uint `json:"old_id" gorm:"type:int4"`
|
||||||
HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"`
|
NeedApprovalFrom *int `json:"need_approval_from" gorm:"type:int4"`
|
||||||
WorkflowId *uint `json:"workflow_id" gorm:"type:int4"`
|
HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"`
|
||||||
CurrentApprovalStep *int `json:"current_approval_step" gorm:"type:int4;default:0"` // 0=not submitted, 1+=approval step
|
WorkflowId *uint `json:"workflow_id" gorm:"type:int4"`
|
||||||
|
CurrentApprovalStep *int `json:"current_approval_step" gorm:"type:int4;default:0"` // 0=not submitted, 1+=approval step
|
||||||
// New fields for no-approval support
|
// New fields for no-approval support
|
||||||
BypassApproval *bool `json:"bypass_approval" gorm:"type:bool;default:false"` // true = skip approval process
|
BypassApproval *bool `json:"bypass_approval" gorm:"type:bool;default:false"` // true = skip approval process
|
||||||
ApprovalExempt *bool `json:"approval_exempt" gorm:"type:bool;default:false"` // true = permanently exempt from approval
|
ApprovalExempt *bool `json:"approval_exempt" gorm:"type:bool;default:false"` // true = permanently exempt from approval
|
||||||
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"`
|
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"`
|
||||||
IsBanner *bool `json:"is_banner" gorm:"type:bool;default:false"`
|
IsBanner *bool `json:"is_banner" gorm:"type:bool;default:false"`
|
||||||
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||||
IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"`
|
IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"`
|
||||||
DraftedAt *time.Time `json:"drafted_at" gorm:"type:timestamp"`
|
DraftedAt *time.Time `json:"drafted_at" gorm:"type:timestamp"`
|
||||||
PublishSchedule *string `json:"publish_schedule" gorm:"type:varchar"`
|
PublishSchedule *string `json:"publish_schedule" gorm:"type:varchar"`
|
||||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ type MasterModules struct {
|
||||||
Name string `json:"name" gorm:"type:varchar"`
|
Name string `json:"name" gorm:"type:varchar"`
|
||||||
Description string `json:"description" gorm:"type:varchar"`
|
Description string `json:"description" gorm:"type:varchar"`
|
||||||
PathUrl string `json:"path_url" gorm:"type:varchar"`
|
PathUrl string `json:"path_url" gorm:"type:varchar"`
|
||||||
|
ActionType *string `json:"action_type" gorm:"type:varchar"` // view, create, edit, delete, approve, export, etc
|
||||||
StatusId int `json:"status_id" gorm:"type:int4"`
|
StatusId int `json:"status_id" gorm:"type:int4"`
|
||||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MenuActions menyimpan actions yang tersedia di setiap menu
|
||||||
|
// Contoh: Menu "Content Management" punya actions: view, create, edit, delete, approve, export
|
||||||
|
type MenuActions struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
MenuId uint `json:"menu_id" gorm:"type:int4;not null"`
|
||||||
|
ActionCode string `json:"action_code" gorm:"type:varchar(50);not null"` // 'view', 'create', 'edit', 'delete', 'approve', 'export'
|
||||||
|
ActionName string `json:"action_name" gorm:"type:varchar(255);not null"` // 'View Content', 'Create Content', etc.
|
||||||
|
Description *string `json:"description" gorm:"type:text"`
|
||||||
|
PathUrl *string `json:"path_url" gorm:"type:varchar(255)"` // Optional: untuk routing frontend
|
||||||
|
HttpMethod *string `json:"http_method" gorm:"type:varchar(10)"` // Optional: 'GET', 'POST', 'PUT', 'DELETE'
|
||||||
|
Position *int `json:"position" gorm:"type:int4"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Menu *MasterMenus `json:"menu,omitempty" gorm:"foreignKey:MenuId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MenuModules menghubungkan menu dengan modul-modul yang dimilikinya
|
||||||
|
// Contoh: Menu "Article" bisa punya modul: "table_article", "create_article", "edit_article", "delete_article"
|
||||||
|
type MenuModules struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
MenuId uint `json:"menu_id" gorm:"type:int4;not null"`
|
||||||
|
ModuleId uint `json:"module_id" gorm:"type:int4;not null"`
|
||||||
|
Position *int `json:"position" gorm:"type:int4"` // Urutan modul dalam menu
|
||||||
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Menu *MasterMenus `json:"menu,omitempty" gorm:"foreignKey:MenuId"`
|
||||||
|
Module *MasterModules `json:"module,omitempty" gorm:"foreignKey:ModuleId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserLevelMenuAccesses mengatur akses user_level ke menu tertentu
|
||||||
|
// Contoh: UserLevel "Admin Pusat" bisa akses semua menu, "Editor" hanya bisa akses "Content Management"
|
||||||
|
type UserLevelMenuAccesses struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserLevelId uint `json:"user_level_id" gorm:"type:int4;not null"`
|
||||||
|
MenuId uint `json:"menu_id" gorm:"type:int4;not null"`
|
||||||
|
CanAccess bool `json:"can_access" gorm:"type:bool;default:true"` // Apakah boleh akses menu ini
|
||||||
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
UserLevel *UserLevels `json:"user_level,omitempty" gorm:"foreignKey:UserLevelId"`
|
||||||
|
Menu *MasterMenus `json:"menu,omitempty" gorm:"foreignKey:MenuId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserLevelMenuActionAccesses mengatur akses user_level ke action tertentu di dalam menu
|
||||||
|
// Contoh: UserLevel "Creator" di menu "Content Management" hanya bisa create dan edit, tidak bisa delete
|
||||||
|
type UserLevelMenuActionAccesses struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserLevelId uint `json:"user_level_id" gorm:"type:int4;not null"`
|
||||||
|
MenuId uint `json:"menu_id" gorm:"type:int4;not null"`
|
||||||
|
ActionCode string `json:"action_code" gorm:"type:varchar(50);not null"` // 'view', 'create', 'edit', 'delete', etc.
|
||||||
|
CanAccess bool `json:"can_access" gorm:"type:bool;default:true"` // Apakah boleh melakukan action ini
|
||||||
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
UserLevel *UserLevels `json:"user_level,omitempty" gorm:"foreignKey:UserLevelId"`
|
||||||
|
Menu *MasterMenus `json:"menu,omitempty" gorm:"foreignKey:MenuId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserLevelModuleAccesses mengatur akses user_level ke modul-modul tertentu
|
||||||
|
// Contoh: UserLevel "Admin Pusat" bisa akses semua modul, "Editor" hanya bisa akses "create_article" dan "edit_article"
|
||||||
|
type UserLevelModuleAccesses struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserLevelId uint `json:"user_level_id" gorm:"type:int4;not null"`
|
||||||
|
ModuleId uint `json:"module_id" gorm:"type:int4;not null"`
|
||||||
|
CanAccess bool `json:"can_access" gorm:"type:bool;default:true"` // Apakah boleh akses modul ini
|
||||||
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
UserLevel *UserLevels `json:"user_level,omitempty" gorm:"foreignKey:UserLevelId"`
|
||||||
|
Module *MasterModules `json:"module,omitempty" gorm:"foreignKey:ModuleId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -127,6 +127,9 @@ func Models() []interface{} {
|
||||||
entity.MagazineFiles{},
|
entity.MagazineFiles{},
|
||||||
entity.MasterMenus{},
|
entity.MasterMenus{},
|
||||||
entity.MasterModules{},
|
entity.MasterModules{},
|
||||||
|
entity.MenuActions{}, // New: Menu actions (view, create, edit, delete, etc)
|
||||||
|
entity.UserLevelMenuAccesses{}, // New: User level menu access control
|
||||||
|
entity.UserLevelMenuActionAccesses{}, // New: User level menu action access control
|
||||||
entity.MasterStatuses{},
|
entity.MasterStatuses{},
|
||||||
entity.MasterApprovalStatuses{},
|
entity.MasterApprovalStatuses{},
|
||||||
entity.Provinces{},
|
entity.Provinces{},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MenuActionAccessMiddleware struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenuActionAccessMiddleware(db *database.Database) *MenuActionAccessMiddleware {
|
||||||
|
return &MenuActionAccessMiddleware{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMenuAccess middleware untuk validasi akses user_level ke menu tertentu
|
||||||
|
func (m *MenuActionAccessMiddleware) CheckMenuAccess(menuId uint) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Get user from context
|
||||||
|
userCtx := c.Locals("user")
|
||||||
|
if userCtx == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak terautentikasi"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := userCtx.(*entity.Users)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak valid"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role untuk mendapatkan user_level_id
|
||||||
|
var userRole entity.UserRoles
|
||||||
|
if err := m.DB.DB.Where("id = ?", user.UserRoleId).First(&userRole).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Error mendapatkan user role"},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userLevelId := userRole.UserLevelId
|
||||||
|
|
||||||
|
// Check akses user_level ke menu
|
||||||
|
var access entity.UserLevelMenuAccesses
|
||||||
|
err := m.DB.DB.Where(
|
||||||
|
"user_level_id = ? AND menu_id = ? AND is_active = ? AND can_access = ?",
|
||||||
|
userLevelId,
|
||||||
|
menuId,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
).First(&access).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Jika tidak ada record, berarti tidak ada akses
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Anda tidak memiliki akses ke menu ini"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"menu_id": menuId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set menu ke context
|
||||||
|
c.Locals("menu_id", menuId)
|
||||||
|
c.Locals("user_level_id", userLevelId)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMenuActionAccess middleware untuk validasi akses user_level ke action tertentu di dalam menu
|
||||||
|
func (m *MenuActionAccessMiddleware) CheckMenuActionAccess(menuId uint, actionCode string) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Get user from context
|
||||||
|
userCtx := c.Locals("user")
|
||||||
|
if userCtx == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak terautentikasi"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := userCtx.(*entity.Users)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak valid"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role untuk mendapatkan user_level_id
|
||||||
|
var userRole entity.UserRoles
|
||||||
|
if err := m.DB.DB.Where("id = ?", user.UserRoleId).First(&userRole).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Error mendapatkan user role"},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userLevelId := userRole.UserLevelId
|
||||||
|
|
||||||
|
// First, check if user has access to the menu
|
||||||
|
var menuAccess entity.UserLevelMenuAccesses
|
||||||
|
err := m.DB.DB.Where(
|
||||||
|
"user_level_id = ? AND menu_id = ? AND is_active = ? AND can_access = ?",
|
||||||
|
userLevelId,
|
||||||
|
menuId,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
).First(&menuAccess).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Anda tidak memiliki akses ke menu ini"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"menu_id": menuId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, check if user has access to the specific action
|
||||||
|
var actionAccess entity.UserLevelMenuActionAccesses
|
||||||
|
err = m.DB.DB.Where(
|
||||||
|
"user_level_id = ? AND menu_id = ? AND action_code = ? AND is_active = ? AND can_access = ?",
|
||||||
|
userLevelId,
|
||||||
|
menuId,
|
||||||
|
actionCode,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
).First(&actionAccess).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Anda tidak memiliki akses untuk melakukan action ini"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"menu_id": menuId,
|
||||||
|
"action_code": actionCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to context
|
||||||
|
c.Locals("menu_id", menuId)
|
||||||
|
c.Locals("action_code", actionCode)
|
||||||
|
c.Locals("user_level_id", userLevelId)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModuleAccessMiddleware struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModuleAccessMiddleware(db *database.Database) *ModuleAccessMiddleware {
|
||||||
|
return &ModuleAccessMiddleware{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckModuleAccess middleware untuk validasi akses user_level ke modul tertentu
|
||||||
|
// Menggunakan module_id atau path_url sebagai identifier
|
||||||
|
func (m *ModuleAccessMiddleware) CheckModuleAccess(moduleIdentifier interface{}) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Get user from context (diasumsikan sudah ada middleware auth yang set user ke context)
|
||||||
|
userCtx := c.Locals("user")
|
||||||
|
if userCtx == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak terautentikasi"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast user dari context
|
||||||
|
user, ok := userCtx.(*entity.Users)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak valid"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role untuk mendapatkan user_level_id
|
||||||
|
var userRole entity.UserRoles
|
||||||
|
if err := m.DB.DB.Where("id = ?", user.UserRoleId).First(&userRole).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Error mendapatkan user role"},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userLevelId := userRole.UserLevelId
|
||||||
|
|
||||||
|
// Dapatkan module berdasarkan identifier (bisa module_id atau path_url)
|
||||||
|
var module entity.MasterModules
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch v := moduleIdentifier.(type) {
|
||||||
|
case uint:
|
||||||
|
// Jika moduleIdentifier adalah ID
|
||||||
|
err = m.DB.DB.Where("id = ? AND is_active = ?", v, true).First(&module).Error
|
||||||
|
case string:
|
||||||
|
// Jika moduleIdentifier adalah path_url
|
||||||
|
err = m.DB.DB.Where("path_url = ? AND is_active = ?", v, true).First(&module).Error
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 400,
|
||||||
|
"messages": []string{"Module identifier tidak valid"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 404,
|
||||||
|
"messages": []string{"Module tidak ditemukan"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check akses user_level ke module
|
||||||
|
var access entity.UserLevelModuleAccesses
|
||||||
|
err = m.DB.DB.Where(
|
||||||
|
"user_level_id = ? AND module_id = ? AND is_active = ?",
|
||||||
|
userLevelId,
|
||||||
|
module.ID,
|
||||||
|
true,
|
||||||
|
).First(&access).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Jika tidak ada record, berarti tidak ada akses
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Anda tidak memiliki akses ke modul ini"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"module_id": module.ID,
|
||||||
|
"module_name": module.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !access.CanAccess {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Akses ke modul ini ditolak"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"module_id": module.ID,
|
||||||
|
"module_name": module.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set module ke context untuk digunakan di handler
|
||||||
|
c.Locals("module", &module)
|
||||||
|
c.Locals("user_level_id", userLevelId)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckModuleAccessByPath middleware untuk validasi akses berdasarkan path yang sedang diakses
|
||||||
|
// Akan otomatis mencocokkan path dengan module.path_url
|
||||||
|
func (m *ModuleAccessMiddleware) CheckModuleAccessByPath() fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Get user from context
|
||||||
|
userCtx := c.Locals("user")
|
||||||
|
if userCtx == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak terautentikasi"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := userCtx.(*entity.Users)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak valid"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role untuk mendapatkan user_level_id
|
||||||
|
var userRole entity.UserRoles
|
||||||
|
if err := m.DB.DB.Where("id = ?", user.UserRoleId).First(&userRole).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Error mendapatkan user role"},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userLevelId := userRole.UserLevelId
|
||||||
|
currentPath := c.Path()
|
||||||
|
|
||||||
|
// Cari module berdasarkan path_url yang cocok
|
||||||
|
var module entity.MasterModules
|
||||||
|
err := m.DB.DB.Where("path_url = ? AND is_active = ?", currentPath, true).First(&module).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Jika module tidak ditemukan, bisa jadi path ini tidak perlu validasi modul
|
||||||
|
// Atau bisa langsung return error tergantung kebijakan
|
||||||
|
return c.Next() // Skip validation jika module tidak ditemukan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check akses user_level ke module
|
||||||
|
var access entity.UserLevelModuleAccesses
|
||||||
|
err = m.DB.DB.Where(
|
||||||
|
"user_level_id = ? AND module_id = ? AND is_active = ?",
|
||||||
|
userLevelId,
|
||||||
|
module.ID,
|
||||||
|
true,
|
||||||
|
).First(&access).Error
|
||||||
|
|
||||||
|
if err != nil || !access.CanAccess {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Anda tidak memiliki akses ke halaman ini"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"path": currentPath,
|
||||||
|
"module_name": module.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set module ke context
|
||||||
|
c.Locals("module", &module)
|
||||||
|
c.Locals("user_level_id", userLevelId)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMenuAccess middleware untuk validasi akses user_level ke menu beserta modul-modulnya
|
||||||
|
func (m *ModuleAccessMiddleware) CheckMenuAccess(menuId uint) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Get user from context
|
||||||
|
userCtx := c.Locals("user")
|
||||||
|
if userCtx == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak terautentikasi"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := userCtx.(*entity.Users)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 401,
|
||||||
|
"messages": []string{"User tidak valid"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role untuk mendapatkan user_level_id
|
||||||
|
var userRole entity.UserRoles
|
||||||
|
if err := m.DB.DB.Where("id = ?", user.UserRoleId).First(&userRole).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Error mendapatkan user role"},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userLevelId := userRole.UserLevelId
|
||||||
|
|
||||||
|
// Get menu
|
||||||
|
var menu entity.MasterMenus
|
||||||
|
if err := m.DB.DB.Where("id = ? AND is_active = ?", menuId, true).First(&menu).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 404,
|
||||||
|
"messages": []string{"Menu tidak ditemukan"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get semua modul yang ada di menu ini
|
||||||
|
var menuModules []entity.MenuModules
|
||||||
|
if err := m.DB.DB.Where("menu_id = ? AND is_active = ?", menuId, true).Find(&menuModules).Error; err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 500,
|
||||||
|
"messages": []string{"Error mendapatkan menu modules"},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(menuModules) == 0 {
|
||||||
|
// Jika menu tidak punya modul, skip validasi
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check apakah user_level memiliki akses ke minimal satu modul di menu ini
|
||||||
|
hasAccess := false
|
||||||
|
for _, menuModule := range menuModules {
|
||||||
|
var access entity.UserLevelModuleAccesses
|
||||||
|
err := m.DB.DB.Where(
|
||||||
|
"user_level_id = ? AND module_id = ? AND is_active = ? AND can_access = ?",
|
||||||
|
userLevelId,
|
||||||
|
menuModule.ModuleId,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
).First(&access).Error
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
hasAccess = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasAccess {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 403,
|
||||||
|
"messages": []string{"Anda tidak memiliki akses ke menu ini"},
|
||||||
|
"user_level_id": userLevelId,
|
||||||
|
"menu_id": menuId,
|
||||||
|
"menu_name": menu.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set menu ke context
|
||||||
|
c.Locals("menu", &menu)
|
||||||
|
c.Locals("user_level_id", userLevelId)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,23 +7,30 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ArticleCategoriesResponseMapper(articleCategoriesReq *entity.ArticleCategories, host string) (articleCategoriesRes *res.ArticleCategoriesResponse) {
|
// func ArticleCategoriesResponseMapper(articleCategoriesReq *entity.ArticleCategories, host string) (articleCategoriesRes *res.ArticleCategoriesResponse) {
|
||||||
|
func ArticleCategoriesResponseMapper(
|
||||||
|
articleCategoriesReq *entity.ArticleCategories,
|
||||||
|
createdByFullname *string,
|
||||||
|
host string,
|
||||||
|
) (articleCategoriesRes *res.ArticleCategoriesResponse) {
|
||||||
|
|
||||||
if articleCategoriesReq != nil {
|
if articleCategoriesReq != nil {
|
||||||
articleCategoriesRes = &res.ArticleCategoriesResponse{
|
articleCategoriesRes = &res.ArticleCategoriesResponse{
|
||||||
ID: articleCategoriesReq.ID,
|
ID: articleCategoriesReq.ID,
|
||||||
Title: articleCategoriesReq.Title,
|
Title: articleCategoriesReq.Title,
|
||||||
Description: articleCategoriesReq.Description,
|
Description: articleCategoriesReq.Description,
|
||||||
Slug: articleCategoriesReq.Slug,
|
Slug: articleCategoriesReq.Slug,
|
||||||
ThumbnailPath: articleCategoriesReq.ThumbnailPath,
|
ThumbnailPath: articleCategoriesReq.ThumbnailPath,
|
||||||
ParentId: articleCategoriesReq.ParentId,
|
ParentId: articleCategoriesReq.ParentId,
|
||||||
OldCategoryId: articleCategoriesReq.OldCategoryId,
|
OldCategoryId: articleCategoriesReq.OldCategoryId,
|
||||||
CreatedById: articleCategoriesReq.CreatedById,
|
CreatedById: articleCategoriesReq.CreatedById,
|
||||||
StatusId: articleCategoriesReq.StatusId,
|
CreatedByFullname: createdByFullname,
|
||||||
IsPublish: articleCategoriesReq.IsPublish,
|
StatusId: articleCategoriesReq.StatusId,
|
||||||
PublishedAt: articleCategoriesReq.PublishedAt,
|
IsPublish: articleCategoriesReq.IsPublish,
|
||||||
IsActive: articleCategoriesReq.IsActive,
|
PublishedAt: articleCategoriesReq.PublishedAt,
|
||||||
CreatedAt: articleCategoriesReq.CreatedAt,
|
IsActive: articleCategoriesReq.IsActive,
|
||||||
UpdatedAt: articleCategoriesReq.UpdatedAt,
|
CreatedAt: articleCategoriesReq.CreatedAt,
|
||||||
|
UpdatedAt: articleCategoriesReq.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if articleCategoriesReq.Tags != nil {
|
if articleCategoriesReq.Tags != nil {
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,16 @@ type articleCategoriesRepository struct {
|
||||||
Cfg *config.Config
|
Cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ArticleCategoryWithCreator struct {
|
||||||
|
entity.ArticleCategories
|
||||||
|
CreatedByFullname *string `gorm:"column:created_by_fullname"`
|
||||||
|
}
|
||||||
|
|
||||||
// ArticleCategoriesRepository define interface of IArticleCategoriesRepository
|
// ArticleCategoriesRepository define interface of IArticleCategoriesRepository
|
||||||
type ArticleCategoriesRepository interface {
|
type ArticleCategoriesRepository interface {
|
||||||
GetAll(clientId *uuid.UUID, req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error)
|
GetAll(clientId *uuid.UUID, req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error)
|
||||||
FindOne(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error)
|
FindOne(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error)
|
||||||
|
FindOneWithCreator(clientId *uuid.UUID, id uint) (*ArticleCategoryWithCreator, error)
|
||||||
FindOneByOldId(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error)
|
FindOneByOldId(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error)
|
||||||
FindOneBySlug(clientId *uuid.UUID, slug string) (articleCategories *entity.ArticleCategories, err error)
|
FindOneBySlug(clientId *uuid.UUID, slug string) (articleCategories *entity.ArticleCategories, err error)
|
||||||
Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error)
|
Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error)
|
||||||
|
|
@ -174,3 +180,29 @@ func (_i *articleCategoriesRepository) Delete(clientId *uuid.UUID, id uint) erro
|
||||||
}
|
}
|
||||||
return query.Delete(&entity.ArticleCategories{}).Error
|
return query.Delete(&entity.ArticleCategories{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_i *articleCategoriesRepository) FindOneWithCreator(
|
||||||
|
clientId *uuid.UUID,
|
||||||
|
id uint,
|
||||||
|
) (*ArticleCategoryWithCreator, error) {
|
||||||
|
|
||||||
|
var result ArticleCategoryWithCreator
|
||||||
|
|
||||||
|
query := _i.DB.DB.Table("article_categories ac").
|
||||||
|
Select(`
|
||||||
|
ac.*,
|
||||||
|
u.fullname AS created_by_fullname
|
||||||
|
`).
|
||||||
|
Joins("LEFT JOIN users u ON u.id = ac.created_by_id").
|
||||||
|
Where("ac.id = ?", id)
|
||||||
|
|
||||||
|
if clientId != nil {
|
||||||
|
query = query.Where("ac.client_id = ?", clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Scan(&result).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ type ArticleCategoriesUpdateRequest struct {
|
||||||
CreatedById *uint `json:"createdById"`
|
CreatedById *uint `json:"createdById"`
|
||||||
IsPublish *bool `json:"isPublish"`
|
IsPublish *bool `json:"isPublish"`
|
||||||
PublishedAt *time.Time `json:"publishedAt"`
|
PublishedAt *time.Time `json:"publishedAt"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ArticleCategoriesUpdateRequest) ToEntity() *entity.ArticleCategories {
|
func (req ArticleCategoriesUpdateRequest) ToEntity() *entity.ArticleCategories {
|
||||||
|
|
@ -67,6 +68,7 @@ func (req ArticleCategoriesUpdateRequest) ToEntity() *entity.ArticleCategories {
|
||||||
Slug: req.Slug,
|
Slug: req.Slug,
|
||||||
Tags: req.Tags,
|
Tags: req.Tags,
|
||||||
StatusId: req.StatusId,
|
StatusId: req.StatusId,
|
||||||
|
IsActive: req.IsActive,
|
||||||
IsPublish: req.IsPublish,
|
IsPublish: req.IsPublish,
|
||||||
PublishedAt: req.PublishedAt,
|
PublishedAt: req.PublishedAt,
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,22 @@ package response
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type ArticleCategoriesResponse struct {
|
type ArticleCategoriesResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
ThumbnailUrl string `json:"thumbnailUrl"`
|
||||||
Slug *string `json:"slug"`
|
Slug *string `json:"slug"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
ThumbnailPath *string `json:"thumbnailPath"`
|
ThumbnailPath *string `json:"thumbnailPath"`
|
||||||
ParentId *int `json:"parentId"`
|
ParentId *int `json:"parentId"`
|
||||||
OldCategoryId *uint `json:"oldCategoryId"`
|
OldCategoryId *uint `json:"oldCategoryId"`
|
||||||
CreatedById *uint `json:"createdById"`
|
CreatedById *uint `json:"createdById"`
|
||||||
StatusId int `json:"statusId"`
|
CreatedByFullname *string `json:"createdByFullname"`
|
||||||
IsPublish *bool `json:"isPublish"`
|
StatusId int `json:"statusId"`
|
||||||
PublishedAt *time.Time `json:"publishedAt"`
|
IsPublish *bool `json:"isPublish"`
|
||||||
IsEnabled *bool `json:"isEnabled"`
|
PublishedAt *time.Time `json:"publishedAt"`
|
||||||
IsActive *bool `json:"isActive"`
|
IsEnabled *bool `json:"isEnabled"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
IsActive *bool `json:"isActive"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
@ -24,6 +20,11 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ArticleCategoriesService
|
// ArticleCategoriesService
|
||||||
|
|
@ -86,9 +87,16 @@ func (_i *articleCategoriesService) All(authToken string, req request.ArticleCat
|
||||||
|
|
||||||
host := _i.Cfg.App.Domain
|
host := _i.Cfg.App.Domain
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
articleCategoriess = append(articleCategoriess, mapper.ArticleCategoriesResponseMapper(result, host))
|
articleCategoriess = append(
|
||||||
|
articleCategoriess,
|
||||||
|
mapper.ArticleCategoriesResponseMapper(result, nil, host),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for _, result := range results {
|
||||||
|
// articleCategoriess = append(articleCategoriess, mapper.ArticleCategoriesResponseMapper(result, host))
|
||||||
|
// }
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,12 +111,25 @@ func (_i *articleCategoriesService) Show(authToken string, id uint) (articleCate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := _i.Repo.FindOne(clientId, id)
|
result, err := _i.Repo.FindOneWithCreator(clientId, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
host := _i.Cfg.App.Domain
|
host := _i.Cfg.App.Domain
|
||||||
return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
|
||||||
|
return mapper.ArticleCategoriesResponseMapper(
|
||||||
|
&result.ArticleCategories,
|
||||||
|
result.CreatedByFullname,
|
||||||
|
host,
|
||||||
|
), nil
|
||||||
|
|
||||||
|
// result, err := _i.Repo.FindOne(clientId, id)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// host := _i.Cfg.App.Domain
|
||||||
|
// return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articleCategoriesService) ShowByOldId(authToken string, id uint) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
func (_i *articleCategoriesService) ShowByOldId(authToken string, id uint) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
||||||
|
|
@ -127,7 +148,8 @@ func (_i *articleCategoriesService) ShowByOldId(authToken string, id uint) (arti
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
host := _i.Cfg.App.Domain
|
host := _i.Cfg.App.Domain
|
||||||
return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
return mapper.ArticleCategoriesResponseMapper(result, nil, host), nil
|
||||||
|
// return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articleCategoriesService) ShowBySlug(authToken string, slug string) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
func (_i *articleCategoriesService) ShowBySlug(authToken string, slug string) (articleCategories *response.ArticleCategoriesResponse, err error) {
|
||||||
|
|
@ -146,7 +168,8 @@ func (_i *articleCategoriesService) ShowBySlug(authToken string, slug string) (a
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
host := _i.Cfg.App.Domain
|
host := _i.Cfg.App.Domain
|
||||||
return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
return mapper.ArticleCategoriesResponseMapper(result, nil, host), nil
|
||||||
|
// return mapper.ArticleCategoriesResponseMapper(result, host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articleCategoriesService) Save(authToken string, req request.ArticleCategoriesCreateRequest) (articleCategories *entity.ArticleCategories, err error) {
|
func (_i *articleCategoriesService) Save(authToken string, req request.ArticleCategoriesCreateRequest) (articleCategories *entity.ArticleCategories, err error) {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func ArticlesResponseMapper(
|
||||||
if len(articleCategories) > 0 {
|
if len(articleCategories) > 0 {
|
||||||
for _, result := range articleCategories {
|
for _, result := range articleCategories {
|
||||||
if result.Category != nil {
|
if result.Category != nil {
|
||||||
articleCategoriesArr = append(articleCategoriesArr, articleCategoriesMapper.ArticleCategoriesResponseMapper(result.Category, host))
|
articleCategoriesArr = append(articleCategoriesArr, articleCategoriesMapper.ArticleCategoriesResponseMapper(result.Category, nil, host))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Info().Interface("articleCategoriesArr", articleCategoriesArr).Msg("")
|
log.Info().Interface("articleCategoriesArr", articleCategoriesArr).Msg("")
|
||||||
|
|
@ -82,6 +82,7 @@ func ArticlesResponseMapper(
|
||||||
HtmlDescription: articlesReq.HtmlDescription,
|
HtmlDescription: articlesReq.HtmlDescription,
|
||||||
TypeId: articlesReq.TypeId,
|
TypeId: articlesReq.TypeId,
|
||||||
Tags: articlesReq.Tags,
|
Tags: articlesReq.Tags,
|
||||||
|
PublishedFor: articlesReq.PublishedFor,
|
||||||
CategoryId: articlesReq.CategoryId,
|
CategoryId: articlesReq.CategoryId,
|
||||||
AiArticleId: articlesReq.AiArticleId,
|
AiArticleId: articlesReq.AiArticleId,
|
||||||
CategoryName: categoryName,
|
CategoryName: categoryName,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ type ArticlesCreateRequest struct {
|
||||||
IsPublish *bool `json:"isPublish"`
|
IsPublish *bool `json:"isPublish"`
|
||||||
IsDraft *bool `json:"isDraft"`
|
IsDraft *bool `json:"isDraft"`
|
||||||
OldId *uint `json:"oldId"`
|
OldId *uint `json:"oldId"`
|
||||||
|
PublishedFor *string `json:"publishedFor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ArticlesCreateRequest) ToEntity() *entity.Articles {
|
func (req ArticlesCreateRequest) ToEntity() *entity.Articles {
|
||||||
|
|
@ -56,6 +57,7 @@ func (req ArticlesCreateRequest) ToEntity() *entity.Articles {
|
||||||
IsPublish: req.IsPublish,
|
IsPublish: req.IsPublish,
|
||||||
IsDraft: req.IsDraft,
|
IsDraft: req.IsDraft,
|
||||||
OldId: req.OldId,
|
OldId: req.OldId,
|
||||||
|
PublishedFor: req.PublishedFor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +75,7 @@ type ArticlesUpdateRequest struct {
|
||||||
IsPublish *bool `json:"isPublish"`
|
IsPublish *bool `json:"isPublish"`
|
||||||
IsDraft *bool `json:"isDraft"`
|
IsDraft *bool `json:"isDraft"`
|
||||||
StatusId *int `json:"statusId"`
|
StatusId *int `json:"statusId"`
|
||||||
|
PublishedFor *string `json:"publishedFor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ArticlesUpdateRequest) ToEntity() *entity.Articles {
|
func (req ArticlesUpdateRequest) ToEntity() *entity.Articles {
|
||||||
|
|
@ -89,6 +92,7 @@ func (req ArticlesUpdateRequest) ToEntity() *entity.Articles {
|
||||||
IsPublish: req.IsPublish,
|
IsPublish: req.IsPublish,
|
||||||
IsDraft: req.IsDraft,
|
IsDraft: req.IsDraft,
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
|
PublishedFor: req.PublishedFor,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return &entity.Articles{
|
return &entity.Articles{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type ArticlesResponse struct {
|
||||||
CategoryName string `json:"categoryName"`
|
CategoryName string `json:"categoryName"`
|
||||||
TypeId int `json:"typeId"`
|
TypeId int `json:"typeId"`
|
||||||
Tags string `json:"tags"`
|
Tags string `json:"tags"`
|
||||||
|
PublishedFor *string `json:"publishedFor"`
|
||||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
ThumbnailUrl string `json:"thumbnailUrl"`
|
||||||
PageUrl *string `json:"pageUrl"`
|
PageUrl *string `json:"pageUrl"`
|
||||||
CreatedById *uint `json:"createdById"`
|
CreatedById *uint `json:"createdById"`
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ func MasterMenusResponseMapper(masterMenusReq *entity.MasterMenus) (masterMenusR
|
||||||
Description: masterMenusReq.Description,
|
Description: masterMenusReq.Description,
|
||||||
ModuleId: masterMenusReq.ModuleId,
|
ModuleId: masterMenusReq.ModuleId,
|
||||||
ParentMenuId: masterMenusReq.ParentMenuId,
|
ParentMenuId: masterMenusReq.ParentMenuId,
|
||||||
|
Group: masterMenusReq.Group,
|
||||||
Icon: masterMenusReq.Icon,
|
Icon: masterMenusReq.Icon,
|
||||||
Position: masterMenusReq.Position,
|
Position: masterMenusReq.Position,
|
||||||
StatusId: masterMenusReq.StatusId,
|
StatusId: masterMenusReq.StatusId,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"netidhub-saas-be/app/module/master_menus/request"
|
"netidhub-saas-be/app/module/master_menus/request"
|
||||||
"netidhub-saas-be/utils/paginator"
|
"netidhub-saas-be/utils/paginator"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type masterMenusRepository struct {
|
type masterMenusRepository struct {
|
||||||
|
|
@ -87,6 +89,10 @@ func (_i *masterMenusRepository) FindOne(id uint) (masterMenus *entity.MasterMen
|
||||||
func (_i *masterMenusRepository) FindLastMenuPosition() (position *int, err error) {
|
func (_i *masterMenusRepository) FindLastMenuPosition() (position *int, err error) {
|
||||||
var masterMenus *entity.MasterMenus
|
var masterMenus *entity.MasterMenus
|
||||||
if err := _i.DB.DB.Where("position IS NOT NULL").Order(fmt.Sprintf("%s %s", "position", "DESC")).First(&masterMenus).Error; err != nil {
|
if err := _i.DB.DB.Where("position IS NOT NULL").Order(fmt.Sprintf("%s %s", "position", "DESC")).First(&masterMenus).Error; err != nil {
|
||||||
|
// If no record found, return nil without error (it's expected when no menus exist yet)
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,22 @@ type MasterMenusQueryRequest struct {
|
||||||
type MasterMenusCreateRequest struct {
|
type MasterMenusCreateRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
ModuleId int `json:"moduleId" validate:"required"`
|
ModuleId *int `json:"moduleId,omitempty"`
|
||||||
Group string `json:"group" validate:"required"`
|
Group string `json:"group" validate:"required"`
|
||||||
StatusId int `json:"statusId" validate:"required"`
|
StatusId int `json:"statusId" validate:"required"`
|
||||||
ParentMenuId *int `json:"parentMenuId"`
|
ParentMenuId *int `json:"parentMenuId,omitempty"`
|
||||||
Icon *string `json:"icon"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req MasterMenusCreateRequest) ToEntity() *entity.MasterMenus {
|
func (req MasterMenusCreateRequest) ToEntity() *entity.MasterMenus {
|
||||||
|
moduleId := 0
|
||||||
|
if req.ModuleId != nil {
|
||||||
|
moduleId = *req.ModuleId
|
||||||
|
}
|
||||||
return &entity.MasterMenus{
|
return &entity.MasterMenus{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
ModuleId: req.ModuleId,
|
ModuleId: moduleId,
|
||||||
ParentMenuId: req.ParentMenuId,
|
ParentMenuId: req.ParentMenuId,
|
||||||
Icon: req.Icon,
|
Icon: req.Icon,
|
||||||
Group: req.Group,
|
Group: req.Group,
|
||||||
|
|
@ -46,20 +50,24 @@ type MasterMenusUpdateRequest struct {
|
||||||
ID uint `json:"id" validate:"required"`
|
ID uint `json:"id" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
ModuleId int `json:"moduleId" validate:"required"`
|
ModuleId *int `json:"moduleId,omitempty"`
|
||||||
Group string `json:"group" validate:"required"`
|
Group string `json:"group" validate:"required"`
|
||||||
StatusId int `json:"statusId" validate:"required"`
|
StatusId int `json:"statusId" validate:"required"`
|
||||||
ParentMenuId *int `json:"parentMenuId"`
|
ParentMenuId *int `json:"parentMenuId,omitempty"`
|
||||||
Icon *string `json:"icon"`
|
Icon *string `json:"icon,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req MasterMenusUpdateRequest) ToEntity() *entity.MasterMenus {
|
func (req MasterMenusUpdateRequest) ToEntity() *entity.MasterMenus {
|
||||||
|
moduleId := 0
|
||||||
|
if req.ModuleId != nil {
|
||||||
|
moduleId = *req.ModuleId
|
||||||
|
}
|
||||||
return &entity.MasterMenus{
|
return &entity.MasterMenus{
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
ModuleId: req.ModuleId,
|
ModuleId: moduleId,
|
||||||
ParentMenuId: req.ParentMenuId,
|
ParentMenuId: req.ParentMenuId,
|
||||||
Icon: req.Icon,
|
Icon: req.Icon,
|
||||||
Group: req.Group,
|
Group: req.Group,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ type MasterMenusResponse struct {
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ModuleId int `json:"module_id"`
|
ModuleId int `json:"module_id"`
|
||||||
ParentMenuId *int `json:"parent_menu_id"`
|
ParentMenuId *int `json:"parent_menu_id"`
|
||||||
|
Group string `json:"group"`
|
||||||
Icon *string `json:"icon"`
|
Icon *string `json:"icon"`
|
||||||
Position *int `json:"position"`
|
Position *int `json:"position"`
|
||||||
StatusId int `json:"status_id"`
|
StatusId int `json:"status_id"`
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,19 @@ func (_i *masterMenusService) Save(req request.MasterMenusCreateRequest) (err er
|
||||||
_i.Log.Info().Interface("data", req).Msg("")
|
_i.Log.Info().Interface("data", req).Msg("")
|
||||||
|
|
||||||
newReq := req.ToEntity()
|
newReq := req.ToEntity()
|
||||||
var latestPosition, _ = _i.Repo.FindLastMenuPosition()
|
latestPosition, err := _i.Repo.FindLastMenuPosition()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// If no menu with position exists, start with position 1
|
||||||
}
|
position := 1
|
||||||
*latestPosition = *latestPosition + 1
|
newReq.Position = &position
|
||||||
|
} else if latestPosition != nil {
|
||||||
if latestPosition != nil {
|
// Increment the latest position
|
||||||
newReq.Position = latestPosition
|
newPosition := *latestPosition + 1
|
||||||
|
newReq.Position = &newPosition
|
||||||
|
} else {
|
||||||
|
// Fallback: set position to 1
|
||||||
|
position := 1
|
||||||
|
newReq.Position = &position
|
||||||
}
|
}
|
||||||
|
|
||||||
return _i.Repo.Create(newReq)
|
return _i.Repo.Create(newReq)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ func MasterModulesResponseMapper(masterModulesReq *entity.MasterModules) (master
|
||||||
Name: masterModulesReq.Name,
|
Name: masterModulesReq.Name,
|
||||||
Description: masterModulesReq.Description,
|
Description: masterModulesReq.Description,
|
||||||
PathUrl: masterModulesReq.PathUrl,
|
PathUrl: masterModulesReq.PathUrl,
|
||||||
|
ActionType: masterModulesReq.ActionType,
|
||||||
StatusId: masterModulesReq.StatusId,
|
StatusId: masterModulesReq.StatusId,
|
||||||
IsActive: masterModulesReq.IsActive,
|
IsActive: masterModulesReq.IsActive,
|
||||||
CreatedAt: masterModulesReq.CreatedAt,
|
CreatedAt: masterModulesReq.CreatedAt,
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@ type MasterModulesQueryRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MasterModulesCreateRequest struct {
|
type MasterModulesCreateRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
PathUrl string `json:"pathUrl" validate:"required"`
|
PathUrl string `json:"pathUrl" validate:"required"`
|
||||||
StatusId int `json:"statusId" validate:"required"`
|
ActionType *string `json:"actionType"`
|
||||||
|
StatusId int `json:"statusId" validate:"required"`
|
||||||
|
MenuIds []int `json:"menuIds"` // Optional: untuk langsung assign ke menu saat create
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req MasterModulesCreateRequest) ToEntity() *entity.MasterModules {
|
func (req MasterModulesCreateRequest) ToEntity() *entity.MasterModules {
|
||||||
|
|
@ -30,16 +32,19 @@ func (req MasterModulesCreateRequest) ToEntity() *entity.MasterModules {
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
PathUrl: req.PathUrl,
|
PathUrl: req.PathUrl,
|
||||||
|
ActionType: req.ActionType,
|
||||||
StatusId: req.StatusId,
|
StatusId: req.StatusId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MasterModulesUpdateRequest struct {
|
type MasterModulesUpdateRequest struct {
|
||||||
ID uint `json:"id" validate:"required"`
|
ID uint `json:"id" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
PathUrl string `json:"pathUrl" validate:"required"`
|
PathUrl string `json:"pathUrl" validate:"required"`
|
||||||
StatusId int `json:"statusId" validate:"required"`
|
ActionType *string `json:"actionType"`
|
||||||
|
StatusId int `json:"statusId" validate:"required"`
|
||||||
|
MenuIds []int `json:"menuIds"` // Optional: untuk update relasi dengan menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req MasterModulesUpdateRequest) ToEntity() *entity.MasterModules {
|
func (req MasterModulesUpdateRequest) ToEntity() *entity.MasterModules {
|
||||||
|
|
@ -48,6 +53,7 @@ func (req MasterModulesUpdateRequest) ToEntity() *entity.MasterModules {
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
PathUrl: req.PathUrl,
|
PathUrl: req.PathUrl,
|
||||||
|
ActionType: req.ActionType,
|
||||||
StatusId: req.StatusId,
|
StatusId: req.StatusId,
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ type MasterModulesResponse struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
PathUrl string `json:"path_url"`
|
PathUrl string `json:"path_url"`
|
||||||
|
ActionType *string `json:"action_type"`
|
||||||
StatusId int `json:"status_id"`
|
StatusId int `json:"status_id"`
|
||||||
IsActive *bool `json:"is_active"`
|
IsActive *bool `json:"is_active"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
MenuActions MenuActionsController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(menuActionsService service.MenuActionsService) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
MenuActions: NewMenuActionsController(menuActionsService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,260 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/request"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/service"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "netidhub-saas-be/utils/response"
|
||||||
|
utilVal "netidhub-saas-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type menuActionsController struct {
|
||||||
|
menuActionsService service.MenuActionsService
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuActionsController interface {
|
||||||
|
All(c *fiber.Ctx) error
|
||||||
|
GetByMenuId(c *fiber.Ctx) error
|
||||||
|
Show(c *fiber.Ctx) error
|
||||||
|
Save(c *fiber.Ctx) error
|
||||||
|
SaveBatch(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenuActionsController(menuActionsService service.MenuActionsService) MenuActionsController {
|
||||||
|
return &menuActionsController{
|
||||||
|
menuActionsService: menuActionsService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All MenuActions
|
||||||
|
// @Summary Get all MenuActions
|
||||||
|
// @Description API for getting all MenuActions
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param menu_id query int false "Menu ID"
|
||||||
|
// @Param action_code query string false "Action Code"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions [get]
|
||||||
|
func (_i *menuActionsController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := request.MenuActionsQueryRequest{
|
||||||
|
Pagination: paginate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuId := c.Query("menu_id"); menuId != "" {
|
||||||
|
id, _ := strconv.ParseUint(menuId, 10, 0)
|
||||||
|
menuIdUint := uint(id)
|
||||||
|
req.MenuId = &menuIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if actionCode := c.Query("action_code"); actionCode != "" {
|
||||||
|
req.ActionCode = &actionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
menuActionsData, paging, err := _i.menuActionsService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuActions list successfully retrieved"},
|
||||||
|
Data: menuActionsData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByMenuId get MenuActions by Menu ID
|
||||||
|
// @Summary Get MenuActions by Menu ID
|
||||||
|
// @Description API for getting MenuActions by Menu ID
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions/menu/{menu_id} [get]
|
||||||
|
func (_i *menuActionsController) GetByMenuId(c *fiber.Ctx) error {
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuActionsData, err := _i.menuActionsService.GetByMenuId(uint(menuId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuActions by menu successfully retrieved"},
|
||||||
|
Data: menuActionsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show get one MenuAction
|
||||||
|
// @Summary Get one MenuAction
|
||||||
|
// @Description API for getting one MenuAction
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "MenuAction ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions/{id} [get]
|
||||||
|
func (_i *menuActionsController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuActionData, err := _i.menuActionsService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuAction successfully retrieved"},
|
||||||
|
Data: menuActionData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save create MenuAction
|
||||||
|
// @Summary Create MenuAction
|
||||||
|
// @Description API for create MenuAction
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.MenuActionsCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions [post]
|
||||||
|
func (_i *menuActionsController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.MenuActionsCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.menuActionsService.Save(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuAction successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBatch create MenuActions batch
|
||||||
|
// @Summary Create MenuActions batch
|
||||||
|
// @Description API for create MenuActions batch
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.MenuActionsBatchCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions/batch [post]
|
||||||
|
func (_i *menuActionsController) SaveBatch(c *fiber.Ctx) error {
|
||||||
|
req := new(request.MenuActionsBatchCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.menuActionsService.SaveBatch(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuActions batch successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update MenuAction
|
||||||
|
// @Summary Update MenuAction
|
||||||
|
// @Description API for update MenuAction
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "MenuAction ID"
|
||||||
|
// @Param payload body request.MenuActionsUpdateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions/{id} [put]
|
||||||
|
func (_i *menuActionsController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.MenuActionsUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.menuActionsService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuAction successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete MenuAction
|
||||||
|
// @Summary Delete MenuAction
|
||||||
|
// @Description API for delete MenuAction
|
||||||
|
// @Tags MenuActions
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "MenuAction ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-actions/{id} [delete]
|
||||||
|
func (_i *menuActionsController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.menuActionsService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuAction successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
res "netidhub-saas-be/app/module/menu_actions/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MenuActionsResponseMapper(menuActionReq *entity.MenuActions) (menuActionRes *res.MenuActionsResponse) {
|
||||||
|
if menuActionReq != nil {
|
||||||
|
menuActionRes = &res.MenuActionsResponse{
|
||||||
|
ID: menuActionReq.ID,
|
||||||
|
MenuId: menuActionReq.MenuId,
|
||||||
|
ActionCode: menuActionReq.ActionCode,
|
||||||
|
ActionName: menuActionReq.ActionName,
|
||||||
|
Description: menuActionReq.Description,
|
||||||
|
PathUrl: menuActionReq.PathUrl,
|
||||||
|
HttpMethod: menuActionReq.HttpMethod,
|
||||||
|
Position: menuActionReq.Position,
|
||||||
|
IsActive: menuActionReq.IsActive,
|
||||||
|
CreatedAt: menuActionReq.CreatedAt,
|
||||||
|
UpdatedAt: menuActionReq.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return menuActionRes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package menu_actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/controller"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/repository"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct of MenuActionsRouter
|
||||||
|
type MenuActionsRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *controller.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// register bulky of MenuActions module
|
||||||
|
var NewMenuActionsModule = fx.Options(
|
||||||
|
// register repository of MenuActions module
|
||||||
|
fx.Provide(repository.NewMenuActionsRepository),
|
||||||
|
|
||||||
|
// register service of MenuActions module
|
||||||
|
fx.Provide(service.NewMenuActionsService),
|
||||||
|
|
||||||
|
// register controller of MenuActions module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of MenuActions module
|
||||||
|
fx.Provide(NewMenuActionsRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// init MenuActionsRouter
|
||||||
|
func NewMenuActionsRouter(fiber *fiber.App, controller *controller.Controller) *MenuActionsRouter {
|
||||||
|
return &MenuActionsRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register routes of MenuActions module
|
||||||
|
func (_i *MenuActionsRouter) RegisterMenuActionsRoutes() {
|
||||||
|
// define controllers
|
||||||
|
menuActionsController := _i.Controller.MenuActions
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/menu-actions", func(router fiber.Router) {
|
||||||
|
router.Get("/", menuActionsController.All)
|
||||||
|
router.Get("/:id", menuActionsController.Show)
|
||||||
|
router.Get("/menu/:menu_id", menuActionsController.GetByMenuId)
|
||||||
|
router.Post("/", menuActionsController.Save)
|
||||||
|
router.Post("/batch", menuActionsController.SaveBatch)
|
||||||
|
router.Put("/:id", menuActionsController.Update)
|
||||||
|
router.Delete("/:id", menuActionsController.Delete)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/request"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type menuActionsRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuActionsRepository define interface of IMenuActionsRepository
|
||||||
|
type MenuActionsRepository interface {
|
||||||
|
GetAll(req request.MenuActionsQueryRequest) (menuActions []*entity.MenuActions, paging paginator.Pagination, err error)
|
||||||
|
GetByMenuId(menuId uint) (menuActions []*entity.MenuActions, err error)
|
||||||
|
GetByActionCode(menuId uint, actionCode string) (menuAction *entity.MenuActions, err error)
|
||||||
|
FindOne(id uint) (menuAction *entity.MenuActions, err error)
|
||||||
|
Create(menuAction *entity.MenuActions) (err error)
|
||||||
|
CreateBatch(menuActions []*entity.MenuActions) (err error)
|
||||||
|
Update(id uint, menuAction *entity.MenuActions) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
DeleteByMenuId(menuId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenuActionsRepository(db *database.Database) MenuActionsRepository {
|
||||||
|
return &menuActionsRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IMenuActionsRepository
|
||||||
|
func (_i *menuActionsRepository) GetAll(req request.MenuActionsQueryRequest) (menuActions []*entity.MenuActions, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.MenuActions{})
|
||||||
|
query = query.Where("is_active = ?", true)
|
||||||
|
|
||||||
|
if req.MenuId != nil {
|
||||||
|
query = query.Where("menu_id = ?", req.MenuId)
|
||||||
|
}
|
||||||
|
if req.ActionCode != nil && *req.ActionCode != "" {
|
||||||
|
query = query.Where("action_code = ?", req.ActionCode)
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
query = query.Where("client_id = ?", req.ClientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload relations
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||||
|
} else {
|
||||||
|
query.Order("position ASC")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination (manual calculation like articles)
|
||||||
|
page := req.Pagination.Page
|
||||||
|
limit := req.Pagination.Limit
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err = query.Offset(offset).Limit(limit).Find(&menuActions).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pagination response
|
||||||
|
paging = paginator.Pagination{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
Count: count,
|
||||||
|
TotalPage: int((count + int64(limit) - 1) / int64(limit)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) GetByMenuId(menuId uint) (menuActions []*entity.MenuActions, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.MenuActions{})
|
||||||
|
query = query.Where("menu_id = ? AND is_active = ?", menuId, true)
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
query = query.Order("position ASC")
|
||||||
|
err = query.Find(&menuActions).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) GetByActionCode(menuId uint, actionCode string) (menuAction *entity.MenuActions, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.MenuActions{})
|
||||||
|
query = query.Where("menu_id = ? AND action_code = ? AND is_active = ?", menuId, actionCode, true)
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
err = query.First(&menuAction).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) FindOne(id uint) (menuAction *entity.MenuActions, err error) {
|
||||||
|
query := _i.DB.DB.Preload("Menu")
|
||||||
|
if err := query.First(&menuAction, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return menuAction, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) Create(menuAction *entity.MenuActions) (err error) {
|
||||||
|
return _i.DB.DB.Create(menuAction).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) CreateBatch(menuActions []*entity.MenuActions) (err error) {
|
||||||
|
return _i.DB.DB.Create(&menuActions).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) Update(id uint, menuAction *entity.MenuActions) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.MenuActions{}).
|
||||||
|
Where(&entity.MenuActions{ID: id}).
|
||||||
|
Updates(menuAction).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) Delete(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Delete(&entity.MenuActions{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsRepository) DeleteByMenuId(menuId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("menu_id = ?", menuId).Delete(&entity.MenuActions{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MenuActionsQueryRequest struct {
|
||||||
|
MenuId *uint `query:"menu_id"`
|
||||||
|
ActionCode *string `query:"action_code"`
|
||||||
|
ClientId *uuid.UUID `query:"client_id"`
|
||||||
|
Pagination *paginator.Pagination `query:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuActionsCreateRequest struct {
|
||||||
|
MenuId uint `json:"menuId" validate:"required"`
|
||||||
|
ActionCode string `json:"actionCode" validate:"required"`
|
||||||
|
ActionName string `json:"actionName" validate:"required"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
PathUrl *string `json:"pathUrl"`
|
||||||
|
HttpMethod *string `json:"httpMethod"`
|
||||||
|
Position *int `json:"position"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req MenuActionsCreateRequest) ToEntity() *entity.MenuActions {
|
||||||
|
return &entity.MenuActions{
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ActionCode: req.ActionCode,
|
||||||
|
ActionName: req.ActionName,
|
||||||
|
Description: req.Description,
|
||||||
|
PathUrl: req.PathUrl,
|
||||||
|
HttpMethod: req.HttpMethod,
|
||||||
|
Position: req.Position,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuActionsUpdateRequest struct {
|
||||||
|
MenuId uint `json:"menuId"`
|
||||||
|
ActionCode string `json:"actionCode"`
|
||||||
|
ActionName string `json:"actionName" validate:"required"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
PathUrl *string `json:"pathUrl"`
|
||||||
|
HttpMethod *string `json:"httpMethod"`
|
||||||
|
Position *int `json:"position"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req MenuActionsUpdateRequest) ToEntity() *entity.MenuActions {
|
||||||
|
return &entity.MenuActions{
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ActionCode: req.ActionCode,
|
||||||
|
ActionName: req.ActionName,
|
||||||
|
Description: req.Description,
|
||||||
|
PathUrl: req.PathUrl,
|
||||||
|
HttpMethod: req.HttpMethod,
|
||||||
|
Position: req.Position,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuActionsBatchCreateRequest struct {
|
||||||
|
MenuId uint `json:"menuId" validate:"required"`
|
||||||
|
ActionCodes []string `json:"actionCodes" validate:"required,min=1"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type MenuActionsResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
MenuId uint `json:"menuId"`
|
||||||
|
ActionCode string `json:"actionCode"`
|
||||||
|
ActionName string `json:"actionName"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
PathUrl *string `json:"pathUrl"`
|
||||||
|
HttpMethod *string `json:"httpMethod"`
|
||||||
|
Position *int `json:"position"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/mapper"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/repository"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/request"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions/response"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MenuActionsService
|
||||||
|
type menuActionsService struct {
|
||||||
|
Repo repository.MenuActionsRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuActionsService define interface of IMenuActionsService
|
||||||
|
type MenuActionsService interface {
|
||||||
|
All(req request.MenuActionsQueryRequest) (menuActions []*response.MenuActionsResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByMenuId(menuId uint) (menuActions []*response.MenuActionsResponse, err error)
|
||||||
|
Show(id uint) (menuAction *response.MenuActionsResponse, err error)
|
||||||
|
Save(req request.MenuActionsCreateRequest) (err error)
|
||||||
|
SaveBatch(req request.MenuActionsBatchCreateRequest) (err error)
|
||||||
|
Update(id uint, req request.MenuActionsUpdateRequest) (err error)
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMenuActionsService init MenuActionsService
|
||||||
|
func NewMenuActionsService(repo repository.MenuActionsRepository, log zerolog.Logger) MenuActionsService {
|
||||||
|
return &menuActionsService{
|
||||||
|
Repo: repo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All implement interface of MenuActionsService
|
||||||
|
func (_i *menuActionsService) All(req request.MenuActionsQueryRequest) (menuActions []*response.MenuActionsResponse, paging paginator.Pagination, err error) {
|
||||||
|
results, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
menuActions = append(menuActions, mapper.MenuActionsResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsService) GetByMenuId(menuId uint) (menuActions []*response.MenuActionsResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByMenuId(menuId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
menuActions = append(menuActions, mapper.MenuActionsResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsService) Show(id uint) (menuAction *response.MenuActionsResponse, err error) {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.MenuActionsResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsService) Save(req request.MenuActionsCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating menu action")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
menuAction := req.ToEntity()
|
||||||
|
menuAction.IsActive = &isActive
|
||||||
|
|
||||||
|
return _i.Repo.Create(menuAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsService) SaveBatch(req request.MenuActionsBatchCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating menu actions batch")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
var menuActions []*entity.MenuActions
|
||||||
|
|
||||||
|
// Standard action names mapping
|
||||||
|
actionNameMap := map[string]string{
|
||||||
|
"view": "View",
|
||||||
|
"create": "Create",
|
||||||
|
"edit": "Edit",
|
||||||
|
"update": "Update",
|
||||||
|
"delete": "Delete",
|
||||||
|
"approve": "Approve",
|
||||||
|
"reject": "Reject",
|
||||||
|
"export": "Export",
|
||||||
|
"import": "Import",
|
||||||
|
"print": "Print",
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, actionCode := range req.ActionCodes {
|
||||||
|
position := idx + 1
|
||||||
|
actionName := actionNameMap[actionCode]
|
||||||
|
if actionName == "" {
|
||||||
|
// Capitalize first letter if not in map
|
||||||
|
if len(actionCode) > 0 {
|
||||||
|
actionName = string(actionCode[0]-32) + actionCode[1:]
|
||||||
|
} else {
|
||||||
|
actionName = actionCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menuAction := &entity.MenuActions{
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ActionCode: actionCode,
|
||||||
|
ActionName: actionName,
|
||||||
|
Position: &position,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
menuActions = append(menuActions, menuAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.CreateBatch(menuActions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsService) Update(id uint, req request.MenuActionsUpdateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Updating menu action")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
menuAction := req.ToEntity()
|
||||||
|
menuAction.IsActive = &isActive
|
||||||
|
|
||||||
|
return _i.Repo.Update(id, menuAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuActionsService) Delete(id uint) error {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive := false
|
||||||
|
result.IsActive = &isActive
|
||||||
|
return _i.Repo.Update(id, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "netidhub-saas-be/app/module/menu_modules/service"
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
MenuModules MenuModulesController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(menuModulesService service.MenuModulesService) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
MenuModules: NewMenuModulesController(menuModulesService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,293 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/request"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/service"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "netidhub-saas-be/utils/response"
|
||||||
|
utilVal "netidhub-saas-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type menuModulesController struct {
|
||||||
|
menuModulesService service.MenuModulesService
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuModulesController interface {
|
||||||
|
All(c *fiber.Ctx) error
|
||||||
|
GetByMenuId(c *fiber.Ctx) error
|
||||||
|
GetByModuleId(c *fiber.Ctx) error
|
||||||
|
Show(c *fiber.Ctx) error
|
||||||
|
Save(c *fiber.Ctx) error
|
||||||
|
SaveBatch(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenuModulesController(menuModulesService service.MenuModulesService) MenuModulesController {
|
||||||
|
return &menuModulesController{
|
||||||
|
menuModulesService: menuModulesService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All MenuModules
|
||||||
|
// @Summary Get all MenuModules
|
||||||
|
// @Description API for getting all MenuModules
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param menu_id query int false "Menu ID"
|
||||||
|
// @Param module_id query int false "Module ID"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules [get]
|
||||||
|
func (_i *menuModulesController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := request.MenuModulesQueryRequest{
|
||||||
|
Pagination: paginate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuId := c.Query("menu_id"); menuId != "" {
|
||||||
|
id, _ := strconv.ParseUint(menuId, 10, 0)
|
||||||
|
menuIdUint := uint(id)
|
||||||
|
req.MenuId = &menuIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleId := c.Query("module_id"); moduleId != "" {
|
||||||
|
id, _ := strconv.ParseUint(moduleId, 10, 0)
|
||||||
|
moduleIdUint := uint(id)
|
||||||
|
req.ModuleId = &moduleIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
menuModulesData, paging, err := _i.menuModulesService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModules list successfully retrieved"},
|
||||||
|
Data: menuModulesData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByMenuId get MenuModules by Menu ID
|
||||||
|
// @Summary Get MenuModules by Menu ID
|
||||||
|
// @Description API for getting MenuModules by Menu ID
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules/menu/{menu_id} [get]
|
||||||
|
func (_i *menuModulesController) GetByMenuId(c *fiber.Ctx) error {
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuModulesData, err := _i.menuModulesService.GetByMenuId(uint(menuId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModules by menu successfully retrieved"},
|
||||||
|
Data: menuModulesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByModuleId get MenuModules by Module ID
|
||||||
|
// @Summary Get MenuModules by Module ID
|
||||||
|
// @Description API for getting MenuModules by Module ID
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param module_id path int true "Module ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules/module/{module_id} [get]
|
||||||
|
func (_i *menuModulesController) GetByModuleId(c *fiber.Ctx) error {
|
||||||
|
moduleId, err := strconv.ParseUint(c.Params("module_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuModulesData, err := _i.menuModulesService.GetByModuleId(uint(moduleId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModules by module successfully retrieved"},
|
||||||
|
Data: menuModulesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show get one MenuModule
|
||||||
|
// @Summary Get one MenuModule
|
||||||
|
// @Description API for getting one MenuModule
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "MenuModule ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules/{id} [get]
|
||||||
|
func (_i *menuModulesController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuModuleData, err := _i.menuModulesService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModule successfully retrieved"},
|
||||||
|
Data: menuModuleData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save create MenuModule
|
||||||
|
// @Summary Create MenuModule
|
||||||
|
// @Description API for create MenuModule
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.MenuModulesCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules [post]
|
||||||
|
func (_i *menuModulesController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.MenuModulesCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.menuModulesService.Save(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModule successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBatch create multiple MenuModules at once
|
||||||
|
// @Summary Create multiple MenuModules
|
||||||
|
// @Description API for creating multiple MenuModules at once for a menu
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.MenuModulesBatchCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules/batch [post]
|
||||||
|
func (_i *menuModulesController) SaveBatch(c *fiber.Ctx) error {
|
||||||
|
req := new(request.MenuModulesBatchCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.menuModulesService.SaveBatch(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModules successfully created in batch"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update MenuModule
|
||||||
|
// @Summary Update MenuModule
|
||||||
|
// @Description API for update MenuModule
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.MenuModulesUpdateRequest true "Required payload"
|
||||||
|
// @Param id path int true "MenuModule ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 401 {object} response.Response
|
||||||
|
// @Failure 404 {object} response.Response
|
||||||
|
// @Failure 422 {object} response.Response
|
||||||
|
// @Failure 500 {object} response.Response
|
||||||
|
// @Router /menu-modules/{id} [put]
|
||||||
|
func (_i *menuModulesController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.MenuModulesUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.menuModulesService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModule successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete MenuModule
|
||||||
|
// @Summary Delete MenuModule
|
||||||
|
// @Description API for delete MenuModule (soft delete)
|
||||||
|
// @Tags MenuModules
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "MenuModule ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /menu-modules/{id} [delete]
|
||||||
|
func (_i *menuModulesController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.menuModulesService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"MenuModule successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MenuModulesResponseMapper(e *entity.MenuModules) *response.MenuModulesResponse {
|
||||||
|
resp := &response.MenuModulesResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
MenuId: e.MenuId,
|
||||||
|
ModuleId: e.ModuleId,
|
||||||
|
Position: e.Position,
|
||||||
|
ClientId: e.ClientId,
|
||||||
|
IsActive: e.IsActive,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Menu != nil {
|
||||||
|
resp.Menu = &response.MenuBasicResponse{
|
||||||
|
ID: e.Menu.ID,
|
||||||
|
Name: e.Menu.Name,
|
||||||
|
Description: e.Menu.Description,
|
||||||
|
Icon: e.Menu.Icon,
|
||||||
|
Group: e.Menu.Group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Module != nil {
|
||||||
|
resp.Module = &response.ModuleBasicResponse{
|
||||||
|
ID: e.Module.ID,
|
||||||
|
Name: e.Module.Name,
|
||||||
|
Description: e.Module.Description,
|
||||||
|
PathUrl: e.Module.PathUrl,
|
||||||
|
ActionType: e.Module.ActionType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package menu_modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/controller"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/repository"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct of MenuModulesRouter
|
||||||
|
type MenuModulesRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *controller.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// register bulky of MenuModules module
|
||||||
|
var NewMenuModulesModule = fx.Options(
|
||||||
|
// register repository of MenuModules module
|
||||||
|
fx.Provide(repository.NewMenuModulesRepository),
|
||||||
|
|
||||||
|
// register service of MenuModules module
|
||||||
|
fx.Provide(service.NewMenuModulesService),
|
||||||
|
|
||||||
|
// register controller of MenuModules module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of MenuModules module
|
||||||
|
fx.Provide(NewMenuModulesRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// init MenuModulesRouter
|
||||||
|
func NewMenuModulesRouter(fiber *fiber.App, controller *controller.Controller) *MenuModulesRouter {
|
||||||
|
return &MenuModulesRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register routes of MenuModules module
|
||||||
|
func (_i *MenuModulesRouter) RegisterMenuModulesRoutes() {
|
||||||
|
// define controllers
|
||||||
|
menuModulesController := _i.Controller.MenuModules
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/menu-modules", func(router fiber.Router) {
|
||||||
|
router.Get("/", menuModulesController.All)
|
||||||
|
router.Get("/:id", menuModulesController.Show)
|
||||||
|
router.Get("/menu/:menu_id", menuModulesController.GetByMenuId)
|
||||||
|
router.Get("/module/:module_id", menuModulesController.GetByModuleId)
|
||||||
|
router.Post("/", menuModulesController.Save)
|
||||||
|
router.Post("/batch", menuModulesController.SaveBatch)
|
||||||
|
router.Put("/:id", menuModulesController.Update)
|
||||||
|
router.Delete("/:id", menuModulesController.Delete)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/request"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type menuModulesRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuModulesRepository define interface of IMenuModulesRepository
|
||||||
|
type MenuModulesRepository interface {
|
||||||
|
GetAll(req request.MenuModulesQueryRequest) (menuModules []*entity.MenuModules, paging paginator.Pagination, err error)
|
||||||
|
GetByMenuId(menuId uint) (menuModules []*entity.MenuModules, err error)
|
||||||
|
GetByModuleId(moduleId uint) (menuModules []*entity.MenuModules, err error)
|
||||||
|
FindOne(id uint) (menuModule *entity.MenuModules, err error)
|
||||||
|
Create(menuModule *entity.MenuModules) (err error)
|
||||||
|
CreateBatch(menuModules []*entity.MenuModules) (err error)
|
||||||
|
Update(id uint, menuModule *entity.MenuModules) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
DeleteByMenuId(menuId uint) (err error)
|
||||||
|
DeleteByModuleId(moduleId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenuModulesRepository(db *database.Database) MenuModulesRepository {
|
||||||
|
return &menuModulesRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IMenuModulesRepository
|
||||||
|
func (_i *menuModulesRepository) GetAll(req request.MenuModulesQueryRequest) (menuModules []*entity.MenuModules, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.MenuModules{})
|
||||||
|
query = query.Where("is_active = ?", true)
|
||||||
|
|
||||||
|
if req.MenuId != nil {
|
||||||
|
query = query.Where("menu_id = ?", req.MenuId)
|
||||||
|
}
|
||||||
|
if req.ModuleId != nil {
|
||||||
|
query = query.Where("module_id = ?", req.ModuleId)
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
query = query.Where("client_id = ?", req.ClientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload relations
|
||||||
|
query = query.Preload("Menu").Preload("Module")
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||||
|
} else {
|
||||||
|
query.Order("position ASC")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination (manual calculation like articles)
|
||||||
|
page := req.Pagination.Page
|
||||||
|
limit := req.Pagination.Limit
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err = query.Offset(offset).Limit(limit).Find(&menuModules).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pagination response
|
||||||
|
paging = paginator.Pagination{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
Count: count,
|
||||||
|
TotalPage: int((count + int64(limit) - 1) / int64(limit)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) GetByMenuId(menuId uint) (menuModules []*entity.MenuModules, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.MenuModules{})
|
||||||
|
query = query.Where("menu_id = ? AND is_active = ?", menuId, true)
|
||||||
|
query = query.Preload("Module")
|
||||||
|
query = query.Order("position ASC")
|
||||||
|
err = query.Find(&menuModules).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) GetByModuleId(moduleId uint) (menuModules []*entity.MenuModules, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.MenuModules{})
|
||||||
|
query = query.Where("module_id = ? AND is_active = ?", moduleId, true)
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
err = query.Find(&menuModules).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) FindOne(id uint) (menuModule *entity.MenuModules, err error) {
|
||||||
|
query := _i.DB.DB.Preload("Menu").Preload("Module")
|
||||||
|
if err := query.First(&menuModule, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return menuModule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) Create(menuModule *entity.MenuModules) (err error) {
|
||||||
|
return _i.DB.DB.Create(menuModule).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) CreateBatch(menuModules []*entity.MenuModules) (err error) {
|
||||||
|
return _i.DB.DB.Create(&menuModules).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) Update(id uint, menuModule *entity.MenuModules) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.MenuModules{}).
|
||||||
|
Where(&entity.MenuModules{ID: id}).
|
||||||
|
Updates(menuModule).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) Delete(id uint) error {
|
||||||
|
return _i.DB.DB.Delete(&entity.MenuModules{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) DeleteByMenuId(menuId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("menu_id = ?", menuId).Delete(&entity.MenuModules{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesRepository) DeleteByModuleId(moduleId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("module_id = ?", moduleId).Delete(&entity.MenuModules{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MenuModulesQueryRequest struct {
|
||||||
|
MenuId *uint `query:"menu_id"`
|
||||||
|
ModuleId *uint `query:"module_id"`
|
||||||
|
ClientId *uuid.UUID `query:"client_id"`
|
||||||
|
Pagination *paginator.Pagination `query:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuModulesCreateRequest struct {
|
||||||
|
MenuId uint `json:"menu_id" validate:"required"`
|
||||||
|
ModuleId uint `json:"module_id" validate:"required"`
|
||||||
|
Position *int `json:"position"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuModulesBatchCreateRequest struct {
|
||||||
|
MenuId uint `json:"menu_id" validate:"required"`
|
||||||
|
ModuleIds []uint `json:"module_ids" validate:"required,min=1"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuModulesUpdateRequest struct {
|
||||||
|
MenuId *uint `json:"menu_id"`
|
||||||
|
ModuleId *uint `json:"module_id"`
|
||||||
|
Position *int `json:"position"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MenuModulesResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
MenuId uint `json:"menu_id"`
|
||||||
|
ModuleId uint `json:"module_id"`
|
||||||
|
Position *int `json:"position"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Menu *MenuBasicResponse `json:"menu,omitempty"`
|
||||||
|
Module *ModuleBasicResponse `json:"module,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuBasicResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Icon *string `json:"icon"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleBasicResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
PathUrl string `json:"path_url"`
|
||||||
|
ActionType *string `json:"action_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/mapper"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/repository"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/request"
|
||||||
|
"netidhub-saas-be/app/module/menu_modules/response"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MenuModulesService
|
||||||
|
type menuModulesService struct {
|
||||||
|
Repo repository.MenuModulesRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuModulesService define interface of IMenuModulesService
|
||||||
|
type MenuModulesService interface {
|
||||||
|
All(req request.MenuModulesQueryRequest) (menuModules []*response.MenuModulesResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByMenuId(menuId uint) (menuModules []*response.MenuModulesResponse, err error)
|
||||||
|
GetByModuleId(moduleId uint) (menuModules []*response.MenuModulesResponse, err error)
|
||||||
|
Show(id uint) (menuModule *response.MenuModulesResponse, err error)
|
||||||
|
Save(req request.MenuModulesCreateRequest) (err error)
|
||||||
|
SaveBatch(req request.MenuModulesBatchCreateRequest) (err error)
|
||||||
|
Update(id uint, req request.MenuModulesUpdateRequest) (err error)
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMenuModulesService init MenuModulesService
|
||||||
|
func NewMenuModulesService(repo repository.MenuModulesRepository, log zerolog.Logger) MenuModulesService {
|
||||||
|
return &menuModulesService{
|
||||||
|
Repo: repo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All implement interface of MenuModulesService
|
||||||
|
func (_i *menuModulesService) All(req request.MenuModulesQueryRequest) (menuModules []*response.MenuModulesResponse, paging paginator.Pagination, err error) {
|
||||||
|
results, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
menuModules = append(menuModules, mapper.MenuModulesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) GetByMenuId(menuId uint) (menuModules []*response.MenuModulesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByMenuId(menuId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
menuModules = append(menuModules, mapper.MenuModulesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) GetByModuleId(moduleId uint) (menuModules []*response.MenuModulesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByModuleId(moduleId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
menuModules = append(menuModules, mapper.MenuModulesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) Show(id uint) (menuModule *response.MenuModulesResponse, err error) {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.MenuModulesResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) Save(req request.MenuModulesCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating menu module")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
menuModule := &entity.MenuModules{
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ModuleId: req.ModuleId,
|
||||||
|
Position: req.Position,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.Create(menuModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) SaveBatch(req request.MenuModulesBatchCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating menu modules batch")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
var menuModules []*entity.MenuModules
|
||||||
|
|
||||||
|
for idx, moduleId := range req.ModuleIds {
|
||||||
|
position := idx + 1
|
||||||
|
menuModule := &entity.MenuModules{
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ModuleId: moduleId,
|
||||||
|
Position: &position,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
menuModules = append(menuModules, menuModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.CreateBatch(menuModules)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) Update(id uint, req request.MenuModulesUpdateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Updating menu module")
|
||||||
|
|
||||||
|
menuModule := &entity.MenuModules{}
|
||||||
|
if req.MenuId != nil {
|
||||||
|
menuModule.MenuId = *req.MenuId
|
||||||
|
}
|
||||||
|
if req.ModuleId != nil {
|
||||||
|
menuModule.ModuleId = *req.ModuleId
|
||||||
|
}
|
||||||
|
if req.Position != nil {
|
||||||
|
menuModule.Position = req.Position
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
menuModule.ClientId = req.ClientId
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
menuModule.IsActive = req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.Update(id, menuModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *menuModulesService) Delete(id uint) error {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive := false
|
||||||
|
result.IsActive = &isActive
|
||||||
|
return _i.Repo.Update(id, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
UserLevelMenuAccesses UserLevelMenuAccessesController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(userLevelMenuAccessesService service.UserLevelMenuAccessesService) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
UserLevelMenuAccesses: NewUserLevelMenuAccessesController(userLevelMenuAccessesService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/request"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/service"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "netidhub-saas-be/utils/response"
|
||||||
|
utilVal "netidhub-saas-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userLevelMenuAccessesController struct {
|
||||||
|
userLevelMenuAccessesService service.UserLevelMenuAccessesService
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuAccessesController interface {
|
||||||
|
All(c *fiber.Ctx) error
|
||||||
|
GetByUserLevelId(c *fiber.Ctx) error
|
||||||
|
GetByMenuId(c *fiber.Ctx) error
|
||||||
|
CheckAccess(c *fiber.Ctx) error
|
||||||
|
Show(c *fiber.Ctx) error
|
||||||
|
Save(c *fiber.Ctx) error
|
||||||
|
SaveBatch(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserLevelMenuAccessesController(userLevelMenuAccessesService service.UserLevelMenuAccessesService) UserLevelMenuAccessesController {
|
||||||
|
return &userLevelMenuAccessesController{
|
||||||
|
userLevelMenuAccessesService: userLevelMenuAccessesService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All UserLevelMenuAccesses
|
||||||
|
// @Summary Get all UserLevelMenuAccesses
|
||||||
|
// @Description API for getting all UserLevelMenuAccesses
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id query int false "User Level ID"
|
||||||
|
// @Param menu_id query int false "Menu ID"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses [get]
|
||||||
|
func (_i *userLevelMenuAccessesController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := request.UserLevelMenuAccessesQueryRequest{
|
||||||
|
Pagination: paginate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if userLevelId := c.Query("user_level_id"); userLevelId != "" {
|
||||||
|
id, _ := strconv.ParseUint(userLevelId, 10, 0)
|
||||||
|
userLevelIdUint := uint(id)
|
||||||
|
req.UserLevelId = &userLevelIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuId := c.Query("menu_id"); menuId != "" {
|
||||||
|
id, _ := strconv.ParseUint(menuId, 10, 0)
|
||||||
|
menuIdUint := uint(id)
|
||||||
|
req.MenuId = &menuIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, paging, err := _i.userLevelMenuAccessesService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccesses list successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserLevelId get UserLevelMenuAccesses by User Level ID
|
||||||
|
// @Summary Get UserLevelMenuAccesses by User Level ID
|
||||||
|
// @Description API for getting UserLevelMenuAccesses by User Level ID
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id path int true "User Level ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/user-level/{user_level_id} [get]
|
||||||
|
func (_i *userLevelMenuAccessesController) GetByUserLevelId(c *fiber.Ctx) error {
|
||||||
|
userLevelId, err := strconv.ParseUint(c.Params("user_level_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelMenuAccessesService.GetByUserLevelId(uint(userLevelId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccesses by user level successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByMenuId get UserLevelMenuAccesses by Menu ID
|
||||||
|
// @Summary Get UserLevelMenuAccesses by Menu ID
|
||||||
|
// @Description API for getting UserLevelMenuAccesses by Menu ID
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/menu/{menu_id} [get]
|
||||||
|
func (_i *userLevelMenuAccessesController) GetByMenuId(c *fiber.Ctx) error {
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelMenuAccessesService.GetByMenuId(uint(menuId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccesses by menu successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAccess check if user level has access to menu
|
||||||
|
// @Summary Check User Level Menu Access
|
||||||
|
// @Description API for checking if user level has access to menu
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id path int true "User Level ID"
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/check/{user_level_id}/{menu_id} [get]
|
||||||
|
func (_i *userLevelMenuAccessesController) CheckAccess(c *fiber.Ctx) error {
|
||||||
|
userLevelId, err := strconv.ParseUint(c.Params("user_level_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAccess, err := _i.userLevelMenuAccessesService.CheckAccess(uint(userLevelId), uint(menuId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Access check completed"},
|
||||||
|
Data: fiber.Map{"has_access": hasAccess},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show get one UserLevelMenuAccess
|
||||||
|
// @Summary Get one UserLevelMenuAccess
|
||||||
|
// @Description API for getting one UserLevelMenuAccess
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "UserLevelMenuAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/{id} [get]
|
||||||
|
func (_i *userLevelMenuAccessesController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessData, err := _i.userLevelMenuAccessesService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccess successfully retrieved"},
|
||||||
|
Data: accessData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save create UserLevelMenuAccess
|
||||||
|
// @Summary Create UserLevelMenuAccess
|
||||||
|
// @Description API for create UserLevelMenuAccess
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelMenuAccessesCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses [post]
|
||||||
|
func (_i *userLevelMenuAccessesController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.UserLevelMenuAccessesCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.userLevelMenuAccessesService.Save(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccess successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBatch create UserLevelMenuAccesses batch
|
||||||
|
// @Summary Create UserLevelMenuAccesses batch
|
||||||
|
// @Description API for create UserLevelMenuAccesses batch
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelMenuAccessesBatchCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/batch [post]
|
||||||
|
func (_i *userLevelMenuAccessesController) SaveBatch(c *fiber.Ctx) error {
|
||||||
|
req := new(request.UserLevelMenuAccessesBatchCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.userLevelMenuAccessesService.SaveBatch(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccesses batch successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UserLevelMenuAccess
|
||||||
|
// @Summary Update UserLevelMenuAccess
|
||||||
|
// @Description API for update UserLevelMenuAccess
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "UserLevelMenuAccess ID"
|
||||||
|
// @Param payload body request.UserLevelMenuAccessesUpdateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/{id} [put]
|
||||||
|
func (_i *userLevelMenuAccessesController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.UserLevelMenuAccessesUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.userLevelMenuAccessesService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccess successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete UserLevelMenuAccess
|
||||||
|
// @Summary Delete UserLevelMenuAccess
|
||||||
|
// @Description API for delete UserLevelMenuAccess
|
||||||
|
// @Tags UserLevelMenuAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "UserLevelMenuAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-accesses/{id} [delete]
|
||||||
|
func (_i *userLevelMenuAccessesController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.userLevelMenuAccessesService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuAccess successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
res "netidhub-saas-be/app/module/user_level_menu_accesses/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserLevelMenuAccessesResponseMapper(accessReq *entity.UserLevelMenuAccesses) (accessRes *res.UserLevelMenuAccessesResponse) {
|
||||||
|
if accessReq != nil {
|
||||||
|
accessRes = &res.UserLevelMenuAccessesResponse{
|
||||||
|
ID: accessReq.ID,
|
||||||
|
UserLevelId: accessReq.UserLevelId,
|
||||||
|
MenuId: accessReq.MenuId,
|
||||||
|
CanAccess: accessReq.CanAccess,
|
||||||
|
IsActive: accessReq.IsActive,
|
||||||
|
CreatedAt: accessReq.CreatedAt,
|
||||||
|
UpdatedAt: accessReq.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessRes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/request"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userLevelMenuAccessesRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLevelMenuAccessesRepository define interface of IUserLevelMenuAccessesRepository
|
||||||
|
type UserLevelMenuAccessesRepository interface {
|
||||||
|
GetAll(req request.UserLevelMenuAccessesQueryRequest) (accesses []*entity.UserLevelMenuAccesses, paging paginator.Pagination, err error)
|
||||||
|
GetByUserLevelId(userLevelId uint) (accesses []*entity.UserLevelMenuAccesses, err error)
|
||||||
|
GetByMenuId(menuId uint) (accesses []*entity.UserLevelMenuAccesses, err error)
|
||||||
|
CheckAccess(userLevelId uint, menuId uint) (hasAccess bool, err error)
|
||||||
|
FindOne(id uint) (access *entity.UserLevelMenuAccesses, err error)
|
||||||
|
Create(access *entity.UserLevelMenuAccesses) (err error)
|
||||||
|
CreateBatch(accesses []*entity.UserLevelMenuAccesses) (err error)
|
||||||
|
Update(id uint, access *entity.UserLevelMenuAccesses) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
DeleteByUserLevelId(userLevelId uint) (err error)
|
||||||
|
DeleteByMenuId(menuId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserLevelMenuAccessesRepository(db *database.Database) UserLevelMenuAccessesRepository {
|
||||||
|
return &userLevelMenuAccessesRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IUserLevelMenuAccessesRepository
|
||||||
|
func (_i *userLevelMenuAccessesRepository) GetAll(req request.UserLevelMenuAccessesQueryRequest) (accesses []*entity.UserLevelMenuAccesses, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuAccesses{})
|
||||||
|
query = query.Where("is_active = ?", true)
|
||||||
|
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
query = query.Where("user_level_id = ?", req.UserLevelId)
|
||||||
|
}
|
||||||
|
if req.MenuId != nil {
|
||||||
|
query = query.Where("menu_id = ?", req.MenuId)
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
query = query.Where("client_id = ?", req.ClientId)
|
||||||
|
}
|
||||||
|
if req.CanAccess != nil {
|
||||||
|
query = query.Where("can_access = ?", req.CanAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload relations
|
||||||
|
query = query.Preload("UserLevel").Preload("Menu")
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination (manual calculation like articles)
|
||||||
|
page := req.Pagination.Page
|
||||||
|
limit := req.Pagination.Limit
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err = query.Offset(offset).Limit(limit).Find(&accesses).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pagination response
|
||||||
|
paging = paginator.Pagination{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
Count: count,
|
||||||
|
TotalPage: int((count + int64(limit) - 1) / int64(limit)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) GetByUserLevelId(userLevelId uint) (accesses []*entity.UserLevelMenuAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND is_active = ?", userLevelId, true)
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) GetByMenuId(menuId uint) (accesses []*entity.UserLevelMenuAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuAccesses{})
|
||||||
|
query = query.Where("menu_id = ? AND is_active = ?", menuId, true)
|
||||||
|
query = query.Preload("UserLevel")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) CheckAccess(userLevelId uint, menuId uint) (hasAccess bool, err error) {
|
||||||
|
var access entity.UserLevelMenuAccesses
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND menu_id = ? AND is_active = ? AND can_access = ?", userLevelId, menuId, true, true)
|
||||||
|
err = query.First(&access).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) FindOne(id uint) (access *entity.UserLevelMenuAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Preload("UserLevel").Preload("Menu")
|
||||||
|
if err := query.First(&access, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return access, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) Create(access *entity.UserLevelMenuAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Create(access).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) CreateBatch(accesses []*entity.UserLevelMenuAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Create(&accesses).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) Update(id uint, access *entity.UserLevelMenuAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.UserLevelMenuAccesses{}).
|
||||||
|
Where(&entity.UserLevelMenuAccesses{ID: id}).
|
||||||
|
Updates(access).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) Delete(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Delete(&entity.UserLevelMenuAccesses{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) DeleteByUserLevelId(userLevelId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("user_level_id = ?", userLevelId).Delete(&entity.UserLevelMenuAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesRepository) DeleteByMenuId(menuId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("menu_id = ?", menuId).Delete(&entity.UserLevelMenuAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserLevelMenuAccessesQueryRequest struct {
|
||||||
|
UserLevelId *uint `query:"user_level_id"`
|
||||||
|
MenuId *uint `query:"menu_id"`
|
||||||
|
ClientId *uuid.UUID `query:"client_id"`
|
||||||
|
CanAccess *bool `query:"can_access"`
|
||||||
|
Pagination *paginator.Pagination `query:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuAccessesCreateRequest struct {
|
||||||
|
UserLevelId uint `json:"userLevelId" validate:"required"`
|
||||||
|
MenuId uint `json:"menuId" validate:"required"`
|
||||||
|
CanAccess bool `json:"canAccess"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UserLevelMenuAccessesCreateRequest) ToEntity() *entity.UserLevelMenuAccesses {
|
||||||
|
return &entity.UserLevelMenuAccesses{
|
||||||
|
UserLevelId: req.UserLevelId,
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
CanAccess: req.CanAccess,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuAccessesUpdateRequest struct {
|
||||||
|
UserLevelId *uint `json:"userLevelId"`
|
||||||
|
MenuId *uint `json:"menuId"`
|
||||||
|
CanAccess *bool `json:"canAccess"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UserLevelMenuAccessesUpdateRequest) ToEntity() *entity.UserLevelMenuAccesses {
|
||||||
|
access := &entity.UserLevelMenuAccesses{}
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
access.UserLevelId = *req.UserLevelId
|
||||||
|
}
|
||||||
|
if req.MenuId != nil {
|
||||||
|
access.MenuId = *req.MenuId
|
||||||
|
}
|
||||||
|
if req.CanAccess != nil {
|
||||||
|
access.CanAccess = *req.CanAccess
|
||||||
|
}
|
||||||
|
access.ClientId = req.ClientId
|
||||||
|
access.IsActive = req.IsActive
|
||||||
|
return access
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuAccessesBatchCreateRequest struct {
|
||||||
|
UserLevelId uint `json:"userLevelId" validate:"required"`
|
||||||
|
MenuIds []uint `json:"menuIds" validate:"required,min=1"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type UserLevelMenuAccessesResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
UserLevelId uint `json:"userLevelId"`
|
||||||
|
MenuId uint `json:"menuId"`
|
||||||
|
CanAccess bool `json:"canAccess"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/mapper"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/repository"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/request"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/response"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserLevelMenuAccessesService
|
||||||
|
type userLevelMenuAccessesService struct {
|
||||||
|
Repo repository.UserLevelMenuAccessesRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLevelMenuAccessesService define interface of IUserLevelMenuAccessesService
|
||||||
|
type UserLevelMenuAccessesService interface {
|
||||||
|
All(req request.UserLevelMenuAccessesQueryRequest) (accesses []*response.UserLevelMenuAccessesResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByUserLevelId(userLevelId uint) (accesses []*response.UserLevelMenuAccessesResponse, err error)
|
||||||
|
GetByMenuId(menuId uint) (accesses []*response.UserLevelMenuAccessesResponse, err error)
|
||||||
|
CheckAccess(userLevelId uint, menuId uint) (hasAccess bool, err error)
|
||||||
|
Show(id uint) (access *response.UserLevelMenuAccessesResponse, err error)
|
||||||
|
Save(req request.UserLevelMenuAccessesCreateRequest) (err error)
|
||||||
|
SaveBatch(req request.UserLevelMenuAccessesBatchCreateRequest) (err error)
|
||||||
|
Update(id uint, req request.UserLevelMenuAccessesUpdateRequest) (err error)
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserLevelMenuAccessesService init UserLevelMenuAccessesService
|
||||||
|
func NewUserLevelMenuAccessesService(repo repository.UserLevelMenuAccessesRepository, log zerolog.Logger) UserLevelMenuAccessesService {
|
||||||
|
return &userLevelMenuAccessesService{
|
||||||
|
Repo: repo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All implement interface of UserLevelMenuAccessesService
|
||||||
|
func (_i *userLevelMenuAccessesService) All(req request.UserLevelMenuAccessesQueryRequest) (accesses []*response.UserLevelMenuAccessesResponse, paging paginator.Pagination, err error) {
|
||||||
|
results, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) GetByUserLevelId(userLevelId uint) (accesses []*response.UserLevelMenuAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByUserLevelId(userLevelId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) GetByMenuId(menuId uint) (accesses []*response.UserLevelMenuAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByMenuId(menuId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) CheckAccess(userLevelId uint, menuId uint) (hasAccess bool, err error) {
|
||||||
|
return _i.Repo.CheckAccess(userLevelId, menuId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) Show(id uint) (access *response.UserLevelMenuAccessesResponse, err error) {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.UserLevelMenuAccessesResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) Save(req request.UserLevelMenuAccessesCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating user level menu access")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
access := req.ToEntity()
|
||||||
|
access.IsActive = &isActive
|
||||||
|
|
||||||
|
return _i.Repo.Create(access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) SaveBatch(req request.UserLevelMenuAccessesBatchCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating user level menu accesses batch")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
var accesses []*entity.UserLevelMenuAccesses
|
||||||
|
|
||||||
|
for _, menuId := range req.MenuIds {
|
||||||
|
access := &entity.UserLevelMenuAccesses{
|
||||||
|
UserLevelId: req.UserLevelId,
|
||||||
|
MenuId: menuId,
|
||||||
|
CanAccess: true,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
accesses = append(accesses, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.CreateBatch(accesses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) Update(id uint, req request.UserLevelMenuAccessesUpdateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Updating user level menu access")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
access := req.ToEntity()
|
||||||
|
access.IsActive = &isActive
|
||||||
|
|
||||||
|
return _i.Repo.Update(id, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuAccessesService) Delete(id uint) error {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive := false
|
||||||
|
result.IsActive = &isActive
|
||||||
|
return _i.Repo.Update(id, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package user_level_menu_accesses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/controller"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/repository"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct of UserLevelMenuAccessesRouter
|
||||||
|
type UserLevelMenuAccessesRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *controller.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// register bulky of UserLevelMenuAccesses module
|
||||||
|
var NewUserLevelMenuAccessesModule = fx.Options(
|
||||||
|
// register repository of UserLevelMenuAccesses module
|
||||||
|
fx.Provide(repository.NewUserLevelMenuAccessesRepository),
|
||||||
|
|
||||||
|
// register service of UserLevelMenuAccesses module
|
||||||
|
fx.Provide(service.NewUserLevelMenuAccessesService),
|
||||||
|
|
||||||
|
// register controller of UserLevelMenuAccesses module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of UserLevelMenuAccesses module
|
||||||
|
fx.Provide(NewUserLevelMenuAccessesRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// init UserLevelMenuAccessesRouter
|
||||||
|
func NewUserLevelMenuAccessesRouter(fiber *fiber.App, controller *controller.Controller) *UserLevelMenuAccessesRouter {
|
||||||
|
return &UserLevelMenuAccessesRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register routes of UserLevelMenuAccesses module
|
||||||
|
func (_i *UserLevelMenuAccessesRouter) RegisterUserLevelMenuAccessesRoutes() {
|
||||||
|
// define controllers
|
||||||
|
userLevelMenuAccessesController := _i.Controller.UserLevelMenuAccesses
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/user-level-menu-accesses", func(router fiber.Router) {
|
||||||
|
router.Get("/", userLevelMenuAccessesController.All)
|
||||||
|
router.Get("/:id", userLevelMenuAccessesController.Show)
|
||||||
|
router.Get("/user-level/:user_level_id", userLevelMenuAccessesController.GetByUserLevelId)
|
||||||
|
router.Get("/menu/:menu_id", userLevelMenuAccessesController.GetByMenuId)
|
||||||
|
router.Get("/check/:user_level_id/:menu_id", userLevelMenuAccessesController.CheckAccess)
|
||||||
|
router.Post("/", userLevelMenuAccessesController.Save)
|
||||||
|
router.Post("/batch", userLevelMenuAccessesController.SaveBatch)
|
||||||
|
router.Put("/:id", userLevelMenuAccessesController.Update)
|
||||||
|
router.Delete("/:id", userLevelMenuAccessesController.Delete)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
UserLevelMenuActionAccesses UserLevelMenuActionAccessesController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(userLevelMenuActionAccessesService service.UserLevelMenuActionAccessesService) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
UserLevelMenuActionAccesses: NewUserLevelMenuActionAccessesController(userLevelMenuActionAccessesService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,379 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/request"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/service"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "netidhub-saas-be/utils/response"
|
||||||
|
utilVal "netidhub-saas-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userLevelMenuActionAccessesController struct {
|
||||||
|
userLevelMenuActionAccessesService service.UserLevelMenuActionAccessesService
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuActionAccessesController interface {
|
||||||
|
All(c *fiber.Ctx) error
|
||||||
|
GetByUserLevelId(c *fiber.Ctx) error
|
||||||
|
GetByUserLevelIdAndMenuId(c *fiber.Ctx) error
|
||||||
|
GetByMenuId(c *fiber.Ctx) error
|
||||||
|
CheckAccess(c *fiber.Ctx) error
|
||||||
|
Show(c *fiber.Ctx) error
|
||||||
|
Save(c *fiber.Ctx) error
|
||||||
|
SaveBatch(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserLevelMenuActionAccessesController(userLevelMenuActionAccessesService service.UserLevelMenuActionAccessesService) UserLevelMenuActionAccessesController {
|
||||||
|
return &userLevelMenuActionAccessesController{
|
||||||
|
userLevelMenuActionAccessesService: userLevelMenuActionAccessesService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All UserLevelMenuActionAccesses
|
||||||
|
// @Summary Get all UserLevelMenuActionAccesses
|
||||||
|
// @Description API for getting all UserLevelMenuActionAccesses
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id query int false "User Level ID"
|
||||||
|
// @Param menu_id query int false "Menu ID"
|
||||||
|
// @Param action_code query string false "Action Code"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses [get]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := request.UserLevelMenuActionAccessesQueryRequest{
|
||||||
|
Pagination: paginate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if userLevelId := c.Query("user_level_id"); userLevelId != "" {
|
||||||
|
id, _ := strconv.ParseUint(userLevelId, 10, 0)
|
||||||
|
userLevelIdUint := uint(id)
|
||||||
|
req.UserLevelId = &userLevelIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuId := c.Query("menu_id"); menuId != "" {
|
||||||
|
id, _ := strconv.ParseUint(menuId, 10, 0)
|
||||||
|
menuIdUint := uint(id)
|
||||||
|
req.MenuId = &menuIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if actionCode := c.Query("action_code"); actionCode != "" {
|
||||||
|
req.ActionCode = &actionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, paging, err := _i.userLevelMenuActionAccessesService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccesses list successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserLevelId get UserLevelMenuActionAccesses by User Level ID
|
||||||
|
// @Summary Get UserLevelMenuActionAccesses by User Level ID
|
||||||
|
// @Description API for getting UserLevelMenuActionAccesses by User Level ID
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id path int true "User Level ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/user-level/{user_level_id} [get]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) GetByUserLevelId(c *fiber.Ctx) error {
|
||||||
|
userLevelId, err := strconv.ParseUint(c.Params("user_level_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelMenuActionAccessesService.GetByUserLevelId(uint(userLevelId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccesses by user level successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserLevelIdAndMenuId get UserLevelMenuActionAccesses by User Level ID and Menu ID
|
||||||
|
// @Summary Get UserLevelMenuActionAccesses by User Level ID and Menu ID
|
||||||
|
// @Description API for getting UserLevelMenuActionAccesses by User Level ID and Menu ID
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id path int true "User Level ID"
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/user-level/{user_level_id}/menu/{menu_id} [get]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) GetByUserLevelIdAndMenuId(c *fiber.Ctx) error {
|
||||||
|
userLevelId, err := strconv.ParseUint(c.Params("user_level_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelMenuActionAccessesService.GetByUserLevelIdAndMenuId(uint(userLevelId), uint(menuId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccesses by user level and menu successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByMenuId get UserLevelMenuActionAccesses by Menu ID
|
||||||
|
// @Summary Get UserLevelMenuActionAccesses by Menu ID
|
||||||
|
// @Description API for getting UserLevelMenuActionAccesses by Menu ID
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/menu/{menu_id} [get]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) GetByMenuId(c *fiber.Ctx) error {
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelMenuActionAccessesService.GetByMenuId(uint(menuId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccesses by menu successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAccess check if user level has access to action in menu
|
||||||
|
// @Summary Check User Level Menu Action Access
|
||||||
|
// @Description API for checking if user level has access to action in menu
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id path int true "User Level ID"
|
||||||
|
// @Param menu_id path int true "Menu ID"
|
||||||
|
// @Param action_code path string true "Action Code"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/check/{user_level_id}/{menu_id}/{action_code} [get]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) CheckAccess(c *fiber.Ctx) error {
|
||||||
|
userLevelId, err := strconv.ParseUint(c.Params("user_level_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menuId, err := strconv.ParseUint(c.Params("menu_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
actionCode := c.Params("action_code")
|
||||||
|
if actionCode == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"code": 400,
|
||||||
|
"messages": []string{"Action code is required"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAccess, err := _i.userLevelMenuActionAccessesService.CheckAccess(uint(userLevelId), uint(menuId), actionCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Access check completed"},
|
||||||
|
Data: fiber.Map{"has_access": hasAccess},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show get one UserLevelMenuActionAccess
|
||||||
|
// @Summary Get one UserLevelMenuActionAccess
|
||||||
|
// @Description API for getting one UserLevelMenuActionAccess
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "UserLevelMenuActionAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/{id} [get]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessData, err := _i.userLevelMenuActionAccessesService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccess successfully retrieved"},
|
||||||
|
Data: accessData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save create UserLevelMenuActionAccess
|
||||||
|
// @Summary Create UserLevelMenuActionAccess
|
||||||
|
// @Description API for create UserLevelMenuActionAccess
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelMenuActionAccessesCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses [post]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.UserLevelMenuActionAccessesCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.userLevelMenuActionAccessesService.Save(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccess successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBatch create UserLevelMenuActionAccesses batch
|
||||||
|
// @Summary Create UserLevelMenuActionAccesses batch
|
||||||
|
// @Description API for create UserLevelMenuActionAccesses batch
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelMenuActionAccessesBatchCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/batch [post]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) SaveBatch(c *fiber.Ctx) error {
|
||||||
|
req := new(request.UserLevelMenuActionAccessesBatchCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.userLevelMenuActionAccessesService.SaveBatch(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccesses batch successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UserLevelMenuActionAccess
|
||||||
|
// @Summary Update UserLevelMenuActionAccess
|
||||||
|
// @Description API for update UserLevelMenuActionAccess
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "UserLevelMenuActionAccess ID"
|
||||||
|
// @Param payload body request.UserLevelMenuActionAccessesUpdateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/{id} [put]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.UserLevelMenuActionAccessesUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.userLevelMenuActionAccessesService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccess successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete UserLevelMenuActionAccess
|
||||||
|
// @Summary Delete UserLevelMenuActionAccess
|
||||||
|
// @Description API for delete UserLevelMenuActionAccess
|
||||||
|
// @Tags UserLevelMenuActionAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "UserLevelMenuActionAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-menu-action-accesses/{id} [delete]
|
||||||
|
func (_i *userLevelMenuActionAccessesController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.userLevelMenuActionAccessesService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelMenuActionAccess successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
res "netidhub-saas-be/app/module/user_level_menu_action_accesses/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserLevelMenuActionAccessesResponseMapper(accessReq *entity.UserLevelMenuActionAccesses) (accessRes *res.UserLevelMenuActionAccessesResponse) {
|
||||||
|
if accessReq != nil {
|
||||||
|
accessRes = &res.UserLevelMenuActionAccessesResponse{
|
||||||
|
ID: accessReq.ID,
|
||||||
|
UserLevelId: accessReq.UserLevelId,
|
||||||
|
MenuId: accessReq.MenuId,
|
||||||
|
ActionCode: accessReq.ActionCode,
|
||||||
|
CanAccess: accessReq.CanAccess,
|
||||||
|
IsActive: accessReq.IsActive,
|
||||||
|
CreatedAt: accessReq.CreatedAt,
|
||||||
|
UpdatedAt: accessReq.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessRes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/request"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userLevelMenuActionAccessesRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLevelMenuActionAccessesRepository define interface of IUserLevelMenuActionAccessesRepository
|
||||||
|
type UserLevelMenuActionAccessesRepository interface {
|
||||||
|
GetAll(req request.UserLevelMenuActionAccessesQueryRequest) (accesses []*entity.UserLevelMenuActionAccesses, paging paginator.Pagination, err error)
|
||||||
|
GetByUserLevelId(userLevelId uint) (accesses []*entity.UserLevelMenuActionAccesses, err error)
|
||||||
|
GetByUserLevelIdAndMenuId(userLevelId uint, menuId uint) (accesses []*entity.UserLevelMenuActionAccesses, err error)
|
||||||
|
GetByMenuId(menuId uint) (accesses []*entity.UserLevelMenuActionAccesses, err error)
|
||||||
|
CheckAccess(userLevelId uint, menuId uint, actionCode string) (hasAccess bool, err error)
|
||||||
|
FindOne(id uint) (access *entity.UserLevelMenuActionAccesses, err error)
|
||||||
|
Create(access *entity.UserLevelMenuActionAccesses) (err error)
|
||||||
|
CreateBatch(accesses []*entity.UserLevelMenuActionAccesses) (err error)
|
||||||
|
Update(id uint, access *entity.UserLevelMenuActionAccesses) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
DeleteByUserLevelId(userLevelId uint) (err error)
|
||||||
|
DeleteByMenuId(menuId uint) (err error)
|
||||||
|
DeleteByUserLevelIdAndMenuId(userLevelId uint, menuId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserLevelMenuActionAccessesRepository(db *database.Database) UserLevelMenuActionAccessesRepository {
|
||||||
|
return &userLevelMenuActionAccessesRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IUserLevelMenuActionAccessesRepository
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) GetAll(req request.UserLevelMenuActionAccessesQueryRequest) (accesses []*entity.UserLevelMenuActionAccesses, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuActionAccesses{})
|
||||||
|
query = query.Where("is_active = ?", true)
|
||||||
|
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
query = query.Where("user_level_id = ?", req.UserLevelId)
|
||||||
|
}
|
||||||
|
if req.MenuId != nil {
|
||||||
|
query = query.Where("menu_id = ?", req.MenuId)
|
||||||
|
}
|
||||||
|
if req.ActionCode != nil && *req.ActionCode != "" {
|
||||||
|
query = query.Where("action_code = ?", req.ActionCode)
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
query = query.Where("client_id = ?", req.ClientId)
|
||||||
|
}
|
||||||
|
if req.CanAccess != nil {
|
||||||
|
query = query.Where("can_access = ?", req.CanAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload relations
|
||||||
|
query = query.Preload("UserLevel").Preload("Menu")
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination (manual calculation like articles)
|
||||||
|
page := req.Pagination.Page
|
||||||
|
limit := req.Pagination.Limit
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err = query.Offset(offset).Limit(limit).Find(&accesses).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pagination response
|
||||||
|
paging = paginator.Pagination{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
Count: count,
|
||||||
|
TotalPage: int((count + int64(limit) - 1) / int64(limit)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) GetByUserLevelId(userLevelId uint) (accesses []*entity.UserLevelMenuActionAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuActionAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND is_active = ?", userLevelId, true)
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) GetByUserLevelIdAndMenuId(userLevelId uint, menuId uint) (accesses []*entity.UserLevelMenuActionAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuActionAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND menu_id = ? AND is_active = ?", userLevelId, menuId, true)
|
||||||
|
query = query.Preload("Menu")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) GetByMenuId(menuId uint) (accesses []*entity.UserLevelMenuActionAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuActionAccesses{})
|
||||||
|
query = query.Where("menu_id = ? AND is_active = ?", menuId, true)
|
||||||
|
query = query.Preload("UserLevel")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) CheckAccess(userLevelId uint, menuId uint, actionCode string) (hasAccess bool, err error) {
|
||||||
|
var access entity.UserLevelMenuActionAccesses
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelMenuActionAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND menu_id = ? AND action_code = ? AND is_active = ? AND can_access = ?", userLevelId, menuId, actionCode, true, true)
|
||||||
|
err = query.First(&access).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) FindOne(id uint) (access *entity.UserLevelMenuActionAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Preload("UserLevel").Preload("Menu")
|
||||||
|
if err := query.First(&access, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return access, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) Create(access *entity.UserLevelMenuActionAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Create(access).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) CreateBatch(accesses []*entity.UserLevelMenuActionAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Create(&accesses).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) Update(id uint, access *entity.UserLevelMenuActionAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.UserLevelMenuActionAccesses{}).
|
||||||
|
Where(&entity.UserLevelMenuActionAccesses{ID: id}).
|
||||||
|
Updates(access).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) Delete(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Delete(&entity.UserLevelMenuActionAccesses{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) DeleteByUserLevelId(userLevelId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("user_level_id = ?", userLevelId).Delete(&entity.UserLevelMenuActionAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) DeleteByMenuId(menuId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("menu_id = ?", menuId).Delete(&entity.UserLevelMenuActionAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesRepository) DeleteByUserLevelIdAndMenuId(userLevelId uint, menuId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("user_level_id = ? AND menu_id = ?", userLevelId, menuId).Delete(&entity.UserLevelMenuActionAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserLevelMenuActionAccessesQueryRequest struct {
|
||||||
|
UserLevelId *uint `query:"user_level_id"`
|
||||||
|
MenuId *uint `query:"menu_id"`
|
||||||
|
ActionCode *string `query:"action_code"`
|
||||||
|
ClientId *uuid.UUID `query:"client_id"`
|
||||||
|
CanAccess *bool `query:"can_access"`
|
||||||
|
Pagination *paginator.Pagination `query:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuActionAccessesCreateRequest struct {
|
||||||
|
UserLevelId uint `json:"userLevelId" validate:"required"`
|
||||||
|
MenuId uint `json:"menuId" validate:"required"`
|
||||||
|
ActionCode string `json:"actionCode" validate:"required"`
|
||||||
|
CanAccess bool `json:"canAccess"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UserLevelMenuActionAccessesCreateRequest) ToEntity() *entity.UserLevelMenuActionAccesses {
|
||||||
|
return &entity.UserLevelMenuActionAccesses{
|
||||||
|
UserLevelId: req.UserLevelId,
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ActionCode: req.ActionCode,
|
||||||
|
CanAccess: req.CanAccess,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuActionAccessesUpdateRequest struct {
|
||||||
|
UserLevelId *uint `json:"userLevelId"`
|
||||||
|
MenuId *uint `json:"menuId"`
|
||||||
|
ActionCode *string `json:"actionCode"`
|
||||||
|
CanAccess *bool `json:"canAccess"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UserLevelMenuActionAccessesUpdateRequest) ToEntity() *entity.UserLevelMenuActionAccesses {
|
||||||
|
access := &entity.UserLevelMenuActionAccesses{}
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
access.UserLevelId = *req.UserLevelId
|
||||||
|
}
|
||||||
|
if req.MenuId != nil {
|
||||||
|
access.MenuId = *req.MenuId
|
||||||
|
}
|
||||||
|
if req.ActionCode != nil {
|
||||||
|
access.ActionCode = *req.ActionCode
|
||||||
|
}
|
||||||
|
if req.CanAccess != nil {
|
||||||
|
access.CanAccess = *req.CanAccess
|
||||||
|
}
|
||||||
|
access.ClientId = req.ClientId
|
||||||
|
access.IsActive = req.IsActive
|
||||||
|
return access
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelMenuActionAccessesBatchCreateRequest struct {
|
||||||
|
UserLevelId uint `json:"userLevelId" validate:"required"`
|
||||||
|
MenuId uint `json:"menuId" validate:"required"`
|
||||||
|
ActionCodes []string `json:"actionCodes" validate:"required,min=1"`
|
||||||
|
ClientId *uuid.UUID `json:"clientId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type UserLevelMenuActionAccessesResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
UserLevelId uint `json:"userLevelId"`
|
||||||
|
MenuId uint `json:"menuId"`
|
||||||
|
ActionCode string `json:"actionCode"`
|
||||||
|
CanAccess bool `json:"canAccess"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/mapper"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/repository"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/request"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/response"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserLevelMenuActionAccessesService
|
||||||
|
type userLevelMenuActionAccessesService struct {
|
||||||
|
Repo repository.UserLevelMenuActionAccessesRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLevelMenuActionAccessesService define interface of IUserLevelMenuActionAccessesService
|
||||||
|
type UserLevelMenuActionAccessesService interface {
|
||||||
|
All(req request.UserLevelMenuActionAccessesQueryRequest) (accesses []*response.UserLevelMenuActionAccessesResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByUserLevelId(userLevelId uint) (accesses []*response.UserLevelMenuActionAccessesResponse, err error)
|
||||||
|
GetByUserLevelIdAndMenuId(userLevelId uint, menuId uint) (accesses []*response.UserLevelMenuActionAccessesResponse, err error)
|
||||||
|
GetByMenuId(menuId uint) (accesses []*response.UserLevelMenuActionAccessesResponse, err error)
|
||||||
|
CheckAccess(userLevelId uint, menuId uint, actionCode string) (hasAccess bool, err error)
|
||||||
|
Show(id uint) (access *response.UserLevelMenuActionAccessesResponse, err error)
|
||||||
|
Save(req request.UserLevelMenuActionAccessesCreateRequest) (err error)
|
||||||
|
SaveBatch(req request.UserLevelMenuActionAccessesBatchCreateRequest) (err error)
|
||||||
|
Update(id uint, req request.UserLevelMenuActionAccessesUpdateRequest) (err error)
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserLevelMenuActionAccessesService init UserLevelMenuActionAccessesService
|
||||||
|
func NewUserLevelMenuActionAccessesService(repo repository.UserLevelMenuActionAccessesRepository, log zerolog.Logger) UserLevelMenuActionAccessesService {
|
||||||
|
return &userLevelMenuActionAccessesService{
|
||||||
|
Repo: repo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All implement interface of UserLevelMenuActionAccessesService
|
||||||
|
func (_i *userLevelMenuActionAccessesService) All(req request.UserLevelMenuActionAccessesQueryRequest) (accesses []*response.UserLevelMenuActionAccessesResponse, paging paginator.Pagination, err error) {
|
||||||
|
results, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuActionAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) GetByUserLevelId(userLevelId uint) (accesses []*response.UserLevelMenuActionAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByUserLevelId(userLevelId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuActionAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) GetByUserLevelIdAndMenuId(userLevelId uint, menuId uint) (accesses []*response.UserLevelMenuActionAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByUserLevelIdAndMenuId(userLevelId, menuId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuActionAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) GetByMenuId(menuId uint) (accesses []*response.UserLevelMenuActionAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByMenuId(menuId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelMenuActionAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) CheckAccess(userLevelId uint, menuId uint, actionCode string) (hasAccess bool, err error) {
|
||||||
|
return _i.Repo.CheckAccess(userLevelId, menuId, actionCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) Show(id uint) (access *response.UserLevelMenuActionAccessesResponse, err error) {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.UserLevelMenuActionAccessesResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) Save(req request.UserLevelMenuActionAccessesCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating user level menu action access")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
access := req.ToEntity()
|
||||||
|
access.IsActive = &isActive
|
||||||
|
|
||||||
|
return _i.Repo.Create(access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) SaveBatch(req request.UserLevelMenuActionAccessesBatchCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating user level menu action accesses batch")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
var accesses []*entity.UserLevelMenuActionAccesses
|
||||||
|
|
||||||
|
for _, actionCode := range req.ActionCodes {
|
||||||
|
access := &entity.UserLevelMenuActionAccesses{
|
||||||
|
UserLevelId: req.UserLevelId,
|
||||||
|
MenuId: req.MenuId,
|
||||||
|
ActionCode: actionCode,
|
||||||
|
CanAccess: true,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
accesses = append(accesses, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.CreateBatch(accesses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) Update(id uint, req request.UserLevelMenuActionAccessesUpdateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Updating user level menu action access")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
access := req.ToEntity()
|
||||||
|
access.IsActive = &isActive
|
||||||
|
|
||||||
|
return _i.Repo.Update(id, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelMenuActionAccessesService) Delete(id uint) error {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive := false
|
||||||
|
result.IsActive = &isActive
|
||||||
|
return _i.Repo.Update(id, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package user_level_menu_action_accesses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/controller"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/repository"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct of UserLevelMenuActionAccessesRouter
|
||||||
|
type UserLevelMenuActionAccessesRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *controller.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// register bulky of UserLevelMenuActionAccesses module
|
||||||
|
var NewUserLevelMenuActionAccessesModule = fx.Options(
|
||||||
|
// register repository of UserLevelMenuActionAccesses module
|
||||||
|
fx.Provide(repository.NewUserLevelMenuActionAccessesRepository),
|
||||||
|
|
||||||
|
// register service of UserLevelMenuActionAccesses module
|
||||||
|
fx.Provide(service.NewUserLevelMenuActionAccessesService),
|
||||||
|
|
||||||
|
// register controller of UserLevelMenuActionAccesses module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of UserLevelMenuActionAccesses module
|
||||||
|
fx.Provide(NewUserLevelMenuActionAccessesRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// init UserLevelMenuActionAccessesRouter
|
||||||
|
func NewUserLevelMenuActionAccessesRouter(fiber *fiber.App, controller *controller.Controller) *UserLevelMenuActionAccessesRouter {
|
||||||
|
return &UserLevelMenuActionAccessesRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register routes of UserLevelMenuActionAccesses module
|
||||||
|
func (_i *UserLevelMenuActionAccessesRouter) RegisterUserLevelMenuActionAccessesRoutes() {
|
||||||
|
// define controllers
|
||||||
|
userLevelMenuActionAccessesController := _i.Controller.UserLevelMenuActionAccesses
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/user-level-menu-action-accesses", func(router fiber.Router) {
|
||||||
|
router.Get("/", userLevelMenuActionAccessesController.All)
|
||||||
|
router.Get("/:id", userLevelMenuActionAccessesController.Show)
|
||||||
|
router.Get("/user-level/:user_level_id", userLevelMenuActionAccessesController.GetByUserLevelId)
|
||||||
|
router.Get("/user-level/:user_level_id/menu/:menu_id", userLevelMenuActionAccessesController.GetByUserLevelIdAndMenuId)
|
||||||
|
router.Get("/menu/:menu_id", userLevelMenuActionAccessesController.GetByMenuId)
|
||||||
|
router.Get("/check/:user_level_id/:menu_id/:action_code", userLevelMenuActionAccessesController.CheckAccess)
|
||||||
|
router.Post("/", userLevelMenuActionAccessesController.Save)
|
||||||
|
router.Post("/batch", userLevelMenuActionAccessesController.SaveBatch)
|
||||||
|
router.Put("/:id", userLevelMenuActionAccessesController.Update)
|
||||||
|
router.Delete("/:id", userLevelMenuActionAccessesController.Delete)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "netidhub-saas-be/app/module/user_level_module_accesses/service"
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
UserLevelModuleAccesses UserLevelModuleAccessesController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(userLevelModuleAccessesService service.UserLevelModuleAccessesService) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
UserLevelModuleAccesses: NewUserLevelModuleAccessesController(userLevelModuleAccessesService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,330 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/request"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/response"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/service"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
utilRes "netidhub-saas-be/utils/response"
|
||||||
|
utilVal "netidhub-saas-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userLevelModuleAccessesController struct {
|
||||||
|
userLevelModuleAccessesService service.UserLevelModuleAccessesService
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelModuleAccessesController interface {
|
||||||
|
All(c *fiber.Ctx) error
|
||||||
|
GetByUserLevelId(c *fiber.Ctx) error
|
||||||
|
GetByModuleId(c *fiber.Ctx) error
|
||||||
|
CheckAccess(c *fiber.Ctx) error
|
||||||
|
Show(c *fiber.Ctx) error
|
||||||
|
Save(c *fiber.Ctx) error
|
||||||
|
SaveBatch(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserLevelModuleAccessesController(userLevelModuleAccessesService service.UserLevelModuleAccessesService) UserLevelModuleAccessesController {
|
||||||
|
return &userLevelModuleAccessesController{
|
||||||
|
userLevelModuleAccessesService: userLevelModuleAccessesService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All UserLevelModuleAccesses
|
||||||
|
// @Summary Get all UserLevelModuleAccesses
|
||||||
|
// @Description API for getting all UserLevelModuleAccesses
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id query int false "User Level ID"
|
||||||
|
// @Param module_id query int false "Module ID"
|
||||||
|
// @Param can_access query bool false "Can Access"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses [get]
|
||||||
|
func (_i *userLevelModuleAccessesController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := request.UserLevelModuleAccessesQueryRequest{
|
||||||
|
Pagination: paginate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if userLevelId := c.Query("user_level_id"); userLevelId != "" {
|
||||||
|
id, _ := strconv.ParseUint(userLevelId, 10, 0)
|
||||||
|
userLevelIdUint := uint(id)
|
||||||
|
req.UserLevelId = &userLevelIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleId := c.Query("module_id"); moduleId != "" {
|
||||||
|
id, _ := strconv.ParseUint(moduleId, 10, 0)
|
||||||
|
moduleIdUint := uint(id)
|
||||||
|
req.ModuleId = &moduleIdUint
|
||||||
|
}
|
||||||
|
|
||||||
|
if canAccess := c.Query("can_access"); canAccess != "" {
|
||||||
|
canAccessBool := canAccess == "true"
|
||||||
|
req.CanAccess = &canAccessBool
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, paging, err := _i.userLevelModuleAccessesService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccesses list successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserLevelId get UserLevelModuleAccesses by User Level ID
|
||||||
|
// @Summary Get UserLevelModuleAccesses by User Level ID
|
||||||
|
// @Description API for getting UserLevelModuleAccesses by User Level ID
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param user_level_id path int true "User Level ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses/user-level/{user_level_id} [get]
|
||||||
|
func (_i *userLevelModuleAccessesController) GetByUserLevelId(c *fiber.Ctx) error {
|
||||||
|
userLevelId, err := strconv.ParseUint(c.Params("user_level_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelModuleAccessesService.GetByUserLevelId(uint(userLevelId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccesses by user level successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByModuleId get UserLevelModuleAccesses by Module ID
|
||||||
|
// @Summary Get UserLevelModuleAccesses by Module ID
|
||||||
|
// @Description API for getting UserLevelModuleAccesses by Module ID
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param module_id path int true "Module ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses/module/{module_id} [get]
|
||||||
|
func (_i *userLevelModuleAccessesController) GetByModuleId(c *fiber.Ctx) error {
|
||||||
|
moduleId, err := strconv.ParseUint(c.Params("module_id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessesData, err := _i.userLevelModuleAccessesService.GetByModuleId(uint(moduleId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccesses by module successfully retrieved"},
|
||||||
|
Data: accessesData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAccess check if user level has access to module
|
||||||
|
// @Summary Check user level module access
|
||||||
|
// @Description API for checking if user level has access to module
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param payload body request.CheckAccessRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses/check-access [post]
|
||||||
|
func (_i *userLevelModuleAccessesController) CheckAccess(c *fiber.Ctx) error {
|
||||||
|
req := new(request.CheckAccessRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAccess, err := _i.userLevelModuleAccessesService.CheckAccess(req.UserLevelId, req.ModuleId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Access check completed"},
|
||||||
|
Data: response.CheckAccessResponse{HasAccess: hasAccess},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show get one UserLevelModuleAccess
|
||||||
|
// @Summary Get one UserLevelModuleAccess
|
||||||
|
// @Description API for getting one UserLevelModuleAccess
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "UserLevelModuleAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses/{id} [get]
|
||||||
|
func (_i *userLevelModuleAccessesController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessData, err := _i.userLevelModuleAccessesService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccess successfully retrieved"},
|
||||||
|
Data: accessData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save create UserLevelModuleAccess
|
||||||
|
// @Summary Create UserLevelModuleAccess
|
||||||
|
// @Description API for create UserLevelModuleAccess
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelModuleAccessesCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses [post]
|
||||||
|
func (_i *userLevelModuleAccessesController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.UserLevelModuleAccessesCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.userLevelModuleAccessesService.Save(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccess successfully created"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBatch create multiple UserLevelModuleAccesses at once
|
||||||
|
// @Summary Create multiple UserLevelModuleAccesses
|
||||||
|
// @Description API for creating multiple UserLevelModuleAccesses at once for a user level
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelModuleAccessesBatchCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses/batch [post]
|
||||||
|
func (_i *userLevelModuleAccessesController) SaveBatch(c *fiber.Ctx) error {
|
||||||
|
req := new(request.UserLevelModuleAccessesBatchCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _i.userLevelModuleAccessesService.SaveBatch(*req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccesses successfully created in batch"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UserLevelModuleAccess
|
||||||
|
// @Summary Update UserLevelModuleAccess
|
||||||
|
// @Description API for update UserLevelModuleAccess
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.UserLevelModuleAccessesUpdateRequest true "Required payload"
|
||||||
|
// @Param id path int true "UserLevelModuleAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 401 {object} response.Response
|
||||||
|
// @Failure 404 {object} response.Response
|
||||||
|
// @Failure 422 {object} response.Response
|
||||||
|
// @Failure 500 {object} response.Response
|
||||||
|
// @Router /user-level-module-accesses/{id} [put]
|
||||||
|
func (_i *userLevelModuleAccessesController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.UserLevelModuleAccessesUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.userLevelModuleAccessesService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccess successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete UserLevelModuleAccess
|
||||||
|
// @Summary Delete UserLevelModuleAccess
|
||||||
|
// @Description API for delete UserLevelModuleAccess (soft delete)
|
||||||
|
// @Tags UserLevelModuleAccesses
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "UserLevelModuleAccess ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /user-level-module-accesses/{id} [delete]
|
||||||
|
func (_i *userLevelModuleAccessesController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.userLevelModuleAccessesService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"UserLevelModuleAccess successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserLevelModuleAccessesResponseMapper(e *entity.UserLevelModuleAccesses) *response.UserLevelModuleAccessesResponse {
|
||||||
|
resp := &response.UserLevelModuleAccessesResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
UserLevelId: e.UserLevelId,
|
||||||
|
ModuleId: e.ModuleId,
|
||||||
|
CanAccess: e.CanAccess,
|
||||||
|
ClientId: e.ClientId,
|
||||||
|
IsActive: e.IsActive,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.UserLevel != nil {
|
||||||
|
resp.UserLevel = &response.UserLevelResponse{
|
||||||
|
ID: e.UserLevel.ID,
|
||||||
|
Name: e.UserLevel.Name,
|
||||||
|
AliasName: e.UserLevel.AliasName,
|
||||||
|
LevelNumber: e.UserLevel.LevelNumber,
|
||||||
|
Group: e.UserLevel.Group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Module != nil {
|
||||||
|
resp.Module = &response.ModuleDetailResponse{
|
||||||
|
ID: e.Module.ID,
|
||||||
|
Name: e.Module.Name,
|
||||||
|
Description: e.Module.Description,
|
||||||
|
PathUrl: e.Module.PathUrl,
|
||||||
|
ActionType: e.Module.ActionType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/request"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userLevelModuleAccessesRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLevelModuleAccessesRepository define interface of IUserLevelModuleAccessesRepository
|
||||||
|
type UserLevelModuleAccessesRepository interface {
|
||||||
|
GetAll(req request.UserLevelModuleAccessesQueryRequest) (accesses []*entity.UserLevelModuleAccesses, paging paginator.Pagination, err error)
|
||||||
|
GetByUserLevelId(userLevelId uint) (accesses []*entity.UserLevelModuleAccesses, err error)
|
||||||
|
GetByModuleId(moduleId uint) (accesses []*entity.UserLevelModuleAccesses, err error)
|
||||||
|
CheckAccess(userLevelId uint, moduleId uint) (hasAccess bool, err error)
|
||||||
|
FindOne(id uint) (access *entity.UserLevelModuleAccesses, err error)
|
||||||
|
Create(access *entity.UserLevelModuleAccesses) (err error)
|
||||||
|
CreateBatch(accesses []*entity.UserLevelModuleAccesses) (err error)
|
||||||
|
Update(id uint, access *entity.UserLevelModuleAccesses) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
DeleteByUserLevelId(userLevelId uint) (err error)
|
||||||
|
DeleteByModuleId(moduleId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserLevelModuleAccessesRepository(db *database.Database) UserLevelModuleAccessesRepository {
|
||||||
|
return &userLevelModuleAccessesRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IUserLevelModuleAccessesRepository
|
||||||
|
func (_i *userLevelModuleAccessesRepository) GetAll(req request.UserLevelModuleAccessesQueryRequest) (accesses []*entity.UserLevelModuleAccesses, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelModuleAccesses{})
|
||||||
|
query = query.Where("is_active = ?", true)
|
||||||
|
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
query = query.Where("user_level_id = ?", req.UserLevelId)
|
||||||
|
}
|
||||||
|
if req.ModuleId != nil {
|
||||||
|
query = query.Where("module_id = ?", req.ModuleId)
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
query = query.Where("client_id = ?", req.ClientId)
|
||||||
|
}
|
||||||
|
if req.CanAccess != nil {
|
||||||
|
query = query.Where("can_access = ?", req.CanAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload relations
|
||||||
|
query = query.Preload("UserLevel").Preload("Module")
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination (manual calculation like articles)
|
||||||
|
page := req.Pagination.Page
|
||||||
|
limit := req.Pagination.Limit
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err = query.Offset(offset).Limit(limit).Find(&accesses).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pagination response
|
||||||
|
paging = paginator.Pagination{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
Count: count,
|
||||||
|
TotalPage: int((count + int64(limit) - 1) / int64(limit)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) GetByUserLevelId(userLevelId uint) (accesses []*entity.UserLevelModuleAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelModuleAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND is_active = ?", userLevelId, true)
|
||||||
|
query = query.Preload("Module")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) GetByModuleId(moduleId uint) (accesses []*entity.UserLevelModuleAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelModuleAccesses{})
|
||||||
|
query = query.Where("module_id = ? AND is_active = ?", moduleId, true)
|
||||||
|
query = query.Preload("UserLevel")
|
||||||
|
err = query.Find(&accesses).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) CheckAccess(userLevelId uint, moduleId uint) (hasAccess bool, err error) {
|
||||||
|
var access entity.UserLevelModuleAccesses
|
||||||
|
query := _i.DB.DB.Model(&entity.UserLevelModuleAccesses{})
|
||||||
|
query = query.Where("user_level_id = ? AND module_id = ? AND is_active = ?", userLevelId, moduleId, true)
|
||||||
|
|
||||||
|
err = query.First(&access).Error
|
||||||
|
if err != nil {
|
||||||
|
// Jika tidak ada record, berarti tidak ada akses
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return access.CanAccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) FindOne(id uint) (access *entity.UserLevelModuleAccesses, err error) {
|
||||||
|
query := _i.DB.DB.Preload("UserLevel").Preload("Module")
|
||||||
|
if err := query.First(&access, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return access, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) Create(access *entity.UserLevelModuleAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Create(access).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) CreateBatch(accesses []*entity.UserLevelModuleAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Create(&accesses).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) Update(id uint, access *entity.UserLevelModuleAccesses) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.UserLevelModuleAccesses{}).
|
||||||
|
Where(&entity.UserLevelModuleAccesses{ID: id}).
|
||||||
|
Updates(access).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) Delete(id uint) error {
|
||||||
|
return _i.DB.DB.Delete(&entity.UserLevelModuleAccesses{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) DeleteByUserLevelId(userLevelId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("user_level_id = ?", userLevelId).Delete(&entity.UserLevelModuleAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesRepository) DeleteByModuleId(moduleId uint) (err error) {
|
||||||
|
return _i.DB.DB.Where("module_id = ?", moduleId).Delete(&entity.UserLevelModuleAccesses{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserLevelModuleAccessesQueryRequest struct {
|
||||||
|
UserLevelId *uint `query:"user_level_id"`
|
||||||
|
ModuleId *uint `query:"module_id"`
|
||||||
|
CanAccess *bool `query:"can_access"`
|
||||||
|
ClientId *uuid.UUID `query:"client_id"`
|
||||||
|
Pagination *paginator.Pagination `query:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelModuleAccessesCreateRequest struct {
|
||||||
|
UserLevelId uint `json:"user_level_id" validate:"required"`
|
||||||
|
ModuleId uint `json:"module_id" validate:"required"`
|
||||||
|
CanAccess bool `json:"can_access"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelModuleAccessesBatchCreateRequest struct {
|
||||||
|
UserLevelId uint `json:"user_level_id" validate:"required"`
|
||||||
|
ModuleIds []uint `json:"module_ids" validate:"required,min=1"`
|
||||||
|
CanAccess bool `json:"can_access"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelModuleAccessesUpdateRequest struct {
|
||||||
|
UserLevelId *uint `json:"user_level_id"`
|
||||||
|
ModuleId *uint `json:"module_id"`
|
||||||
|
CanAccess *bool `json:"can_access"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckAccessRequest struct {
|
||||||
|
UserLevelId uint `json:"user_level_id" validate:"required"`
|
||||||
|
ModuleId uint `json:"module_id" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserLevelModuleAccessesResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
UserLevelId uint `json:"user_level_id"`
|
||||||
|
ModuleId uint `json:"module_id"`
|
||||||
|
CanAccess bool `json:"can_access"`
|
||||||
|
ClientId *uuid.UUID `json:"client_id"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
UserLevel *UserLevelResponse `json:"user_level,omitempty"`
|
||||||
|
Module *ModuleDetailResponse `json:"module,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLevelResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AliasName string `json:"alias_name"`
|
||||||
|
LevelNumber int `json:"level_number"`
|
||||||
|
Group *string `json:"group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleDetailResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
PathUrl string `json:"path_url"`
|
||||||
|
ActionType *string `json:"action_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckAccessResponse struct {
|
||||||
|
HasAccess bool `json:"has_access"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/mapper"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/repository"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/request"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/response"
|
||||||
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserLevelModuleAccessesService
|
||||||
|
type userLevelModuleAccessesService struct {
|
||||||
|
Repo repository.UserLevelModuleAccessesRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLevelModuleAccessesService define interface of IUserLevelModuleAccessesService
|
||||||
|
type UserLevelModuleAccessesService interface {
|
||||||
|
All(req request.UserLevelModuleAccessesQueryRequest) (accesses []*response.UserLevelModuleAccessesResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByUserLevelId(userLevelId uint) (accesses []*response.UserLevelModuleAccessesResponse, err error)
|
||||||
|
GetByModuleId(moduleId uint) (accesses []*response.UserLevelModuleAccessesResponse, err error)
|
||||||
|
CheckAccess(userLevelId uint, moduleId uint) (hasAccess bool, err error)
|
||||||
|
Show(id uint) (access *response.UserLevelModuleAccessesResponse, err error)
|
||||||
|
Save(req request.UserLevelModuleAccessesCreateRequest) (err error)
|
||||||
|
SaveBatch(req request.UserLevelModuleAccessesBatchCreateRequest) (err error)
|
||||||
|
Update(id uint, req request.UserLevelModuleAccessesUpdateRequest) (err error)
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserLevelModuleAccessesService init UserLevelModuleAccessesService
|
||||||
|
func NewUserLevelModuleAccessesService(repo repository.UserLevelModuleAccessesRepository, log zerolog.Logger) UserLevelModuleAccessesService {
|
||||||
|
return &userLevelModuleAccessesService{
|
||||||
|
Repo: repo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All implement interface of UserLevelModuleAccessesService
|
||||||
|
func (_i *userLevelModuleAccessesService) All(req request.UserLevelModuleAccessesQueryRequest) (accesses []*response.UserLevelModuleAccessesResponse, paging paginator.Pagination, err error) {
|
||||||
|
results, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelModuleAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) GetByUserLevelId(userLevelId uint) (accesses []*response.UserLevelModuleAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByUserLevelId(userLevelId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelModuleAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) GetByModuleId(moduleId uint) (accesses []*response.UserLevelModuleAccessesResponse, err error) {
|
||||||
|
results, err := _i.Repo.GetByModuleId(moduleId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
accesses = append(accesses, mapper.UserLevelModuleAccessesResponseMapper(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) CheckAccess(userLevelId uint, moduleId uint) (hasAccess bool, err error) {
|
||||||
|
return _i.Repo.CheckAccess(userLevelId, moduleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) Show(id uint) (access *response.UserLevelModuleAccessesResponse, err error) {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.UserLevelModuleAccessesResponseMapper(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) Save(req request.UserLevelModuleAccessesCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating user level module access")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
access := &entity.UserLevelModuleAccesses{
|
||||||
|
UserLevelId: req.UserLevelId,
|
||||||
|
ModuleId: req.ModuleId,
|
||||||
|
CanAccess: req.CanAccess,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.Create(access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) SaveBatch(req request.UserLevelModuleAccessesBatchCreateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Creating user level module accesses batch")
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
var accesses []*entity.UserLevelModuleAccesses
|
||||||
|
|
||||||
|
for _, moduleId := range req.ModuleIds {
|
||||||
|
access := &entity.UserLevelModuleAccesses{
|
||||||
|
UserLevelId: req.UserLevelId,
|
||||||
|
ModuleId: moduleId,
|
||||||
|
CanAccess: req.CanAccess,
|
||||||
|
ClientId: req.ClientId,
|
||||||
|
IsActive: &isActive,
|
||||||
|
}
|
||||||
|
accesses = append(accesses, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.CreateBatch(accesses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) Update(id uint, req request.UserLevelModuleAccessesUpdateRequest) (err error) {
|
||||||
|
_i.Log.Info().Interface("data", req).Msg("Updating user level module access")
|
||||||
|
|
||||||
|
access := &entity.UserLevelModuleAccesses{}
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
access.UserLevelId = *req.UserLevelId
|
||||||
|
}
|
||||||
|
if req.ModuleId != nil {
|
||||||
|
access.ModuleId = *req.ModuleId
|
||||||
|
}
|
||||||
|
if req.CanAccess != nil {
|
||||||
|
access.CanAccess = *req.CanAccess
|
||||||
|
}
|
||||||
|
if req.ClientId != nil {
|
||||||
|
access.ClientId = req.ClientId
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
access.IsActive = req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i.Repo.Update(id, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *userLevelModuleAccessesService) Delete(id uint) error {
|
||||||
|
result, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive := false
|
||||||
|
result.IsActive = &isActive
|
||||||
|
return _i.Repo.Update(id, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package user_level_module_accesses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/controller"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/repository"
|
||||||
|
"netidhub-saas-be/app/module/user_level_module_accesses/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct of UserLevelModuleAccessesRouter
|
||||||
|
type UserLevelModuleAccessesRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *controller.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// register bulky of UserLevelModuleAccesses module
|
||||||
|
var NewUserLevelModuleAccessesModule = fx.Options(
|
||||||
|
// register repository of UserLevelModuleAccesses module
|
||||||
|
fx.Provide(repository.NewUserLevelModuleAccessesRepository),
|
||||||
|
|
||||||
|
// register service of UserLevelModuleAccesses module
|
||||||
|
fx.Provide(service.NewUserLevelModuleAccessesService),
|
||||||
|
|
||||||
|
// register controller of UserLevelModuleAccesses module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of UserLevelModuleAccesses module
|
||||||
|
fx.Provide(NewUserLevelModuleAccessesRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// init UserLevelModuleAccessesRouter
|
||||||
|
func NewUserLevelModuleAccessesRouter(fiber *fiber.App, controller *controller.Controller) *UserLevelModuleAccessesRouter {
|
||||||
|
return &UserLevelModuleAccessesRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register routes of UserLevelModuleAccesses module
|
||||||
|
func (_i *UserLevelModuleAccessesRouter) RegisterUserLevelModuleAccessesRoutes() {
|
||||||
|
// define controllers
|
||||||
|
userLevelModuleAccessesController := _i.Controller.UserLevelModuleAccesses
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/user-level-module-accesses", func(router fiber.Router) {
|
||||||
|
router.Get("/", userLevelModuleAccessesController.All)
|
||||||
|
router.Get("/:id", userLevelModuleAccessesController.Show)
|
||||||
|
router.Get("/user-level/:user_level_id", userLevelModuleAccessesController.GetByUserLevelId)
|
||||||
|
router.Get("/module/:module_id", userLevelModuleAccessesController.GetByModuleId)
|
||||||
|
router.Post("/", userLevelModuleAccessesController.Save)
|
||||||
|
router.Post("/batch", userLevelModuleAccessesController.SaveBatch)
|
||||||
|
router.Post("/check-access", userLevelModuleAccessesController.CheckAccess)
|
||||||
|
router.Put("/:id", userLevelModuleAccessesController.Update)
|
||||||
|
router.Delete("/:id", userLevelModuleAccessesController.Delete)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -73,8 +73,8 @@ type UsersUpdateRequest struct {
|
||||||
Username string `json:"username" validate:"required,lowercase"`
|
Username string `json:"username" validate:"required,lowercase"`
|
||||||
Email string `json:"email" validate:"required,email"`
|
Email string `json:"email" validate:"required,email"`
|
||||||
Fullname string `json:"fullname" validate:"required"`
|
Fullname string `json:"fullname" validate:"required"`
|
||||||
UserLevelId uint `json:"userLevelId" validate:"required"`
|
UserLevelId *uint `json:"userLevelId"`
|
||||||
UserRoleId uint `json:"userRoleId" validate:"required"`
|
UserRoleId *uint `json:"userRoleId"`
|
||||||
PhoneNumber *string `json:"phoneNumber"`
|
PhoneNumber *string `json:"phoneNumber"`
|
||||||
Address *string `json:"address"`
|
Address *string `json:"address"`
|
||||||
WorkType *string `json:"workType"`
|
WorkType *string `json:"workType"`
|
||||||
|
|
@ -88,8 +88,27 @@ type UsersUpdateRequest struct {
|
||||||
StatusId *int `json:"statusId"`
|
StatusId *int `json:"statusId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type UsersUpdateRequest struct {
|
||||||
|
// Username string `json:"username" validate:"required,lowercase"`
|
||||||
|
// Email string `json:"email" validate:"required,email"`
|
||||||
|
// Fullname string `json:"fullname" validate:"required"`
|
||||||
|
// UserLevelId uint `json:"userLevelId" validate:"required"`
|
||||||
|
// UserRoleId uint `json:"userRoleId" validate:"required"`
|
||||||
|
// PhoneNumber *string `json:"phoneNumber"`
|
||||||
|
// Address *string `json:"address"`
|
||||||
|
// WorkType *string `json:"workType"`
|
||||||
|
// GenderType *string `json:"genderType"`
|
||||||
|
// IdentityType *string `json:"identityType"`
|
||||||
|
// IdentityGroup *string `json:"identityGroup"`
|
||||||
|
// IdentityGroupNumber *string `json:"identityGroupNumber"`
|
||||||
|
// IdentityNumber *string `json:"identityNumber"`
|
||||||
|
// DateOfBirth *string `json:"dateOfBirth"`
|
||||||
|
// LastEducation *string `json:"lastEducation"`
|
||||||
|
// StatusId *int `json:"statusId"`
|
||||||
|
// }
|
||||||
|
|
||||||
func (req UsersUpdateRequest) ToEntity() *users.Users {
|
func (req UsersUpdateRequest) ToEntity() *users.Users {
|
||||||
return &users.Users{
|
entity := &users.Users{
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Fullname: req.Fullname,
|
Fullname: req.Fullname,
|
||||||
|
|
@ -103,13 +122,44 @@ func (req UsersUpdateRequest) ToEntity() *users.Users {
|
||||||
IdentityNumber: req.IdentityNumber,
|
IdentityNumber: req.IdentityNumber,
|
||||||
DateOfBirth: req.DateOfBirth,
|
DateOfBirth: req.DateOfBirth,
|
||||||
LastEducation: req.LastEducation,
|
LastEducation: req.LastEducation,
|
||||||
UserRoleId: req.UserRoleId,
|
|
||||||
StatusId: req.StatusId,
|
StatusId: req.StatusId,
|
||||||
UserLevelId: req.UserLevelId,
|
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⬇️ HANYA SET JIKA DIKIRIM
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
entity.UserLevelId = *req.UserLevelId
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.UserRoleId != nil {
|
||||||
|
entity.UserRoleId = *req.UserRoleId
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (req UsersUpdateRequest) ToEntity() *users.Users {
|
||||||
|
// return &users.Users{
|
||||||
|
// Username: req.Username,
|
||||||
|
// Email: req.Email,
|
||||||
|
// Fullname: req.Fullname,
|
||||||
|
// Address: req.Address,
|
||||||
|
// PhoneNumber: req.PhoneNumber,
|
||||||
|
// WorkType: req.WorkType,
|
||||||
|
// GenderType: req.GenderType,
|
||||||
|
// IdentityType: req.IdentityType,
|
||||||
|
// IdentityGroup: req.IdentityGroup,
|
||||||
|
// IdentityGroupNumber: req.IdentityGroupNumber,
|
||||||
|
// IdentityNumber: req.IdentityNumber,
|
||||||
|
// DateOfBirth: req.DateOfBirth,
|
||||||
|
// LastEducation: req.LastEducation,
|
||||||
|
// UserRoleId: req.UserRoleId,
|
||||||
|
// StatusId: req.StatusId,
|
||||||
|
// UserLevelId: req.UserLevelId,
|
||||||
|
// UpdatedAt: time.Now(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
type UserLogin struct {
|
type UserLogin struct {
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
|
|
|
||||||
|
|
@ -375,25 +375,80 @@ func (_i *usersService) Update(authToken string, id uint, req request.UsersUpdat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_i.Log.Info().Interface("data", req).Msg("")
|
// 1️⃣ Ambil data user lama
|
||||||
newReq := req.ToEntity()
|
existingUser, err := _i.Repo.FindOne(clientId, id)
|
||||||
|
|
||||||
findUser, err := _i.Repo.FindOne(clientId, id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email)
|
// 2️⃣ Update Keycloak (nama & email)
|
||||||
|
err = _i.Keycloak.UpdateUser(existingUser.KeycloakId, req.Fullname, req.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set ClientId on entity
|
// 3️⃣ Update field yang BOLEH diubah
|
||||||
newReq.ClientId = clientId
|
existingUser.Username = req.Username
|
||||||
|
existingUser.Email = req.Email
|
||||||
|
existingUser.Fullname = req.Fullname
|
||||||
|
existingUser.Address = req.Address
|
||||||
|
existingUser.PhoneNumber = req.PhoneNumber
|
||||||
|
existingUser.WorkType = req.WorkType
|
||||||
|
existingUser.GenderType = req.GenderType
|
||||||
|
existingUser.IdentityType = req.IdentityType
|
||||||
|
existingUser.IdentityGroup = req.IdentityGroup
|
||||||
|
existingUser.IdentityGroupNumber = req.IdentityGroupNumber
|
||||||
|
existingUser.IdentityNumber = req.IdentityNumber
|
||||||
|
existingUser.DateOfBirth = req.DateOfBirth
|
||||||
|
existingUser.LastEducation = req.LastEducation
|
||||||
|
existingUser.StatusId = req.StatusId
|
||||||
|
existingUser.UpdatedAt = time.Now()
|
||||||
|
|
||||||
return _i.Repo.Update(clientId, id, newReq)
|
// 4️⃣ Role & Level HANYA jika dikirim
|
||||||
|
if req.UserLevelId != nil {
|
||||||
|
existingUser.UserLevelId = *req.UserLevelId
|
||||||
|
}
|
||||||
|
if req.UserRoleId != nil {
|
||||||
|
existingUser.UserRoleId = *req.UserRoleId
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5️⃣ Pastikan clientId tidak hilang
|
||||||
|
existingUser.ClientId = clientId
|
||||||
|
|
||||||
|
// 6️⃣ Simpan
|
||||||
|
return _i.Repo.Update(clientId, id, existingUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (_i *usersService) Update(authToken string, id uint, req request.UsersUpdateRequest) (err error) {
|
||||||
|
// // Extract clientId from authToken
|
||||||
|
// var clientId *uuid.UUID
|
||||||
|
// if authToken != "" {
|
||||||
|
// user := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||||||
|
// if user != nil && user.ClientId != nil {
|
||||||
|
// clientId = user.ClientId
|
||||||
|
// _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _i.Log.Info().Interface("data", req).Msg("")
|
||||||
|
// newReq := req.ToEntity()
|
||||||
|
|
||||||
|
// findUser, err := _i.Repo.FindOne(clientId, id)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Set ClientId on entity
|
||||||
|
// newReq.ClientId = clientId
|
||||||
|
|
||||||
|
// return _i.Repo.Update(clientId, id, newReq)
|
||||||
|
// }
|
||||||
|
|
||||||
func (_i *usersService) Delete(authToken string, id uint) error {
|
func (_i *usersService) Delete(authToken string, id uint) error {
|
||||||
// Extract clientId from authToken
|
// Extract clientId from authToken
|
||||||
var clientId *uuid.UUID
|
var clientId *uuid.UUID
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ import (
|
||||||
"netidhub-saas-be/app/module/magazines"
|
"netidhub-saas-be/app/module/magazines"
|
||||||
"netidhub-saas-be/app/module/master_menus"
|
"netidhub-saas-be/app/module/master_menus"
|
||||||
"netidhub-saas-be/app/module/master_modules"
|
"netidhub-saas-be/app/module/master_modules"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses"
|
||||||
"netidhub-saas-be/app/module/provinces"
|
"netidhub-saas-be/app/module/provinces"
|
||||||
"netidhub-saas-be/app/module/schedules"
|
"netidhub-saas-be/app/module/schedules"
|
||||||
"netidhub-saas-be/app/module/subscription"
|
"netidhub-saas-be/app/module/subscription"
|
||||||
|
|
@ -65,9 +68,12 @@ type Router struct {
|
||||||
FeedbacksRouter *feedbacks.FeedbacksRouter
|
FeedbacksRouter *feedbacks.FeedbacksRouter
|
||||||
MagazineFilesRouter *magazine_files.MagazineFilesRouter
|
MagazineFilesRouter *magazine_files.MagazineFilesRouter
|
||||||
MagazinesRouter *magazines.MagazinesRouter
|
MagazinesRouter *magazines.MagazinesRouter
|
||||||
MasterMenusRouter *master_menus.MasterMenusRouter
|
MasterMenusRouter *master_menus.MasterMenusRouter
|
||||||
MasterModulesRouter *master_modules.MasterModulesRouter
|
MasterModulesRouter *master_modules.MasterModulesRouter
|
||||||
ProvincesRouter *provinces.ProvincesRouter
|
MenuActionsRouter *menu_actions.MenuActionsRouter
|
||||||
|
UserLevelMenuAccessesRouter *user_level_menu_accesses.UserLevelMenuAccessesRouter
|
||||||
|
UserLevelMenuActionAccessesRouter *user_level_menu_action_accesses.UserLevelMenuActionAccessesRouter
|
||||||
|
ProvincesRouter *provinces.ProvincesRouter
|
||||||
SchedulesRouter *schedules.SchedulesRouter
|
SchedulesRouter *schedules.SchedulesRouter
|
||||||
SubscriptionRouter *subscription.SubscriptionRouter
|
SubscriptionRouter *subscription.SubscriptionRouter
|
||||||
UserLevelsRouter *user_levels.UserLevelsRouter
|
UserLevelsRouter *user_levels.UserLevelsRouter
|
||||||
|
|
@ -104,6 +110,9 @@ func NewRouter(
|
||||||
magazinesRouter *magazines.MagazinesRouter,
|
magazinesRouter *magazines.MagazinesRouter,
|
||||||
masterMenuRouter *master_menus.MasterMenusRouter,
|
masterMenuRouter *master_menus.MasterMenusRouter,
|
||||||
masterModuleRouter *master_modules.MasterModulesRouter,
|
masterModuleRouter *master_modules.MasterModulesRouter,
|
||||||
|
menuActionsRouter *menu_actions.MenuActionsRouter,
|
||||||
|
userLevelMenuAccessesRouter *user_level_menu_accesses.UserLevelMenuAccessesRouter,
|
||||||
|
userLevelMenuActionAccessesRouter *user_level_menu_action_accesses.UserLevelMenuActionAccessesRouter,
|
||||||
provincesRouter *provinces.ProvincesRouter,
|
provincesRouter *provinces.ProvincesRouter,
|
||||||
schedulesRouter *schedules.SchedulesRouter,
|
schedulesRouter *schedules.SchedulesRouter,
|
||||||
subscriptionRouter *subscription.SubscriptionRouter,
|
subscriptionRouter *subscription.SubscriptionRouter,
|
||||||
|
|
@ -137,9 +146,12 @@ func NewRouter(
|
||||||
FeedbacksRouter: feedbacksRouter,
|
FeedbacksRouter: feedbacksRouter,
|
||||||
MagazineFilesRouter: magazineFilesRouter,
|
MagazineFilesRouter: magazineFilesRouter,
|
||||||
MagazinesRouter: magazinesRouter,
|
MagazinesRouter: magazinesRouter,
|
||||||
MasterMenusRouter: masterMenuRouter,
|
MasterMenusRouter: masterMenuRouter,
|
||||||
MasterModulesRouter: masterModuleRouter,
|
MasterModulesRouter: masterModuleRouter,
|
||||||
ProvincesRouter: provincesRouter,
|
MenuActionsRouter: menuActionsRouter,
|
||||||
|
UserLevelMenuAccessesRouter: userLevelMenuAccessesRouter,
|
||||||
|
UserLevelMenuActionAccessesRouter: userLevelMenuActionAccessesRouter,
|
||||||
|
ProvincesRouter: provincesRouter,
|
||||||
SchedulesRouter: schedulesRouter,
|
SchedulesRouter: schedulesRouter,
|
||||||
SubscriptionRouter: subscriptionRouter,
|
SubscriptionRouter: subscriptionRouter,
|
||||||
UserLevelsRouter: userLevelsRouter,
|
UserLevelsRouter: userLevelsRouter,
|
||||||
|
|
@ -184,6 +196,9 @@ func (r *Router) Register() {
|
||||||
r.MagazineFilesRouter.RegisterMagazineFilesRoutes()
|
r.MagazineFilesRouter.RegisterMagazineFilesRoutes()
|
||||||
r.MasterMenusRouter.RegisterMasterMenusRoutes()
|
r.MasterMenusRouter.RegisterMasterMenusRoutes()
|
||||||
r.MasterModulesRouter.RegisterMasterModulesRoutes()
|
r.MasterModulesRouter.RegisterMasterModulesRoutes()
|
||||||
|
r.MenuActionsRouter.RegisterMenuActionsRoutes()
|
||||||
|
r.UserLevelMenuAccessesRouter.RegisterUserLevelMenuAccessesRoutes()
|
||||||
|
r.UserLevelMenuActionAccessesRouter.RegisterUserLevelMenuActionAccessesRoutes()
|
||||||
r.ProvincesRouter.RegisterProvincesRoutes()
|
r.ProvincesRouter.RegisterProvincesRoutes()
|
||||||
r.SchedulesRouter.RegisterSchedulesRoutes()
|
r.SchedulesRouter.RegisterSchedulesRoutes()
|
||||||
r.SubscriptionRouter.RegisterSubscriptionRoutes()
|
r.SubscriptionRouter.RegisterSubscriptionRoutes()
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,16 @@ domain = "https://kontenhumas.com/api"
|
||||||
external-port = ":8809"
|
external-port = ":8809"
|
||||||
idle-timeout = 5 # As seconds
|
idle-timeout = 5 # As seconds
|
||||||
print-routes = false
|
print-routes = false
|
||||||
prefork = false
|
prefork = true
|
||||||
production = false
|
production = false
|
||||||
body-limit = 1048576000 # "100 * 1024 * 1024"
|
body-limit = 1048576000 # "100 * 1024 * 1024"
|
||||||
primary-client-key = "78356d32-52fa-4dfc-b836-6cebf4e3eead"
|
primary-client-key = "78356d32-52fa-4dfc-b836-6cebf4e3eead"
|
||||||
|
|
||||||
[db.postgres]
|
[db.postgres]
|
||||||
dsn = "postgresql://netidhub_user:NetidhubDB%402025@38.47.185.79:5432/netidhub_db" # <driver>://<username>:<password>@<host>:<port>/<database>
|
dsn = "postgresql://netidhub_user:NetidhubDB%402025@38.47.185.79:5432/netidhub_db" # <driver>://<username>:<password>@<host>:<port>/<database>
|
||||||
log-mode = "ERROR"
|
log-mode = "NONE"
|
||||||
migrate = true
|
migrate = true
|
||||||
seed = false
|
seed = true
|
||||||
|
|
||||||
[logger]
|
[logger]
|
||||||
log-dir = "debug.log"
|
log-dir = "debug.log"
|
||||||
|
|
@ -25,7 +25,7 @@ level = 0 # panic -> 5, fatal -> 4, error -> 3, warn -> 2, info -> 1, debug -> 0
|
||||||
prettier = true
|
prettier = true
|
||||||
|
|
||||||
[objectstorage.miniostorage]
|
[objectstorage.miniostorage]
|
||||||
endpoint = "https://is3.cloudhost.id"
|
endpoint = "is3.cloudhost.id"
|
||||||
access-key-id = "YRP1RM617986USRU6NN8"
|
access-key-id = "YRP1RM617986USRU6NN8"
|
||||||
secret-access-key = "vfbwQDYb1m7nfzo4LVEz90BIyOWfBMZ6bfGQbqDO"
|
secret-access-key = "vfbwQDYb1m7nfzo4LVEz90BIyOWfBMZ6bfGQbqDO"
|
||||||
use-ssl = true
|
use-ssl = true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,342 @@
|
||||||
|
# 📦 Implementation Summary - Menu Module Access Control System
|
||||||
|
|
||||||
|
## ✅ Yang Sudah Dibuat
|
||||||
|
|
||||||
|
### 1. **Database Entities** (3 files)
|
||||||
|
|
||||||
|
#### ✓ `app/database/entity/menu_modules.entity.go`
|
||||||
|
- Relasi many-to-many antara menu dan modul
|
||||||
|
- Satu menu bisa punya banyak modul (view, create, edit, delete, etc.)
|
||||||
|
|
||||||
|
#### ✓ `app/database/entity/user_level_module_accesses.entity.go`
|
||||||
|
- Mengatur akses user-level ke modul-modul tertentu
|
||||||
|
- Kontrol akses granular per modul
|
||||||
|
|
||||||
|
#### ✓ `app/database/entity/master_modules.entity.go` (Enhanced)
|
||||||
|
- Ditambahkan field `action_type` untuk membedakan jenis aksi (view, create, edit, delete, approve, export)
|
||||||
|
|
||||||
|
### 2. **Repositories** (2 files)
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/repository/menu_modules.repository.go`
|
||||||
|
Fungsi yang tersedia:
|
||||||
|
- `GetAll()` - Get all menu-modules dengan pagination
|
||||||
|
- `GetByMenuId()` - Get modules berdasarkan menu
|
||||||
|
- `GetByModuleId()` - Get menus berdasarkan module
|
||||||
|
- `FindOne()` - Get satu menu-module
|
||||||
|
- `Create()` - Create satu menu-module
|
||||||
|
- `CreateBatch()` - Create banyak menu-modules sekaligus
|
||||||
|
- `Update()` - Update menu-module
|
||||||
|
- `Delete()` - Delete menu-module
|
||||||
|
- `DeleteByMenuId()` - Delete semua modules dari menu
|
||||||
|
- `DeleteByModuleId()` - Delete semua menus dari module
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/repository/user_level_module_accesses.repository.go`
|
||||||
|
Fungsi yang tersedia:
|
||||||
|
- `GetAll()` - Get all accesses dengan pagination
|
||||||
|
- `GetByUserLevelId()` - Get accesses berdasarkan user level
|
||||||
|
- `GetByModuleId()` - Get accesses berdasarkan module
|
||||||
|
- `CheckAccess()` - Check apakah user level punya akses ke module
|
||||||
|
- `FindOne()` - Get satu access
|
||||||
|
- `Create()` - Create satu access
|
||||||
|
- `CreateBatch()` - Create banyak accesses sekaligus
|
||||||
|
- `Update()` - Update access
|
||||||
|
- `Delete()` - Delete access
|
||||||
|
|
||||||
|
### 3. **Services** (2 files)
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/service/menu_modules.service.go`
|
||||||
|
Business logic untuk mengelola menu-modules
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/service/user_level_module_accesses.service.go`
|
||||||
|
Business logic untuk mengelola user level accesses
|
||||||
|
|
||||||
|
### 4. **Controllers** (2 files)
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/controller/menu_modules.controller.go`
|
||||||
|
API Endpoints:
|
||||||
|
- `GET /menu-modules` - List all
|
||||||
|
- `GET /menu-modules/:id` - Get one
|
||||||
|
- `GET /menu-modules/menu/:menu_id` - Get by menu
|
||||||
|
- `GET /menu-modules/module/:module_id` - Get by module
|
||||||
|
- `POST /menu-modules` - Create one
|
||||||
|
- `POST /menu-modules/batch` - Create many
|
||||||
|
- `PUT /menu-modules/:id` - Update
|
||||||
|
- `DELETE /menu-modules/:id` - Delete
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/controller/user_level_module_accesses.controller.go`
|
||||||
|
API Endpoints:
|
||||||
|
- `GET /user-level-module-accesses` - List all
|
||||||
|
- `GET /user-level-module-accesses/:id` - Get one
|
||||||
|
- `GET /user-level-module-accesses/user-level/:user_level_id` - Get by user level
|
||||||
|
- `GET /user-level-module-accesses/module/:module_id` - Get by module
|
||||||
|
- `POST /user-level-module-accesses` - Create one
|
||||||
|
- `POST /user-level-module-accesses/batch` - Create many
|
||||||
|
- `POST /user-level-module-accesses/check-access` - Check access
|
||||||
|
- `PUT /user-level-module-accesses/:id` - Update
|
||||||
|
- `DELETE /user-level-module-accesses/:id` - Delete
|
||||||
|
|
||||||
|
### 5. **Middleware** (1 file)
|
||||||
|
|
||||||
|
#### ✓ `app/middleware/module_access.middleware.go`
|
||||||
|
3 Middleware functions:
|
||||||
|
1. **`CheckModuleAccess(moduleId|pathUrl)`** - Check akses berdasarkan module ID atau path URL
|
||||||
|
2. **`CheckModuleAccessByPath()`** - Auto-detect module dari current path
|
||||||
|
3. **`CheckMenuAccess(menuId)`** - Check akses ke menu (minimal punya 1 akses modul di menu tersebut)
|
||||||
|
|
||||||
|
### 6. **Request/Response DTOs** (4 files)
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/request/menu_modules.request.go`
|
||||||
|
- MenuModulesQueryRequest
|
||||||
|
- MenuModulesCreateRequest
|
||||||
|
- MenuModulesBatchCreateRequest
|
||||||
|
- MenuModulesUpdateRequest
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/response/menu_modules.response.go`
|
||||||
|
- MenuModulesResponse
|
||||||
|
- MenuBasicResponse
|
||||||
|
- ModuleBasicResponse
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/request/user_level_module_accesses.request.go`
|
||||||
|
- UserLevelModuleAccessesQueryRequest
|
||||||
|
- UserLevelModuleAccessesCreateRequest
|
||||||
|
- UserLevelModuleAccessesBatchCreateRequest
|
||||||
|
- UserLevelModuleAccessesUpdateRequest
|
||||||
|
- CheckAccessRequest
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/response/user_level_module_accesses.response.go`
|
||||||
|
- UserLevelModuleAccessesResponse
|
||||||
|
- UserLevelResponse
|
||||||
|
- ModuleDetailResponse
|
||||||
|
- CheckAccessResponse
|
||||||
|
|
||||||
|
### 7. **Mappers** (2 files)
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/mapper/menu_modules.mapper.go`
|
||||||
|
- MenuModulesResponseMapper()
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/mapper/user_level_module_accesses.mapper.go`
|
||||||
|
- UserLevelModuleAccessesResponseMapper()
|
||||||
|
|
||||||
|
### 8. **Module Registration** (2 files)
|
||||||
|
|
||||||
|
#### ✓ `app/module/menu_modules/menu_modules.module.go`
|
||||||
|
- Dependency injection setup
|
||||||
|
- Route registration
|
||||||
|
|
||||||
|
#### ✓ `app/module/user_level_module_accesses/user_level_module_accesses.module.go`
|
||||||
|
- Dependency injection setup
|
||||||
|
- Route registration
|
||||||
|
|
||||||
|
### 9. **Documentation** (3 files)
|
||||||
|
|
||||||
|
#### ✓ `docs/MENU_MODULE_ACCESS_SYSTEM.md`
|
||||||
|
Dokumentasi lengkap tentang:
|
||||||
|
- Overview sistem
|
||||||
|
- Database schema
|
||||||
|
- API endpoints
|
||||||
|
- Middleware usage
|
||||||
|
- Implementation examples
|
||||||
|
- Best practices
|
||||||
|
|
||||||
|
#### ✓ `docs/MENU_MODULE_QUICK_START.md`
|
||||||
|
Quick start guide dengan langkah-langkah:
|
||||||
|
- Setup migration
|
||||||
|
- Setup data master
|
||||||
|
- Implementasi di code
|
||||||
|
- Testing
|
||||||
|
- Troubleshooting
|
||||||
|
|
||||||
|
#### ✓ `docs/migrations/004_add_menu_module_access_system.sql`
|
||||||
|
SQL migration script untuk:
|
||||||
|
- Alter `master_modules` (add action_type)
|
||||||
|
- Create `menu_modules` table
|
||||||
|
- Create `user_level_module_accesses` table
|
||||||
|
- Indexes dan constraints
|
||||||
|
- Data migration dari existing structure
|
||||||
|
- Verification queries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Langkah Selanjutnya
|
||||||
|
|
||||||
|
### 1. Jalankan Migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
psql -U your_username -d your_database -f docs/migrations/004_add_menu_module_access_system.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Register Modules ke Main App
|
||||||
|
|
||||||
|
Edit `app/router/api.go` atau main file untuk register module:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
menuModulesModule "netidhub-saas-be/app/module/menu_modules"
|
||||||
|
userLevelModuleAccessesModule "netidhub-saas-be/app/module/user_level_module_accesses"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Di dalam fx.Options
|
||||||
|
fx.Provide(
|
||||||
|
// ... existing modules ...
|
||||||
|
menuModulesModule.NewMenuModulesModule,
|
||||||
|
userLevelModuleAccessesModule.NewUserLevelModuleAccessesModule,
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
Dan register routes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func RegisterRoutes(
|
||||||
|
menuModulesRouter *menuModulesModule.MenuModulesRouter,
|
||||||
|
userLevelModuleAccessesRouter *userLevelModuleAccessesModule.UserLevelModuleAccessesRouter,
|
||||||
|
) {
|
||||||
|
menuModulesRouter.RegisterMenuModulesRoutes()
|
||||||
|
userLevelModuleAccessesRouter.RegisterUserLevelModuleAccessesRoutes()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Setup Data Master
|
||||||
|
|
||||||
|
Jalankan SQL untuk membuat modules, menus, dan accesses. Lihat contoh di `docs/MENU_MODULE_QUICK_START.md`.
|
||||||
|
|
||||||
|
### 4. Terapkan Middleware ke Routes
|
||||||
|
|
||||||
|
Contoh:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SetupArticleRoutes(app *fiber.App, db *database.Database) {
|
||||||
|
moduleAccessMw := middleware.NewModuleAccessMiddleware(db)
|
||||||
|
|
||||||
|
articles := app.Group("/api/articles")
|
||||||
|
|
||||||
|
articles.Get("/",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(1)), // View module
|
||||||
|
articleController.GetAll,
|
||||||
|
)
|
||||||
|
|
||||||
|
articles.Post("/",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(2)), // Create module
|
||||||
|
articleController.Create,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Testing
|
||||||
|
|
||||||
|
Gunakan contoh testing di `docs/MENU_MODULE_QUICK_START.md` untuk validasi implementasi.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Struktur yang Dibuat
|
||||||
|
|
||||||
|
```
|
||||||
|
netidhub-saas-be/
|
||||||
|
├── app/
|
||||||
|
│ ├── database/
|
||||||
|
│ │ └── entity/
|
||||||
|
│ │ ├── menu_modules.entity.go ✓
|
||||||
|
│ │ ├── user_level_module_accesses.entity.go ✓
|
||||||
|
│ │ └── master_modules.entity.go (Enhanced) ✓
|
||||||
|
│ ├── middleware/
|
||||||
|
│ │ └── module_access.middleware.go ✓
|
||||||
|
│ └── module/
|
||||||
|
│ ├── menu_modules/
|
||||||
|
│ │ ├── controller/
|
||||||
|
│ │ │ ├── controller.go ✓
|
||||||
|
│ │ │ └── menu_modules.controller.go ✓
|
||||||
|
│ │ ├── mapper/
|
||||||
|
│ │ │ └── menu_modules.mapper.go ✓
|
||||||
|
│ │ ├── repository/
|
||||||
|
│ │ │ └── menu_modules.repository.go ✓
|
||||||
|
│ │ ├── request/
|
||||||
|
│ │ │ └── menu_modules.request.go ✓
|
||||||
|
│ │ ├── response/
|
||||||
|
│ │ │ └── menu_modules.response.go ✓
|
||||||
|
│ │ ├── service/
|
||||||
|
│ │ │ └── menu_modules.service.go ✓
|
||||||
|
│ │ └── menu_modules.module.go ✓
|
||||||
|
│ └── user_level_module_accesses/
|
||||||
|
│ ├── controller/
|
||||||
|
│ │ ├── controller.go ✓
|
||||||
|
│ │ └── user_level_module_accesses.controller.go ✓
|
||||||
|
│ ├── mapper/
|
||||||
|
│ │ └── user_level_module_accesses.mapper.go ✓
|
||||||
|
│ ├── repository/
|
||||||
|
│ │ └── user_level_module_accesses.repository.go ✓
|
||||||
|
│ ├── request/
|
||||||
|
│ │ └── user_level_module_accesses.request.go ✓
|
||||||
|
│ ├── response/
|
||||||
|
│ │ └── user_level_module_accesses.response.go ✓
|
||||||
|
│ ├── service/
|
||||||
|
│ │ └── user_level_module_accesses.service.go ✓
|
||||||
|
│ └── user_level_module_accesses.module.go ✓
|
||||||
|
└── docs/
|
||||||
|
├── MENU_MODULE_ACCESS_SYSTEM.md ✓
|
||||||
|
├── MENU_MODULE_QUICK_START.md ✓
|
||||||
|
└── migrations/
|
||||||
|
└── 004_add_menu_module_access_system.sql ✓
|
||||||
|
|
||||||
|
Total: 27 files created/updated
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Fitur yang Dapat Dilakukan
|
||||||
|
|
||||||
|
### ✅ Management Menu-Module
|
||||||
|
- [x] Menghubungkan banyak modul ke satu menu
|
||||||
|
- [x] Batch create untuk efisiensi
|
||||||
|
- [x] Get modules by menu
|
||||||
|
- [x] Get menus by module
|
||||||
|
- [x] Manage position/urutan modul
|
||||||
|
|
||||||
|
### ✅ Management User Level Access
|
||||||
|
- [x] Set akses user level ke modul tertentu
|
||||||
|
- [x] Batch create untuk setup awal
|
||||||
|
- [x] Check access programmatically
|
||||||
|
- [x] Get accesses by user level
|
||||||
|
- [x] Get accesses by module
|
||||||
|
|
||||||
|
### ✅ Middleware Protection
|
||||||
|
- [x] Check akses berdasarkan module ID
|
||||||
|
- [x] Check akses berdasarkan path URL
|
||||||
|
- [x] Auto-detect module dari current path
|
||||||
|
- [x] Check akses menu
|
||||||
|
- [x] Set context untuk handler
|
||||||
|
|
||||||
|
### ✅ API Endpoints
|
||||||
|
- [x] CRUD menu modules
|
||||||
|
- [x] CRUD user level accesses
|
||||||
|
- [x] Check access endpoint
|
||||||
|
- [x] Batch operations
|
||||||
|
- [x] Filter by various parameters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Dokumentasi
|
||||||
|
|
||||||
|
Silakan baca dokumentasi lengkap:
|
||||||
|
1. **[MENU_MODULE_ACCESS_SYSTEM.md](./MENU_MODULE_ACCESS_SYSTEM.md)** - Dokumentasi lengkap
|
||||||
|
2. **[MENU_MODULE_QUICK_START.md](./MENU_MODULE_QUICK_START.md)** - Quick start guide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Support & Troubleshooting
|
||||||
|
|
||||||
|
Jika ada masalah, cek:
|
||||||
|
1. Migration sudah dijalankan dengan benar
|
||||||
|
2. Tables `menu_modules` dan `user_level_module_accesses` sudah ada
|
||||||
|
3. Data master sudah disetup
|
||||||
|
4. Middleware sudah diterapkan dengan benar
|
||||||
|
5. User sudah terautentikasi sebelum check access
|
||||||
|
|
||||||
|
Untuk troubleshooting detail, lihat bagian Troubleshooting di `MENU_MODULE_QUICK_START.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status: ✅ READY TO USE**
|
||||||
|
|
||||||
|
Sistem sudah lengkap dan siap digunakan. Tinggal jalankan migration dan setup data master!
|
||||||
|
|
||||||
|
|
@ -0,0 +1,574 @@
|
||||||
|
# Menu Module Access Control System
|
||||||
|
|
||||||
|
## 📋 Overview
|
||||||
|
|
||||||
|
Sistem ini memaksimalkan penggunaan `master_menus` dan `master_modules` untuk mengatur akses berbasis user-level yang lebih granular. Sistem ini memungkinkan:
|
||||||
|
|
||||||
|
1. **Menu memiliki banyak modul** - Satu menu dapat terdiri dari berbagai modul (view, create, edit, delete, etc.)
|
||||||
|
2. **Akses berbasis user-level** - User-level dapat dikonfigurasi untuk mengakses modul-modul tertentu
|
||||||
|
3. **Pengecekan akses otomatis** - Middleware untuk validasi akses sebelum user mengakses endpoint
|
||||||
|
|
||||||
|
## 🗂️ Database Schema
|
||||||
|
|
||||||
|
### 1. `master_modules` (Enhanced)
|
||||||
|
Tabel modul yang sudah ditingkatkan dengan field `action_type`.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE master_modules
|
||||||
|
ADD COLUMN action_type VARCHAR NULL COMMENT 'Tipe aksi: view, create, edit, delete, approve, export, etc';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `menu_modules` (New)
|
||||||
|
Tabel relasi many-to-many antara menu dan modul.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE menu_modules (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
module_id INT NOT NULL,
|
||||||
|
position INT NULL,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id),
|
||||||
|
FOREIGN KEY (module_id) REFERENCES master_modules(id),
|
||||||
|
UNIQUE(menu_id, module_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_menu_modules_menu_id ON menu_modules(menu_id);
|
||||||
|
CREATE INDEX idx_menu_modules_module_id ON menu_modules(module_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. `user_level_module_accesses` (New)
|
||||||
|
Tabel untuk mengatur akses user-level ke modul-modul tertentu.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE user_level_module_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
module_id INT NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id),
|
||||||
|
FOREIGN KEY (module_id) REFERENCES master_modules(id),
|
||||||
|
UNIQUE(user_level_id, module_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_user_level_id ON user_level_module_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_module_id ON user_level_module_accesses(module_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Entity Relationships
|
||||||
|
|
||||||
|
```
|
||||||
|
master_menus (1) ─────< (N) menu_modules (N) >───── (1) master_modules
|
||||||
|
│
|
||||||
|
│ (1)
|
||||||
|
│
|
||||||
|
∨
|
||||||
|
user_levels (1) ─────< (N) user_level_module_accesses (N) >┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 API Endpoints
|
||||||
|
|
||||||
|
### Menu Modules API
|
||||||
|
|
||||||
|
#### 1. Get All Menu Modules
|
||||||
|
```http
|
||||||
|
GET /api/menu-modules?menu_id=1&page=1&limit=10
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Get Modules by Menu ID
|
||||||
|
```http
|
||||||
|
GET /api/menu-modules/menu/{menu_id}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["MenuModules by menu successfully retrieved"],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"menu_id": 1,
|
||||||
|
"module_id": 5,
|
||||||
|
"position": 1,
|
||||||
|
"module": {
|
||||||
|
"id": 5,
|
||||||
|
"name": "View Articles",
|
||||||
|
"description": "View article list",
|
||||||
|
"path_url": "/api/articles",
|
||||||
|
"action_type": "view"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Create Menu Module
|
||||||
|
```http
|
||||||
|
POST /api/menu-modules
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"menu_id": 1,
|
||||||
|
"module_id": 5,
|
||||||
|
"position": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Create Menu Modules in Batch
|
||||||
|
```http
|
||||||
|
POST /api/menu-modules/batch
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"menu_id": 1,
|
||||||
|
"module_ids": [5, 6, 7, 8]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Update Menu Module
|
||||||
|
```http
|
||||||
|
PUT /api/menu-modules/{id}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"position": 2,
|
||||||
|
"is_active": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. Delete Menu Module
|
||||||
|
```http
|
||||||
|
DELETE /api/menu-modules/{id}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Level Module Accesses API
|
||||||
|
|
||||||
|
#### 1. Get All Accesses
|
||||||
|
```http
|
||||||
|
GET /api/user-level-module-accesses?user_level_id=1&page=1&limit=10
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Get Accesses by User Level ID
|
||||||
|
```http
|
||||||
|
GET /api/user-level-module-accesses/user-level/{user_level_id}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["UserLevelModuleAccesses by user level successfully retrieved"],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"user_level_id": 1,
|
||||||
|
"module_id": 5,
|
||||||
|
"can_access": true,
|
||||||
|
"module": {
|
||||||
|
"id": 5,
|
||||||
|
"name": "View Articles",
|
||||||
|
"description": "View article list",
|
||||||
|
"path_url": "/api/articles",
|
||||||
|
"action_type": "view"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Check Access
|
||||||
|
```http
|
||||||
|
POST /api/user-level-module-accesses/check-access
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"user_level_id": 1,
|
||||||
|
"module_id": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Access check completed"],
|
||||||
|
"data": {
|
||||||
|
"has_access": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Create Access
|
||||||
|
```http
|
||||||
|
POST /api/user-level-module-accesses
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"user_level_id": 1,
|
||||||
|
"module_id": 5,
|
||||||
|
"can_access": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Create Accesses in Batch
|
||||||
|
```http
|
||||||
|
POST /api/user-level-module-accesses/batch
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"user_level_id": 1,
|
||||||
|
"module_ids": [5, 6, 7, 8],
|
||||||
|
"can_access": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛡️ Middleware Usage
|
||||||
|
|
||||||
|
### 1. Check Module Access by Module ID
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Di router setup
|
||||||
|
moduleAccessMiddleware := middleware.NewModuleAccessMiddleware(db)
|
||||||
|
|
||||||
|
// Protect endpoint dengan module ID
|
||||||
|
app.Get("/api/articles",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMiddleware.CheckModuleAccess(uint(5)), // module_id = 5
|
||||||
|
articleController.GetAll,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Check Module Access by Path URL
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Protect endpoint dengan path_url
|
||||||
|
app.Get("/api/articles",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMiddleware.CheckModuleAccess("/api/articles"), // path_url
|
||||||
|
articleController.GetAll,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Check Module Access by Current Path (Auto)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Auto-detect dari path yang sedang diakses
|
||||||
|
app.Get("/api/articles",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMiddleware.CheckModuleAccessByPath(), // auto-detect
|
||||||
|
articleController.GetAll,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Check Menu Access
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Check akses ke menu (minimal punya akses ke 1 modul di menu tersebut)
|
||||||
|
app.Get("/api/articles/menu",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMiddleware.CheckMenuAccess(uint(1)), // menu_id = 1
|
||||||
|
articleController.GetByMenu,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Implementation Example
|
||||||
|
|
||||||
|
### Scenario: Article Management System
|
||||||
|
|
||||||
|
#### Step 1: Create Modules
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Insert modules untuk Article
|
||||||
|
INSERT INTO master_modules (name, description, path_url, action_type, status_id, is_active) VALUES
|
||||||
|
('View Articles', 'View article list', '/api/articles', 'view', 1, true),
|
||||||
|
('Create Article', 'Create new article', '/api/articles/create', 'create', 1, true),
|
||||||
|
('Edit Article', 'Edit existing article', '/api/articles/edit', 'edit', 1, true),
|
||||||
|
('Delete Article', 'Delete article', '/api/articles/delete', 'delete', 1, true),
|
||||||
|
('Approve Article', 'Approve article', '/api/articles/approve', 'approve', 1, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Create Menu
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Insert menu Article
|
||||||
|
INSERT INTO master_menus (name, description, module_id, icon, "group", position, status_id, is_active) VALUES
|
||||||
|
('Article Management', 'Manage articles', 1, 'article-icon', 'Content', 1, 1, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Link Modules to Menu
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/menu-modules/batch
|
||||||
|
{
|
||||||
|
"menu_id": 1,
|
||||||
|
"module_ids": [1, 2, 3, 4, 5]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via SQL:
|
||||||
|
```sql
|
||||||
|
INSERT INTO menu_modules (menu_id, module_id, position, is_active) VALUES
|
||||||
|
(1, 1, 1, true), -- View Articles
|
||||||
|
(1, 2, 2, true), -- Create Article
|
||||||
|
(1, 3, 3, true), -- Edit Article
|
||||||
|
(1, 4, 4, true), -- Delete Article
|
||||||
|
(1, 5, 5, true); -- Approve Article
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 4: Grant Access to User Levels
|
||||||
|
|
||||||
|
**Admin Pusat (user_level_id = 1) - Full Access:**
|
||||||
|
```http
|
||||||
|
POST /api/user-level-module-accesses/batch
|
||||||
|
{
|
||||||
|
"user_level_id": 1,
|
||||||
|
"module_ids": [1, 2, 3, 4, 5],
|
||||||
|
"can_access": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Editor (user_level_id = 2) - Limited Access:**
|
||||||
|
```http
|
||||||
|
POST /api/user-level-module-accesses/batch
|
||||||
|
{
|
||||||
|
"user_level_id": 2,
|
||||||
|
"module_ids": [1, 2, 3], // Only view, create, edit
|
||||||
|
"can_access": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Viewer (user_level_id = 3) - Read Only:**
|
||||||
|
```http
|
||||||
|
POST /api/user-level-module-accesses/batch
|
||||||
|
{
|
||||||
|
"user_level_id": 3,
|
||||||
|
"module_ids": [1], // Only view
|
||||||
|
"can_access": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 5: Protect Routes
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SetupArticleRoutes(app *fiber.App, db *database.Database, articleController controller.ArticleController) {
|
||||||
|
moduleAccessMw := middleware.NewModuleAccessMiddleware(db)
|
||||||
|
|
||||||
|
articles := app.Group("/api/articles")
|
||||||
|
|
||||||
|
// View - Accessible by all levels with access
|
||||||
|
articles.Get("/",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(1)), // module_id for "View Articles"
|
||||||
|
articleController.GetAll,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create - Accessible by Admin & Editor only
|
||||||
|
articles.Post("/",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(2)), // module_id for "Create Article"
|
||||||
|
articleController.Create,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Edit - Accessible by Admin & Editor only
|
||||||
|
articles.Put("/:id",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(3)), // module_id for "Edit Article"
|
||||||
|
articleController.Update,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete - Accessible by Admin only
|
||||||
|
articles.Delete("/:id",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(4)), // module_id for "Delete Article"
|
||||||
|
articleController.Delete,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Approve - Accessible by Admin only
|
||||||
|
articles.Post("/:id/approve",
|
||||||
|
authMiddleware.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(5)), // module_id for "Approve Article"
|
||||||
|
articleController.Approve,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Migration Script
|
||||||
|
|
||||||
|
Create file: `docs/migrations/004_add_menu_module_access_system.sql`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Migration: Add Menu Module Access System
|
||||||
|
-- Description: Enhance master_modules and create menu_modules & user_level_module_accesses tables
|
||||||
|
|
||||||
|
-- Step 1: Enhance master_modules with action_type
|
||||||
|
ALTER TABLE master_modules
|
||||||
|
ADD COLUMN IF NOT EXISTS action_type VARCHAR NULL;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN master_modules.action_type IS 'Tipe aksi: view, create, edit, delete, approve, export, etc';
|
||||||
|
|
||||||
|
-- Step 2: Create menu_modules table
|
||||||
|
CREATE TABLE IF NOT EXISTS menu_modules (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
module_id INT NOT NULL,
|
||||||
|
position INT NULL,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (module_id) REFERENCES master_modules(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(menu_id, module_id, client_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_menu_modules_menu_id ON menu_modules(menu_id);
|
||||||
|
CREATE INDEX idx_menu_modules_module_id ON menu_modules(module_id);
|
||||||
|
CREATE INDEX idx_menu_modules_client_id ON menu_modules(client_id);
|
||||||
|
CREATE INDEX idx_menu_modules_is_active ON menu_modules(is_active);
|
||||||
|
|
||||||
|
COMMENT ON TABLE menu_modules IS 'Relasi many-to-many antara menu dan modul';
|
||||||
|
COMMENT ON COLUMN menu_modules.position IS 'Urutan modul dalam menu';
|
||||||
|
|
||||||
|
-- Step 3: Create user_level_module_accesses table
|
||||||
|
CREATE TABLE IF NOT EXISTS user_level_module_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
module_id INT NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (module_id) REFERENCES master_modules(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_level_id, module_id, client_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_user_level_id ON user_level_module_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_module_id ON user_level_module_accesses(module_id);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_client_id ON user_level_module_accesses(client_id);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_is_active ON user_level_module_accesses(is_active);
|
||||||
|
|
||||||
|
COMMENT ON TABLE user_level_module_accesses IS 'Mengatur akses user_level ke modul-modul tertentu';
|
||||||
|
COMMENT ON COLUMN user_level_module_accesses.can_access IS 'Apakah user level ini boleh akses modul ini';
|
||||||
|
|
||||||
|
-- Step 4: Migrate existing data (optional)
|
||||||
|
-- Copy existing menu.module_id relationship to menu_modules
|
||||||
|
INSERT INTO menu_modules (menu_id, module_id, position, client_id, is_active, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
id as menu_id,
|
||||||
|
module_id,
|
||||||
|
1 as position,
|
||||||
|
client_id,
|
||||||
|
is_active,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM master_menus
|
||||||
|
WHERE module_id IS NOT NULL
|
||||||
|
ON CONFLICT (menu_id, module_id, client_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- Success message
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE 'Migration completed successfully!';
|
||||||
|
RAISE NOTICE 'Tables created: menu_modules, user_level_module_accesses';
|
||||||
|
RAISE NOTICE 'Column added: master_modules.action_type';
|
||||||
|
END $$;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Testing the System
|
||||||
|
|
||||||
|
### Test 1: Setup Test Data
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Create test modules
|
||||||
|
INSERT INTO master_modules (name, description, path_url, action_type, status_id) VALUES
|
||||||
|
('Test View', 'Test view module', '/test/view', 'view', 1),
|
||||||
|
('Test Create', 'Test create module', '/test/create', 'create', 1),
|
||||||
|
('Test Edit', 'Test edit module', '/test/edit', 'edit', 1);
|
||||||
|
|
||||||
|
-- Create test menu
|
||||||
|
INSERT INTO master_menus (name, description, module_id, status_id) VALUES
|
||||||
|
('Test Menu', 'Test menu for module access', 1, 1);
|
||||||
|
|
||||||
|
-- Link modules to menu
|
||||||
|
INSERT INTO menu_modules (menu_id, module_id, position) VALUES
|
||||||
|
(1, 1, 1),
|
||||||
|
(1, 2, 2),
|
||||||
|
(1, 3, 3);
|
||||||
|
|
||||||
|
-- Grant access to user level
|
||||||
|
INSERT INTO user_level_module_accesses (user_level_id, module_id, can_access) VALUES
|
||||||
|
(1, 1, true), -- Level 1 can view
|
||||||
|
(1, 2, true), -- Level 1 can create
|
||||||
|
(2, 1, true); -- Level 2 can only view
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test 2: Check Access via API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if user level 1 can access module 1 (should return true)
|
||||||
|
curl -X POST http://localhost:3000/api/user-level-module-accesses/check-access \
|
||||||
|
-H "Authorization: Bearer {token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_level_id": 1,
|
||||||
|
"module_id": 1
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Check if user level 2 can access module 2 (should return false)
|
||||||
|
curl -X POST http://localhost:3000/api/user-level-module-accesses/check-access \
|
||||||
|
-H "Authorization: Bearer {token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_level_id": 2,
|
||||||
|
"module_id": 2
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Best Practices
|
||||||
|
|
||||||
|
1. **Granular Modules**: Pisahkan setiap action menjadi modul terpisah untuk kontrol akses yang lebih baik
|
||||||
|
2. **Action Types**: Gunakan action_type konsisten: `view`, `create`, `edit`, `delete`, `approve`, `export`
|
||||||
|
3. **Batch Operations**: Gunakan batch endpoints untuk mengatur banyak akses sekaligus
|
||||||
|
4. **Middleware Layers**: Kombinasikan dengan middleware lain (auth, CSRF, rate limit)
|
||||||
|
5. **Audit Trail**: Log setiap akses yang ditolak untuk security monitoring
|
||||||
|
6. **Default Deny**: Jika tidak ada record di `user_level_module_accesses`, default adalah tidak ada akses
|
||||||
|
|
||||||
|
## 🎯 Benefits
|
||||||
|
|
||||||
|
✅ **Kontrol Akses Granular** - Atur akses hingga level action (view, create, edit, delete)
|
||||||
|
✅ **Fleksibel** - Mudah menambah/mengurangi modul tanpa mengubah kode
|
||||||
|
✅ **Scalable** - Support multi-client/tenant
|
||||||
|
✅ **Maintainable** - Struktur yang jelas dan terorganisir
|
||||||
|
✅ **Secure** - Middleware otomatis block unauthorized access
|
||||||
|
|
||||||
|
## 🔗 Related Documentation
|
||||||
|
|
||||||
|
- [Multi Client Access Guide](./MULTI_CLIENT_ACCESS_GUIDE.md)
|
||||||
|
- [Approval Workflow Architecture](../plan/approval-workflow-architecture.md)
|
||||||
|
- [API Documentation](./notes/api-endpoints-documentation.md)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,317 @@
|
||||||
|
# Quick Start Guide - Menu Module Access System
|
||||||
|
|
||||||
|
## 🚀 Langkah-langkah Setup
|
||||||
|
|
||||||
|
### 1. Jalankan Migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect ke PostgreSQL dan jalankan migration
|
||||||
|
psql -U your_username -d your_database -f docs/migrations/004_add_menu_module_access_system.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Setup Data Master
|
||||||
|
|
||||||
|
#### a. Buat Modules untuk Artikel
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO master_modules (name, description, path_url, action_type, status_id, is_active) VALUES
|
||||||
|
('Lihat Artikel', 'Melihat daftar dan detail artikel', '/api/articles', 'view', 1, true),
|
||||||
|
('Buat Artikel', 'Membuat artikel baru', '/api/articles', 'create', 1, true),
|
||||||
|
('Edit Artikel', 'Mengedit artikel yang ada', '/api/articles/:id', 'edit', 1, true),
|
||||||
|
('Hapus Artikel', 'Menghapus artikel', '/api/articles/:id', 'delete', 1, true),
|
||||||
|
('Approve Artikel', 'Menyetujui artikel', '/api/articles/:id/approve', 'approve', 1, true);
|
||||||
|
-- Dapatkan ID modules yang baru dibuat untuk step selanjutnya
|
||||||
|
```
|
||||||
|
|
||||||
|
#### b. Buat Menu Artikel
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO master_menus (name, description, module_id, icon, "group", position, status_id, is_active)
|
||||||
|
VALUES ('Artikel', 'Manajemen Artikel', 1, 'article-icon', 'Konten', 1, 1, true);
|
||||||
|
-- Dapatkan menu_id untuk step selanjutnya (misal: menu_id = 10)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### c. Hubungkan Menu dengan Modules
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Asumsikan menu_id = 10, dan module_ids = 1,2,3,4,5
|
||||||
|
INSERT INTO menu_modules (menu_id, module_id, position, is_active) VALUES
|
||||||
|
(10, 1, 1, true), -- Lihat
|
||||||
|
(10, 2, 2, true), -- Buat
|
||||||
|
(10, 3, 3, true), -- Edit
|
||||||
|
(10, 4, 4, true), -- Hapus
|
||||||
|
(10, 5, 5, true); -- Approve
|
||||||
|
```
|
||||||
|
|
||||||
|
#### d. Berikan Akses ke User Levels
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Admin Pusat (user_level_id = 1) - Full Access
|
||||||
|
INSERT INTO user_level_module_accesses (user_level_id, module_id, can_access, is_active) VALUES
|
||||||
|
(1, 1, true, true), -- Lihat
|
||||||
|
(1, 2, true, true), -- Buat
|
||||||
|
(1, 3, true, true), -- Edit
|
||||||
|
(1, 4, true, true), -- Hapus
|
||||||
|
(1, 5, true, true); -- Approve
|
||||||
|
|
||||||
|
-- Editor (user_level_id = 2) - Lihat, Buat, Edit saja
|
||||||
|
INSERT INTO user_level_module_accesses (user_level_id, module_id, can_access, is_active) VALUES
|
||||||
|
(2, 1, true, true), -- Lihat
|
||||||
|
(2, 2, true, true), -- Buat
|
||||||
|
(2, 3, true, true); -- Edit
|
||||||
|
|
||||||
|
-- Viewer (user_level_id = 3) - Lihat saja
|
||||||
|
INSERT INTO user_level_module_accesses (user_level_id, module_id, can_access, is_active) VALUES
|
||||||
|
(3, 1, true, true); -- Lihat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Implementasi di Code
|
||||||
|
|
||||||
|
#### a. Tambahkan Routes dengan Middleware
|
||||||
|
|
||||||
|
Buat file baru atau update: `app/router/article.routes.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"netidhub-saas-be/app/database"
|
||||||
|
"netidhub-saas-be/app/middleware"
|
||||||
|
"netidhub-saas-be/app/module/articles/controller"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupArticleRoutes(app *fiber.App, db *database.Database, ctrl controller.ArticleController) {
|
||||||
|
// Initialize middlewares
|
||||||
|
authMw := middleware.NewUserMiddleware(db)
|
||||||
|
moduleAccessMw := middleware.NewModuleAccessMiddleware(db)
|
||||||
|
|
||||||
|
// Article routes group
|
||||||
|
articles := app.Group("/api/articles")
|
||||||
|
|
||||||
|
// GET /api/articles - View (module_id = 1)
|
||||||
|
articles.Get("/",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(1)),
|
||||||
|
ctrl.GetAll,
|
||||||
|
)
|
||||||
|
|
||||||
|
// GET /api/articles/:id - View detail (module_id = 1)
|
||||||
|
articles.Get("/:id",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(1)),
|
||||||
|
ctrl.GetOne,
|
||||||
|
)
|
||||||
|
|
||||||
|
// POST /api/articles - Create (module_id = 2)
|
||||||
|
articles.Post("/",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(2)),
|
||||||
|
ctrl.Create,
|
||||||
|
)
|
||||||
|
|
||||||
|
// PUT /api/articles/:id - Edit (module_id = 3)
|
||||||
|
articles.Put("/:id",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(3)),
|
||||||
|
ctrl.Update,
|
||||||
|
)
|
||||||
|
|
||||||
|
// DELETE /api/articles/:id - Delete (module_id = 4)
|
||||||
|
articles.Delete("/:id",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(4)),
|
||||||
|
ctrl.Delete,
|
||||||
|
)
|
||||||
|
|
||||||
|
// POST /api/articles/:id/approve - Approve (module_id = 5)
|
||||||
|
articles.Post("/:id/approve",
|
||||||
|
authMw.ValidateToken(),
|
||||||
|
moduleAccessMw.CheckModuleAccess(uint(5)),
|
||||||
|
ctrl.Approve,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### b. Register Routes di Main Router
|
||||||
|
|
||||||
|
Update `app/router/api.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Import article routes
|
||||||
|
import (
|
||||||
|
articleController "netidhub-saas-be/app/module/articles/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Di dalam fungsi RegisterRoutes
|
||||||
|
func RegisterRoutes(app *fiber.App, db *database.Database) {
|
||||||
|
// ... existing routes ...
|
||||||
|
|
||||||
|
// Article routes with module access control
|
||||||
|
articleCtrl := articleController.NewArticleController(articleService)
|
||||||
|
SetupArticleRoutes(app, db, articleCtrl)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Testing
|
||||||
|
|
||||||
|
#### Test 1: User dengan Full Access (Admin)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login sebagai admin (user_level_id = 1)
|
||||||
|
curl -X POST http://localhost:3000/api/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "password"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Copy token dari response
|
||||||
|
TOKEN="your_admin_token_here"
|
||||||
|
|
||||||
|
# Test akses semua endpoint - Semua harus berhasil
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles -d '{"title":"Test"}'
|
||||||
|
curl -X PUT -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles/1 -d '{"title":"Updated"}'
|
||||||
|
curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles/1
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles/1/approve
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 2: User dengan Limited Access (Editor)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login sebagai editor (user_level_id = 2)
|
||||||
|
curl -X POST http://localhost:3000/api/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"username": "editor",
|
||||||
|
"password": "password"
|
||||||
|
}'
|
||||||
|
|
||||||
|
TOKEN="your_editor_token_here"
|
||||||
|
|
||||||
|
# Test akses - View, Create, Edit harus berhasil
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles # ✓ Berhasil
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles -d '{"title":"Test"}' # ✓ Berhasil
|
||||||
|
curl -X PUT -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles/1 -d '{"title":"Updated"}' # ✓ Berhasil
|
||||||
|
|
||||||
|
# Test akses - Delete dan Approve harus ditolak
|
||||||
|
curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles/1 # ✗ 403 Forbidden
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/articles/1/approve # ✗ 403 Forbidden
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 3: Check Access via API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check apakah user level 2 bisa akses module 4 (Delete)
|
||||||
|
curl -X POST http://localhost:3000/api/user-level-module-accesses/check-access \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_level_id": 2,
|
||||||
|
"module_id": 4
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Expected response:
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "messages": ["Access check completed"],
|
||||||
|
# "data": {
|
||||||
|
# "has_access": false
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Manage Access via API
|
||||||
|
|
||||||
|
#### Berikan Akses Baru
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Berikan akses Delete ke Editor (user_level_id = 2, module_id = 4)
|
||||||
|
curl -X POST http://localhost:3000/api/user-level-module-accesses \
|
||||||
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_level_id": 2,
|
||||||
|
"module_id": 4,
|
||||||
|
"can_access": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Berikan Akses Multiple Modules Sekaligus
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Berikan akses ke banyak modul sekaligus (batch)
|
||||||
|
curl -X POST http://localhost:3000/api/user-level-module-accesses/batch \
|
||||||
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_level_id": 4,
|
||||||
|
"module_ids": [1, 2, 3],
|
||||||
|
"can_access": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lihat Akses User Level
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lihat semua akses untuk user_level_id = 2
|
||||||
|
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||||
|
"http://localhost:3000/api/user-level-module-accesses/user-level/2"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cabut Akses
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update akses menjadi false (cabut akses)
|
||||||
|
curl -X PUT http://localhost:3000/api/user-level-module-accesses/123 \
|
||||||
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"can_access": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Checklist Implementation
|
||||||
|
|
||||||
|
- [ ] Migration script sudah dijalankan
|
||||||
|
- [ ] Tabel `menu_modules` dan `user_level_module_accesses` sudah ada
|
||||||
|
- [ ] Modules sudah dibuat di `master_modules`
|
||||||
|
- [ ] Menu sudah dibuat di `master_menus`
|
||||||
|
- [ ] Menu-Module sudah dihubungkan di `menu_modules`
|
||||||
|
- [ ] User Level Access sudah dikonfigurasi di `user_level_module_accesses`
|
||||||
|
- [ ] Middleware `ModuleAccessMiddleware` sudah diterapkan di routes
|
||||||
|
- [ ] Testing berhasil untuk berbagai user level
|
||||||
|
- [ ] Dokumentasi internal sudah diupdate
|
||||||
|
|
||||||
|
## 🎯 Tips & Best Practices
|
||||||
|
|
||||||
|
1. **Konsisten dengan Action Type**: Gunakan standar `view`, `create`, `edit`, `delete`, `approve`, `export`
|
||||||
|
2. **Batch Operations**: Gunakan endpoint batch untuk setup awal atau bulk changes
|
||||||
|
3. **Soft Delete**: Gunakan `is_active=false` daripada hard delete
|
||||||
|
4. **Audit Log**: Log setiap perubahan access control untuk audit trail
|
||||||
|
5. **Default Deny**: Jika tidak ada record = tidak ada akses (secure by default)
|
||||||
|
|
||||||
|
## ❓ Troubleshooting
|
||||||
|
|
||||||
|
### Error: "User tidak valid"
|
||||||
|
- Pastikan middleware auth (`ValidateToken()`) dipanggil sebelum `CheckModuleAccess()`
|
||||||
|
- Pastikan user sudah login dan token valid
|
||||||
|
|
||||||
|
### Error: "Module tidak ditemukan"
|
||||||
|
- Cek module_id yang digunakan di middleware
|
||||||
|
- Pastikan module exists dan `is_active = true`
|
||||||
|
|
||||||
|
### Error: "Anda tidak memiliki akses ke modul ini"
|
||||||
|
- Cek `user_level_module_accesses` untuk user level tersebut
|
||||||
|
- Pastikan `can_access = true` dan `is_active = true`
|
||||||
|
|
||||||
|
### User Level tidak sesuai
|
||||||
|
- Cek `user_roles.user_level_id` untuk user tersebut
|
||||||
|
- Pastikan relasi users -> user_roles -> user_levels sudah benar
|
||||||
|
|
||||||
|
## 📚 Selanjutnya
|
||||||
|
|
||||||
|
Baca dokumentasi lengkap di: [MENU_MODULE_ACCESS_SYSTEM.md](./MENU_MODULE_ACCESS_SYSTEM.md)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,311 @@
|
||||||
|
# 📋 Plan: Menu & Action Access Control System
|
||||||
|
|
||||||
|
## 🎯 Tujuan
|
||||||
|
|
||||||
|
Membangun sistem akses kontrol yang memungkinkan:
|
||||||
|
1. **1 Menu memiliki banyak Actions** - Satu menu dapat memiliki berbagai action (view table, create, edit, delete, approve, export, dll)
|
||||||
|
2. **User Level dapat di-assign Menu** - Setiap user level dapat di-assign menu apa saja yang bisa diakses
|
||||||
|
3. **User Level dapat di-assign Actions per Menu** - Setiap user level dapat di-assign action apa saja yang bisa dilakukan di setiap menu (contoh: Creator hanya bisa create dan edit, Approver bisa delete)
|
||||||
|
|
||||||
|
## 🔍 Analisis Kebutuhan
|
||||||
|
|
||||||
|
### Use Case 1: Menu "Content Management"
|
||||||
|
- **Actions yang tersedia:**
|
||||||
|
- `view` - Melihat daftar content
|
||||||
|
- `create` - Membuat content baru
|
||||||
|
- `edit` - Mengedit content yang ada
|
||||||
|
- `delete` - Menghapus content
|
||||||
|
- `approve` - Approve content
|
||||||
|
- `export` - Export data content
|
||||||
|
|
||||||
|
### Use Case 2: User Level "Creator"
|
||||||
|
- **Menu yang bisa diakses:** Content Management
|
||||||
|
- **Actions yang bisa dilakukan:**
|
||||||
|
- ✅ `view` - Bisa melihat daftar
|
||||||
|
- ✅ `create` - Bisa membuat baru
|
||||||
|
- ✅ `edit` - Bisa mengedit
|
||||||
|
- ❌ `delete` - Tidak bisa menghapus
|
||||||
|
- ❌ `approve` - Tidak bisa approve
|
||||||
|
- ❌ `export` - Tidak bisa export
|
||||||
|
|
||||||
|
### Use Case 3: User Level "Approver"
|
||||||
|
- **Menu yang bisa diakses:** Content Management
|
||||||
|
- **Actions yang bisa dilakukan:**
|
||||||
|
- ✅ `view` - Bisa melihat daftar
|
||||||
|
- ❌ `create` - Tidak bisa membuat baru
|
||||||
|
- ❌ `edit` - Tidak bisa mengedit
|
||||||
|
- ✅ `delete` - Bisa menghapus
|
||||||
|
- ✅ `approve` - Bisa approve
|
||||||
|
- ✅ `export` - Bisa export
|
||||||
|
|
||||||
|
## 🏗️ Arsitektur Sistem
|
||||||
|
|
||||||
|
### Opsi 1: Menu-Action Based (Recommended) ⭐
|
||||||
|
|
||||||
|
**Konsep:**
|
||||||
|
- Menu memiliki Actions langsung (bukan melalui Module)
|
||||||
|
- User Level memiliki akses ke Menu
|
||||||
|
- User Level memiliki akses ke Actions di dalam Menu
|
||||||
|
|
||||||
|
**Struktur Database:**
|
||||||
|
|
||||||
|
#### 1. `master_menus` (Existing - Enhanced)
|
||||||
|
```sql
|
||||||
|
-- Sudah ada, tidak perlu perubahan
|
||||||
|
-- Menu seperti: "Content Management", "User Management", "Settings"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. `menu_actions` (New)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE menu_actions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
action_code VARCHAR(50) NOT NULL, -- 'view', 'create', 'edit', 'delete', 'approve', 'export'
|
||||||
|
action_name VARCHAR(255) NOT NULL, -- 'View Content', 'Create Content', etc.
|
||||||
|
description TEXT NULL,
|
||||||
|
path_url VARCHAR(255) NULL, -- Optional: untuk routing frontend
|
||||||
|
http_method VARCHAR(10) NULL, -- Optional: 'GET', 'POST', 'PUT', 'DELETE'
|
||||||
|
position INT NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(menu_id, action_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_menu_actions_menu_id ON menu_actions(menu_id);
|
||||||
|
CREATE INDEX idx_menu_actions_action_code ON menu_actions(action_code);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. `user_level_menu_accesses` (New)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE user_level_menu_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_level_id, menu_id, client_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_level_menu_accesses_user_level_id ON user_level_menu_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_accesses_menu_id ON user_level_menu_accesses(menu_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. `user_level_menu_action_accesses` (New)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE user_level_menu_action_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
action_code VARCHAR(50) NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (menu_id, action_code) REFERENCES menu_actions(menu_id, action_code) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_level_id, menu_id, action_code, client_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_user_level_id ON user_level_menu_action_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_menu_id ON user_level_menu_action_accesses(menu_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_action_code ON user_level_menu_action_accesses(action_code);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relasi:**
|
||||||
|
```
|
||||||
|
master_menus (1) ─────< (N) menu_actions
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ (1) │ (N)
|
||||||
|
│ │
|
||||||
|
∨ ∨
|
||||||
|
user_level_menu_accesses user_level_menu_action_accesses
|
||||||
|
│ │
|
||||||
|
│ (N) │ (N)
|
||||||
|
│ │
|
||||||
|
∨ ∨
|
||||||
|
user_levels (1) ──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keuntungan:**
|
||||||
|
- ✅ Lebih sederhana dan intuitif
|
||||||
|
- ✅ Langsung ke point: Menu → Actions
|
||||||
|
- ✅ Mudah di-manage: assign menu, lalu assign actions
|
||||||
|
- ✅ Tidak perlu konsep "Module" yang membingungkan
|
||||||
|
- ✅ Frontend lebih mudah: cek menu access, lalu cek action access
|
||||||
|
|
||||||
|
**Kekurangan:**
|
||||||
|
- ❌ Perlu migration dari sistem Module yang ada
|
||||||
|
- ❌ Perlu refactor middleware dan service yang sudah ada
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Opsi 2: Keep Module, Enhance Structure
|
||||||
|
|
||||||
|
**Konsep:**
|
||||||
|
- Tetap menggunakan Module, tapi Module sekarang lebih spesifik ke Menu
|
||||||
|
- Module = Action di dalam Menu
|
||||||
|
- User Level memiliki akses ke Menu
|
||||||
|
- User Level memiliki akses ke Module (Action) di dalam Menu
|
||||||
|
|
||||||
|
**Struktur Database:**
|
||||||
|
|
||||||
|
#### 1. `master_menus` (Existing)
|
||||||
|
```sql
|
||||||
|
-- Tidak berubah
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. `master_modules` (Existing - Enhanced)
|
||||||
|
```sql
|
||||||
|
-- Tetap ada, tapi sekarang lebih spesifik:
|
||||||
|
-- Module sekarang = Action di dalam Menu
|
||||||
|
-- action_type menjadi lebih penting
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. `menu_modules` (Existing - Enhanced)
|
||||||
|
```sql
|
||||||
|
-- Tetap ada, relasi Menu dengan Module (Action)
|
||||||
|
-- Tapi sekarang lebih strict: 1 module hanya untuk 1 menu
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. `user_level_menu_accesses` (New)
|
||||||
|
```sql
|
||||||
|
-- Sama seperti Opsi 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. `user_level_module_accesses` (Existing)
|
||||||
|
```sql
|
||||||
|
-- Tetap ada, tapi sekarang lebih spesifik:
|
||||||
|
-- Module = Action di dalam Menu
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keuntungan:**
|
||||||
|
- ✅ Tidak perlu migration besar-besaran
|
||||||
|
- ✅ Bisa reuse struktur yang sudah ada
|
||||||
|
- ✅ Lebih fleksibel (module bisa digunakan oleh banyak menu)
|
||||||
|
|
||||||
|
**Kekurangan:**
|
||||||
|
- ❌ Konsep "Module" masih membingungkan
|
||||||
|
- ❌ Lebih kompleks: Menu → Module → Action
|
||||||
|
- ❌ Perlu mapping yang lebih kompleks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Rekomendasi: Opsi 1 (Menu-Action Based)
|
||||||
|
|
||||||
|
**Alasan:**
|
||||||
|
1. **Lebih intuitif** - Langsung ke point: Menu punya Actions
|
||||||
|
2. **Lebih mudah di-manage** - Admin langsung assign menu dan actions
|
||||||
|
3. **Lebih mudah di-frontend** - Cek menu access, lalu cek action access
|
||||||
|
4. **Lebih scalable** - Mudah ditambah menu baru dengan actions baru
|
||||||
|
5. **Lebih maintainable** - Struktur lebih jelas dan mudah dipahami
|
||||||
|
|
||||||
|
## 📝 Implementation Plan
|
||||||
|
|
||||||
|
### Phase 1: Database Schema
|
||||||
|
1. ✅ Buat tabel `menu_actions`
|
||||||
|
2. ✅ Buat tabel `user_level_menu_accesses`
|
||||||
|
3. ✅ Buat tabel `user_level_menu_action_accesses`
|
||||||
|
4. ✅ Buat migration script
|
||||||
|
5. ✅ Migrate data dari `master_modules` dan `menu_modules` ke `menu_actions` (jika perlu)
|
||||||
|
|
||||||
|
### Phase 2: Backend Entities & Repositories
|
||||||
|
1. ✅ Buat entity `MenuActions`
|
||||||
|
2. ✅ Buat entity `UserLevelMenuAccesses`
|
||||||
|
3. ✅ Buat entity `UserLevelMenuActionAccesses`
|
||||||
|
4. ✅ Buat repository untuk masing-masing entity
|
||||||
|
5. ✅ Buat service untuk masing-masing entity
|
||||||
|
|
||||||
|
### Phase 3: Backend API Endpoints
|
||||||
|
1. ✅ CRUD API untuk `menu_actions`
|
||||||
|
2. ✅ CRUD API untuk `user_level_menu_accesses`
|
||||||
|
3. ✅ CRUD API untuk `user_level_menu_action_accesses`
|
||||||
|
4. ✅ API untuk get menu actions by menu_id
|
||||||
|
5. ✅ API untuk get user level menu accesses
|
||||||
|
6. ✅ API untuk get user level menu action accesses
|
||||||
|
|
||||||
|
### Phase 4: Backend Middleware
|
||||||
|
1. ✅ Buat middleware `CheckMenuAccess` - cek apakah user level bisa akses menu
|
||||||
|
2. ✅ Buat middleware `CheckMenuActionAccess` - cek apakah user level bisa akses action di menu
|
||||||
|
3. ✅ Update existing middleware untuk menggunakan struktur baru
|
||||||
|
|
||||||
|
### Phase 5: Frontend Services
|
||||||
|
1. ✅ Buat service untuk `menu_actions`
|
||||||
|
2. ✅ Buat service untuk `user_level_menu_accesses`
|
||||||
|
3. ✅ Buat service untuk `user_level_menu_action_accesses`
|
||||||
|
4. ✅ Update existing services
|
||||||
|
|
||||||
|
### Phase 6: Frontend UI - Menu Management
|
||||||
|
1. ✅ Update halaman Menu Management untuk menampilkan Actions
|
||||||
|
2. ✅ Tambah form untuk manage Actions di setiap Menu
|
||||||
|
3. ✅ Tambah UI untuk assign Actions ke Menu
|
||||||
|
|
||||||
|
### Phase 7: Frontend UI - User Level Management
|
||||||
|
1. ✅ Update halaman User Level Management
|
||||||
|
2. ✅ Tambah tab "Menu Access" untuk assign menu ke user level
|
||||||
|
3. ✅ Tambah tab "Action Access" untuk assign actions per menu ke user level
|
||||||
|
4. ✅ Update form User Level untuk include menu dan action access
|
||||||
|
|
||||||
|
### Phase 8: Frontend UI - Permission Check
|
||||||
|
1. ✅ Buat hook `useMenuAccess` untuk cek menu access
|
||||||
|
2. ✅ Buat hook `useMenuActionAccess` untuk cek action access
|
||||||
|
3. ✅ Update components untuk menggunakan hooks
|
||||||
|
4. ✅ Hide/show UI elements berdasarkan permission
|
||||||
|
|
||||||
|
### Phase 9: Testing & Documentation
|
||||||
|
1. ✅ Test semua API endpoints
|
||||||
|
2. ✅ Test middleware
|
||||||
|
3. ✅ Test frontend UI
|
||||||
|
4. ✅ Update documentation
|
||||||
|
|
||||||
|
## 🔄 Migration Strategy
|
||||||
|
|
||||||
|
### Option A: Clean Slate (Recommended)
|
||||||
|
- Buat struktur baru dari awal
|
||||||
|
- Data lama tetap ada tapi tidak digunakan
|
||||||
|
- Migrate data secara bertahap jika perlu
|
||||||
|
|
||||||
|
### Option B: Gradual Migration
|
||||||
|
- Keep struktur lama dan baru berjalan bersamaan
|
||||||
|
- Migrate data dari lama ke baru
|
||||||
|
- Deprecate struktur lama setelah semua ter-migrate
|
||||||
|
|
||||||
|
## 📊 Comparison: Current vs Proposed
|
||||||
|
|
||||||
|
### Current System
|
||||||
|
```
|
||||||
|
Menu → Module (via menu_modules) → User Level Access (via user_level_module_accesses)
|
||||||
|
```
|
||||||
|
- Module adalah entitas terpisah
|
||||||
|
- Module bisa digunakan oleh banyak menu
|
||||||
|
- Akses diberikan ke Module, bukan ke Menu + Action
|
||||||
|
|
||||||
|
### Proposed System
|
||||||
|
```
|
||||||
|
Menu → Actions (via menu_actions) → User Level Menu Access → User Level Action Access
|
||||||
|
```
|
||||||
|
- Actions langsung di dalam Menu
|
||||||
|
- Actions spesifik untuk Menu tertentu
|
||||||
|
- Akses diberikan ke Menu dan Actions secara terpisah
|
||||||
|
|
||||||
|
## ✅ Next Steps
|
||||||
|
|
||||||
|
1. **Review & Approval** - Review plan ini dengan tim
|
||||||
|
2. **Decision** - Pilih antara Opsi 1 atau Opsi 2
|
||||||
|
3. **Database Design** - Finalize database schema
|
||||||
|
4. **Implementation** - Mulai implementasi sesuai phase yang sudah direncanakan
|
||||||
|
|
||||||
|
## 📚 Reference
|
||||||
|
|
||||||
|
- Current system: `docs/MENU_MODULE_ACCESS_SYSTEM.md`
|
||||||
|
- Implementation summary: `docs/IMPLEMENTATION_SUMMARY_MENU_MODULE_ACCESS.md`
|
||||||
|
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
-- Migration: Add Menu Module Access System
|
||||||
|
-- Description: Enhance master_modules and create menu_modules & user_level_module_accesses tables
|
||||||
|
-- Date: 2026-01-15
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 1: Enhance master_modules with action_type
|
||||||
|
-- ============================================================================
|
||||||
|
ALTER TABLE master_modules
|
||||||
|
ADD COLUMN IF NOT EXISTS action_type VARCHAR NULL;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN master_modules.action_type IS 'Tipe aksi: view, create, edit, delete, approve, export, etc';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 2: Create menu_modules table
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS menu_modules (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
module_id INT NOT NULL,
|
||||||
|
position INT NULL,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (module_id) REFERENCES master_modules(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add unique constraint with client_id consideration
|
||||||
|
CREATE UNIQUE INDEX idx_menu_modules_unique
|
||||||
|
ON menu_modules(menu_id, module_id, COALESCE(client_id, '00000000-0000-0000-0000-000000000000'::UUID));
|
||||||
|
|
||||||
|
-- Add other indexes for performance
|
||||||
|
CREATE INDEX idx_menu_modules_menu_id ON menu_modules(menu_id);
|
||||||
|
CREATE INDEX idx_menu_modules_module_id ON menu_modules(module_id);
|
||||||
|
CREATE INDEX idx_menu_modules_client_id ON menu_modules(client_id) WHERE client_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_menu_modules_is_active ON menu_modules(is_active);
|
||||||
|
|
||||||
|
-- Add comments
|
||||||
|
COMMENT ON TABLE menu_modules IS 'Relasi many-to-many antara menu dan modul. Satu menu bisa punya banyak modul.';
|
||||||
|
COMMENT ON COLUMN menu_modules.position IS 'Urutan modul dalam menu untuk sorting';
|
||||||
|
COMMENT ON COLUMN menu_modules.menu_id IS 'Foreign key ke master_menus';
|
||||||
|
COMMENT ON COLUMN menu_modules.module_id IS 'Foreign key ke master_modules';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 3: Create user_level_module_accesses table
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS user_level_module_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
module_id INT NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (module_id) REFERENCES master_modules(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add unique constraint with client_id consideration
|
||||||
|
CREATE UNIQUE INDEX idx_user_level_module_accesses_unique
|
||||||
|
ON user_level_module_accesses(user_level_id, module_id, COALESCE(client_id, '00000000-0000-0000-0000-000000000000'::UUID));
|
||||||
|
|
||||||
|
-- Add other indexes for performance
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_user_level_id ON user_level_module_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_module_id ON user_level_module_accesses(module_id);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_client_id ON user_level_module_accesses(client_id) WHERE client_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_is_active ON user_level_module_accesses(is_active);
|
||||||
|
CREATE INDEX idx_user_level_module_accesses_can_access ON user_level_module_accesses(can_access);
|
||||||
|
|
||||||
|
-- Add comments
|
||||||
|
COMMENT ON TABLE user_level_module_accesses IS 'Mengatur akses user_level ke modul-modul tertentu. Kontrol akses granular per modul.';
|
||||||
|
COMMENT ON COLUMN user_level_module_accesses.can_access IS 'Apakah user level ini boleh akses modul ini. True=boleh, False=tidak boleh';
|
||||||
|
COMMENT ON COLUMN user_level_module_accesses.user_level_id IS 'Foreign key ke user_levels';
|
||||||
|
COMMENT ON COLUMN user_level_module_accesses.module_id IS 'Foreign key ke master_modules';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 4: Migrate existing data (optional - only if you have existing data)
|
||||||
|
-- ============================================================================
|
||||||
|
-- Copy existing menu.module_id relationship to menu_modules
|
||||||
|
-- This creates initial menu-module relationships from existing master_menus
|
||||||
|
INSERT INTO menu_modules (menu_id, module_id, position, client_id, is_active, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
id as menu_id,
|
||||||
|
module_id,
|
||||||
|
position as position, -- Use existing position if available
|
||||||
|
client_id,
|
||||||
|
is_active,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM master_menus
|
||||||
|
WHERE module_id IS NOT NULL
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 5: Insert sample modules for testing (optional)
|
||||||
|
-- ============================================================================
|
||||||
|
-- You can uncomment this section if you want sample data
|
||||||
|
|
||||||
|
/*
|
||||||
|
-- Sample modules for Article Management
|
||||||
|
INSERT INTO master_modules (name, description, path_url, action_type, status_id, is_active) VALUES
|
||||||
|
('View Articles', 'View article list and details', '/api/articles', 'view', 1, true),
|
||||||
|
('Create Article', 'Create new article', '/api/articles/create', 'create', 1, true),
|
||||||
|
('Edit Article', 'Edit existing article', '/api/articles/edit', 'edit', 1, true),
|
||||||
|
('Delete Article', 'Delete article', '/api/articles/delete', 'delete', 1, true),
|
||||||
|
('Approve Article', 'Approve article submission', '/api/articles/approve', 'approve', 1, true),
|
||||||
|
('Export Articles', 'Export articles to file', '/api/articles/export', 'export', 1, true)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Sample modules for User Management
|
||||||
|
INSERT INTO master_modules (name, description, path_url, action_type, status_id, is_active) VALUES
|
||||||
|
('View Users', 'View user list and details', '/api/users', 'view', 1, true),
|
||||||
|
('Create User', 'Create new user', '/api/users/create', 'create', 1, true),
|
||||||
|
('Edit User', 'Edit existing user', '/api/users/edit', 'edit', 1, true),
|
||||||
|
('Delete User', 'Delete user', '/api/users/delete', 'delete', 1, true)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 6: Verification queries
|
||||||
|
-- ============================================================================
|
||||||
|
-- Run these queries to verify the migration
|
||||||
|
|
||||||
|
-- Check if tables exist
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
v_menu_modules_exists BOOLEAN;
|
||||||
|
v_user_level_module_accesses_exists BOOLEAN;
|
||||||
|
v_action_type_exists BOOLEAN;
|
||||||
|
BEGIN
|
||||||
|
-- Check menu_modules table
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'menu_modules'
|
||||||
|
) INTO v_menu_modules_exists;
|
||||||
|
|
||||||
|
-- Check user_level_module_accesses table
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'user_level_module_accesses'
|
||||||
|
) INTO v_user_level_module_accesses_exists;
|
||||||
|
|
||||||
|
-- Check action_type column
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'master_modules'
|
||||||
|
AND column_name = 'action_type'
|
||||||
|
) INTO v_action_type_exists;
|
||||||
|
|
||||||
|
-- Report results
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
RAISE NOTICE 'Migration Verification Results:';
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
|
||||||
|
IF v_menu_modules_exists THEN
|
||||||
|
RAISE NOTICE '✓ Table menu_modules created successfully';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '✗ Table menu_modules NOT created';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_user_level_module_accesses_exists THEN
|
||||||
|
RAISE NOTICE '✓ Table user_level_module_accesses created successfully';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '✗ Table user_level_module_accesses NOT created';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_action_type_exists THEN
|
||||||
|
RAISE NOTICE '✓ Column action_type added to master_modules';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '✗ Column action_type NOT added to master_modules';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
|
||||||
|
IF v_menu_modules_exists AND v_user_level_module_accesses_exists AND v_action_type_exists THEN
|
||||||
|
RAISE NOTICE 'Migration completed successfully! ✓';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Migration completed with errors! Please check above.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Show table statistics
|
||||||
|
SELECT
|
||||||
|
'menu_modules' as table_name,
|
||||||
|
COUNT(*) as row_count
|
||||||
|
FROM menu_modules
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'user_level_module_accesses' as table_name,
|
||||||
|
COUNT(*) as row_count
|
||||||
|
FROM user_level_module_accesses;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
-- Migration: Add Menu Action Access System
|
||||||
|
-- Description: Create menu_actions, user_level_menu_accesses, and user_level_menu_action_accesses tables
|
||||||
|
-- Date: 2026-01-16
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 1: Create menu_actions table
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS menu_actions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
action_code VARCHAR(50) NOT NULL,
|
||||||
|
action_name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
path_url VARCHAR(255) NULL,
|
||||||
|
http_method VARCHAR(10) NULL,
|
||||||
|
position INT NULL,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(menu_id, action_code, COALESCE(client_id, '00000000-0000-0000-0000-000000000000'::UUID))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add indexes for performance
|
||||||
|
CREATE INDEX idx_menu_actions_menu_id ON menu_actions(menu_id);
|
||||||
|
CREATE INDEX idx_menu_actions_action_code ON menu_actions(action_code);
|
||||||
|
CREATE INDEX idx_menu_actions_client_id ON menu_actions(client_id) WHERE client_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_menu_actions_is_active ON menu_actions(is_active);
|
||||||
|
|
||||||
|
-- Add comments
|
||||||
|
COMMENT ON TABLE menu_actions IS 'Actions yang tersedia di setiap menu (view, create, edit, delete, approve, export, etc)';
|
||||||
|
COMMENT ON COLUMN menu_actions.menu_id IS 'Foreign key ke master_menus';
|
||||||
|
COMMENT ON COLUMN menu_actions.action_code IS 'Kode action: view, create, edit, delete, approve, export, etc';
|
||||||
|
COMMENT ON COLUMN menu_actions.action_name IS 'Nama action yang ditampilkan ke user';
|
||||||
|
COMMENT ON COLUMN menu_actions.path_url IS 'Optional: URL path untuk routing frontend';
|
||||||
|
COMMENT ON COLUMN menu_actions.http_method IS 'Optional: HTTP method (GET, POST, PUT, DELETE)';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 2: Create user_level_menu_accesses table
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS user_level_menu_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_level_id, menu_id, COALESCE(client_id, '00000000-0000-0000-0000-000000000000'::UUID))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add indexes for performance
|
||||||
|
CREATE INDEX idx_user_level_menu_accesses_user_level_id ON user_level_menu_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_accesses_menu_id ON user_level_menu_accesses(menu_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_accesses_client_id ON user_level_menu_accesses(client_id) WHERE client_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_user_level_menu_accesses_is_active ON user_level_menu_accesses(is_active);
|
||||||
|
|
||||||
|
-- Add comments
|
||||||
|
COMMENT ON TABLE user_level_menu_accesses IS 'Mengatur akses user_level ke menu tertentu';
|
||||||
|
COMMENT ON COLUMN user_level_menu_accesses.user_level_id IS 'Foreign key ke user_levels';
|
||||||
|
COMMENT ON COLUMN user_level_menu_accesses.menu_id IS 'Foreign key ke master_menus';
|
||||||
|
COMMENT ON COLUMN user_level_menu_accesses.can_access IS 'Apakah user level boleh mengakses menu ini';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 3: Create user_level_menu_action_accesses table
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS user_level_menu_action_accesses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_level_id INT NOT NULL,
|
||||||
|
menu_id INT NOT NULL,
|
||||||
|
action_code VARCHAR(50) NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT TRUE,
|
||||||
|
client_id UUID NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (user_level_id) REFERENCES user_levels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (menu_id) REFERENCES master_menus(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_level_id, menu_id, action_code, COALESCE(client_id, '00000000-0000-0000-0000-000000000000'::UUID))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add indexes for performance
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_user_level_id ON user_level_menu_action_accesses(user_level_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_menu_id ON user_level_menu_action_accesses(menu_id);
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_action_code ON user_level_menu_action_accesses(action_code);
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_client_id ON user_level_menu_action_accesses(client_id) WHERE client_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_user_level_menu_action_accesses_is_active ON user_level_menu_action_accesses(is_active);
|
||||||
|
|
||||||
|
-- Add comments
|
||||||
|
COMMENT ON TABLE user_level_menu_action_accesses IS 'Mengatur akses user_level ke action tertentu di dalam menu';
|
||||||
|
COMMENT ON COLUMN user_level_menu_action_accesses.user_level_id IS 'Foreign key ke user_levels';
|
||||||
|
COMMENT ON COLUMN user_level_menu_action_accesses.menu_id IS 'Foreign key ke master_menus';
|
||||||
|
COMMENT ON COLUMN user_level_menu_action_accesses.action_code IS 'Kode action (harus sesuai dengan menu_actions.action_code)';
|
||||||
|
COMMENT ON COLUMN user_level_menu_action_accesses.can_access IS 'Apakah user level boleh melakukan action ini';
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Step 4: Create trigger for updated_at
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ language 'plpgsql';
|
||||||
|
|
||||||
|
-- Apply trigger to menu_actions
|
||||||
|
DROP TRIGGER IF EXISTS update_menu_actions_updated_at ON menu_actions;
|
||||||
|
CREATE TRIGGER update_menu_actions_updated_at
|
||||||
|
BEFORE UPDATE ON menu_actions
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- Apply trigger to user_level_menu_accesses
|
||||||
|
DROP TRIGGER IF EXISTS update_user_level_menu_accesses_updated_at ON user_level_menu_accesses;
|
||||||
|
CREATE TRIGGER update_user_level_menu_accesses_updated_at
|
||||||
|
BEFORE UPDATE ON user_level_menu_accesses
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- Apply trigger to user_level_menu_action_accesses
|
||||||
|
DROP TRIGGER IF EXISTS update_user_level_menu_action_accesses_updated_at ON user_level_menu_action_accesses;
|
||||||
|
CREATE TRIGGER update_user_level_menu_action_accesses_updated_at
|
||||||
|
BEFORE UPDATE ON user_level_menu_action_accesses
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Verification
|
||||||
|
-- ============================================================================
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
v_menu_actions_exists BOOLEAN;
|
||||||
|
v_user_level_menu_accesses_exists BOOLEAN;
|
||||||
|
v_user_level_menu_action_accesses_exists BOOLEAN;
|
||||||
|
BEGIN
|
||||||
|
-- Check menu_actions table
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'menu_actions'
|
||||||
|
) INTO v_menu_actions_exists;
|
||||||
|
|
||||||
|
-- Check user_level_menu_accesses table
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'user_level_menu_accesses'
|
||||||
|
) INTO v_user_level_menu_accesses_exists;
|
||||||
|
|
||||||
|
-- Check user_level_menu_action_accesses table
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'user_level_menu_action_accesses'
|
||||||
|
) INTO v_user_level_menu_action_accesses_exists;
|
||||||
|
|
||||||
|
-- Report results
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
RAISE NOTICE 'Migration Verification Results:';
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
|
||||||
|
IF v_menu_actions_exists THEN
|
||||||
|
RAISE NOTICE '✓ Table menu_actions created successfully';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '✗ Table menu_actions NOT created';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_user_level_menu_accesses_exists THEN
|
||||||
|
RAISE NOTICE '✓ Table user_level_menu_accesses created successfully';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '✗ Table user_level_menu_accesses NOT created';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_user_level_menu_action_accesses_exists THEN
|
||||||
|
RAISE NOTICE '✓ Table user_level_menu_action_accesses created successfully';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '✗ Table user_level_menu_action_accesses NOT created';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
|
||||||
|
IF v_menu_actions_exists AND v_user_level_menu_accesses_exists AND v_user_level_menu_action_accesses_exists THEN
|
||||||
|
RAISE NOTICE 'Migration completed successfully! ✓';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Migration completed with errors! Please check above.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RAISE NOTICE '========================================';
|
||||||
|
END $$;
|
||||||
|
|
||||||
2960
docs/swagger/docs.go
2960
docs/swagger/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6
main.go
6
main.go
|
|
@ -27,6 +27,9 @@ import (
|
||||||
"netidhub-saas-be/app/module/magazines"
|
"netidhub-saas-be/app/module/magazines"
|
||||||
"netidhub-saas-be/app/module/master_menus"
|
"netidhub-saas-be/app/module/master_menus"
|
||||||
"netidhub-saas-be/app/module/master_modules"
|
"netidhub-saas-be/app/module/master_modules"
|
||||||
|
"netidhub-saas-be/app/module/menu_actions"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_accesses"
|
||||||
|
"netidhub-saas-be/app/module/user_level_menu_action_accesses"
|
||||||
"netidhub-saas-be/app/module/provinces"
|
"netidhub-saas-be/app/module/provinces"
|
||||||
"netidhub-saas-be/app/module/schedules"
|
"netidhub-saas-be/app/module/schedules"
|
||||||
"netidhub-saas-be/app/module/subscription"
|
"netidhub-saas-be/app/module/subscription"
|
||||||
|
|
@ -93,6 +96,9 @@ func main() {
|
||||||
magazine_files.NewMagazineFilesModule,
|
magazine_files.NewMagazineFilesModule,
|
||||||
master_menus.NewMasterMenusModule,
|
master_menus.NewMasterMenusModule,
|
||||||
master_modules.NewMasterModulesModule,
|
master_modules.NewMasterModulesModule,
|
||||||
|
menu_actions.NewMenuActionsModule,
|
||||||
|
user_level_menu_accesses.NewUserLevelMenuAccessesModule,
|
||||||
|
user_level_menu_action_accesses.NewUserLevelMenuActionAccessesModule,
|
||||||
provinces.NewProvincesModule,
|
provinces.NewProvincesModule,
|
||||||
schedules.NewSchedulesModule,
|
schedules.NewSchedulesModule,
|
||||||
subscription.NewSubscriptionModule,
|
subscription.NewSubscriptionModule,
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue