package system import ( "errors" "strconv" "strings" platformmodule "kra/pkg/module" "gorm.io/gorm" ) // EnsureAdminSurface upgrades an already initialized installation with a // module's current menus, API metadata, root menu links, and root policies. // It intentionally does nothing before first-install seed data exists. func EnsureAdminSurface(db *gorm.DB, surface platformmodule.Surface, rootAuthorityID uint) error { if db == nil || !db.Migrator().HasTable(&menuPO{}) || !db.Migrator().HasTable(&apiPO{}) { return nil } return db.Transaction(func(tx *gorm.DB) error { parentIDs := map[string]uint{} for _, item := range surface.Menus { if item.ParentName == "" { continue } if _, ok := parentIDs[item.ParentName]; ok { continue } var parent menuPO if err := tx.Where("name = ?", item.ParentName).First(&parent).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } parentIDs[item.ParentName] = parent.ID } menus := make([]menuPO, 0, len(surface.Menus)) for _, item := range surface.Menus { menu := menuPO{ MenuLevel: 1, ParentID: parentIDs[item.ParentName], Path: item.Path, Name: item.Name, Component: item.Component, Title: item.Title, Icon: item.Icon, Sort: item.Sort, } if item.ParentName == "" { menu.MenuLevel = 0 } var current menuPO err := tx.Where("name = ?", item.Name).First(¤t).Error switch { case errors.Is(err, gorm.ErrRecordNotFound): if err = tx.Create(&menu).Error; err != nil { return err } case err != nil: return err default: if err = tx.Model(¤t).Updates(map[string]any{ "menu_level": menu.MenuLevel, "parent_id": menu.ParentID, "path": menu.Path, "component": menu.Component, "title": menu.Title, "icon": menu.Icon, "sort": menu.Sort, }).Error; err != nil { return err } menu.ID = current.ID } menus = append(menus, menu) } apis := make([]apiPO, 0, len(surface.APIs)) for _, item := range surface.APIs { api := apiPO{Path: item.Path, Method: strings.ToUpper(item.Method), APIGroup: item.Group, Description: item.Description} var current apiPO err := tx.Where("path = ? AND method = ?", api.Path, api.Method).First(¤t).Error switch { case errors.Is(err, gorm.ErrRecordNotFound): if err = tx.Create(&api).Error; err != nil { return err } case err != nil: return err default: if err = tx.Model(¤t).Updates(map[string]any{"api_group": api.APIGroup, "description": api.Description}).Error; err != nil { return err } api.ID = current.ID } apis = append(apis, api) } if rootAuthorityID == 0 || !tx.Migrator().HasTable(&authorityPO{}) || !tx.Migrator().HasTable(&authorityMenuPO{}) || !tx.Migrator().HasTable(&casbinRulePO{}) { return nil } var root authorityPO if err := tx.Where("authority_id = ?", rootAuthorityID).First(&root).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } for _, menu := range menus { link := authorityMenuPO{SysAuthorityAuthorityID: rootAuthorityID, SysBaseMenuID: menu.ID} var count int64 if err := tx.Model(&authorityMenuPO{}). Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", rootAuthorityID, menu.ID). Count(&count).Error; err != nil { return err } if count == 0 { if err := tx.Create(&link).Error; err != nil { return err } } } for _, api := range apis { exists, err := policyExists(tx, rootAuthorityID, api.Path, api.Method) if err != nil { return err } if !exists { rule := newPolicyRule(rootAuthorityID, api.Path, api.Method) if err := tx.Create(&rule).Error; err != nil { return err } } } return nil }) } // InheritAPIPolicy grants a new route only to authorities that already own // the supplied predecessor route. This preserves existing read-access intent // without broadening unrelated write permissions. func InheritAPIPolicy(db *gorm.DB, sourcePath, sourceMethod, targetPath, targetMethod string) error { if db == nil || !db.Migrator().HasTable(&casbinRulePO{}) { return nil } sourceMethod = strings.ToUpper(sourceMethod) targetMethod = strings.ToUpper(targetMethod) return db.Transaction(func(tx *gorm.DB) error { var rows []casbinRulePO if err := policyScope(tx).Where("v1 = ? AND v2 = ?", sourcePath, sourceMethod).Find(&rows).Error; err != nil { return err } for _, row := range rows { parsed, err := strconv.ParseUint(row.V0, 10, 64) if err != nil { continue } authorityID := uint(parsed) exists, err := policyExists(tx, authorityID, targetPath, targetMethod) if err != nil { return err } if !exists { rule := newPolicyRule(authorityID, targetPath, targetMethod) if err := tx.Create(&rule).Error; err != nil { return err } } } return nil }) } // GrantMenuAPIs makes a visible administration menu usable by granting only // its explicitly supplied API set to authorities that already own the menu. func GrantMenuAPIs(db *gorm.DB, menuName string, apis []platformmodule.API) error { if db == nil || !db.Migrator().HasTable(&menuPO{}) || !db.Migrator().HasTable(&authorityMenuPO{}) || !db.Migrator().HasTable(&casbinRulePO{}) { return nil } return db.Transaction(func(tx *gorm.DB) error { var menu menuPO if err := tx.Where("name = ?", menuName).First(&menu).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } var links []authorityMenuPO if err := tx.Where("sys_base_menu_id = ?", menu.ID).Find(&links).Error; err != nil { return err } for _, link := range links { for _, api := range apis { method := strings.ToUpper(api.Method) exists, err := policyExists(tx, link.SysAuthorityAuthorityID, api.Path, method) if err != nil { return err } if !exists { rule := newPolicyRule(link.SysAuthorityAuthorityID, api.Path, method) if err := tx.Create(&rule).Error; err != nil { return err } } } } return nil }) }