Yimaru-BackEnd/internal/services/rbac/service.go

166 lines
4.7 KiB
Go

package rbac
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"context"
"fmt"
"log/slog"
"sync/atomic"
)
type snapshot struct {
rolePerms map[string]map[string]struct{} // roleName -> set of permissionKeys
}
type Service struct {
store ports.RBACStore
cache atomic.Value // holds *snapshot
logger *slog.Logger
}
func NewService(store ports.RBACStore, logger *slog.Logger) *Service {
s := &Service{
store: store,
logger: logger,
}
// Initialize with empty snapshot
s.cache.Store(&snapshot{rolePerms: make(map[string]map[string]struct{})})
return s
}
// HasPermission checks if a role has a specific permission key.
// SUPER_ADMIN always returns true.
func (s *Service) HasPermission(roleName, permKey string) bool {
if roleName == string(domain.RoleSuperAdmin) {
return true
}
snap := s.cache.Load().(*snapshot)
perms, ok := snap.rolePerms[roleName]
if !ok {
return false
}
_, has := perms[permKey]
return has
}
// Reload rebuilds the in-memory cache from the database.
func (s *Service) Reload(ctx context.Context) error {
rolePerms, err := s.store.GetAllRolesWithPermissions(ctx)
if err != nil {
return fmt.Errorf("rbac reload: %w", err)
}
s.cache.Store(&snapshot{rolePerms: rolePerms})
s.logger.Info("RBAC cache reloaded", "roles", len(rolePerms))
return nil
}
// SeedPermissions upserts all permission definitions into the database.
func (s *Service) SeedPermissions(ctx context.Context) error {
for _, p := range AllPermissions {
if _, err := s.store.UpsertPermission(ctx, p); err != nil {
return fmt.Errorf("seed permission %s: %w", p.Key, err)
}
}
s.logger.Info("RBAC permissions seeded", "count", len(AllPermissions))
return nil
}
// SeedDefaultRolePermissions assigns the default permission sets to each system role.
// It only assigns permissions if the role currently has zero permissions (first-time setup).
func (s *Service) SeedDefaultRolePermissions(ctx context.Context) error {
for roleName, permKeys := range DefaultRolePermissions {
role, err := s.store.GetRoleByName(ctx, roleName)
if err != nil {
s.logger.Warn("skipping role permission seed: role not found", "role", roleName)
continue
}
existing, err := s.store.GetRolePermissions(ctx, role.ID)
if err != nil {
return fmt.Errorf("check existing permissions for %s: %w", roleName, err)
}
if len(existing) > 0 {
s.logger.Info("role already has permissions, skipping seed", "role", roleName, "count", len(existing))
continue
}
var permIDs []int64
for _, key := range permKeys {
perm, err := s.store.GetPermissionByKey(ctx, key)
if err != nil {
s.logger.Warn("permission key not found during seed", "key", key, "role", roleName)
continue
}
permIDs = append(permIDs, perm.ID)
}
if len(permIDs) > 0 {
if err := s.store.SetRolePermissions(ctx, role.ID, permIDs); err != nil {
return fmt.Errorf("seed permissions for role %s: %w", roleName, err)
}
s.logger.Info("seeded default permissions for role", "role", roleName, "count", len(permIDs))
}
}
return nil
}
// --- Role CRUD (pass-through to store + reload cache) ---
func (s *Service) CreateRole(ctx context.Context, name, description string) (domain.RoleRecord, error) {
role, err := s.store.CreateRole(ctx, name, description, false)
if err != nil {
return domain.RoleRecord{}, err
}
_ = s.Reload(ctx)
return role, nil
}
func (s *Service) GetRoleByID(ctx context.Context, id int64) (domain.RoleRecord, error) {
return s.store.GetRoleByID(ctx, id)
}
func (s *Service) ListRoles(ctx context.Context, filter domain.RoleListFilter) ([]domain.RoleRecord, int64, error) {
return s.store.ListRoles(ctx, filter)
}
func (s *Service) UpdateRole(ctx context.Context, id int64, name, description string) error {
if err := s.store.UpdateRole(ctx, id, name, description); err != nil {
return err
}
_ = s.Reload(ctx)
return nil
}
func (s *Service) DeleteRole(ctx context.Context, id int64) error {
if err := s.store.DeleteRole(ctx, id); err != nil {
return err
}
_ = s.Reload(ctx)
return nil
}
// --- Permission queries ---
func (s *Service) ListPermissions(ctx context.Context) ([]domain.Permission, error) {
return s.store.ListPermissions(ctx)
}
func (s *Service) ListPermissionGroups(ctx context.Context) ([]string, error) {
return s.store.ListPermissionGroups(ctx)
}
// --- Role-Permission management ---
func (s *Service) SetRolePermissions(ctx context.Context, roleID int64, permissionIDs []int64) error {
if err := s.store.SetRolePermissions(ctx, roleID, permissionIDs); err != nil {
return err
}
_ = s.Reload(ctx)
return nil
}
func (s *Service) GetRolePermissions(ctx context.Context, roleID int64) ([]domain.Permission, error) {
return s.store.GetRolePermissions(ctx, roleID)
}