Files
bssapp/svc/permissions/repository.go
2026-02-11 17:46:22 +03:00

602 lines
10 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package permissions
import (
"database/sql"
"log"
"strings"
"github.com/lib/pq"
)
type PermissionRepository struct {
DB *sql.DB
}
func NewPermissionRepository(db *sql.DB) *PermissionRepository {
return &PermissionRepository{DB: db}
}
/* =====================================================
MATRIX READ (V2) - ROLE BASED
===================================================== */
func (r *PermissionRepository) GetPermissionMatrixForRoles(
roleIDs []int,
) ([]PermissionMatrixRow, error) {
if len(roleIDs) == 0 {
return []PermissionMatrixRow{}, nil
}
query := `
SELECT
rp.role_id,
rol.code,
rp.module_code,
rp.action,
rp.allowed
FROM mk_sys_role_permissions rp
JOIN dfrole rol ON rol.id = rp.role_id
WHERE rp.role_id = ANY($1)
ORDER BY rol.id, rp.module_code, rp.action
`
rows, err := r.DB.Query(query, pq.Array(roleIDs))
if err != nil {
return nil, err
}
defer rows.Close()
list := make([]PermissionMatrixRow, 0)
for rows.Next() {
var row PermissionMatrixRow
if err := rows.Scan(
&row.RoleID,
&row.RoleCode,
&row.Module,
&row.Action,
&row.Allowed,
); err != nil {
return nil, err
}
row.Source = "role"
list = append(list, row)
}
return list, nil
}
/* =====================================================
MATRIX UPDATE (V2) - ROLE BASED
===================================================== */
func (r *PermissionRepository) UpdatePermissions(
list []PermissionUpdateRequest,
) error {
tx, err := r.DB.Begin()
if err != nil {
return err
}
defer tx.Rollback()
stmt, err := tx.Prepare(`
INSERT INTO mk_sys_role_permissions
(role_id, module_code, action, allowed)
VALUES ($1,$2,$3,$4)
ON CONFLICT (role_id, module_code, action)
DO UPDATE SET allowed = EXCLUDED.allowed
`)
if err != nil {
return err
}
defer stmt.Close()
for _, p := range list {
if _, err := stmt.Exec(
p.RoleID,
p.Module,
p.Action,
p.Allowed,
); err != nil {
return err
}
}
return tx.Commit()
}
/* =====================================================
USER OVERRIDES - READ
GET /api/users/{id}/permissions
===================================================== */
// Tek tip: PermissionMatrixRow döndürüyoruz (source=user)
func (r *PermissionRepository) GetUserOverridesByUserID(
userID int64,
) ([]PermissionMatrixRow, error) {
rows, err := r.DB.Query(`
SELECT
user_id,
module_code,
action,
allowed
FROM mk_sys_user_permissions
WHERE user_id = $1
ORDER BY module_code, action
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
list := make([]PermissionMatrixRow, 0)
for rows.Next() {
var row PermissionMatrixRow
if err := rows.Scan(
&row.UserID,
&row.Module,
&row.Action,
&row.Allowed,
); err != nil {
return nil, err
}
row.Source = "user"
list = append(list, row)
}
return list, nil
}
/* =====================================================
USER OVERRIDES - UPSERT SAVE (typed)
POST /api/users/{id}/permissions
===================================================== */
func (r *PermissionRepository) SaveUserOverrides(
userID int64,
list []UserPermissionRequest,
) error {
log.Println("➡️ REPO SaveUserOverrides START")
log.Println("USER:", userID)
log.Println("ROWS:", len(list))
tx, err := r.DB.Begin()
if err != nil {
log.Println("❌ TX BEGIN ERROR:", err)
return err
}
defer tx.Rollback()
// önce sil
_, err = tx.Exec(`
DELETE FROM mk_sys_user_permissions
WHERE user_id = $1
`, userID)
if err != nil {
log.Println("❌ DELETE ERROR:", err)
return err
}
stmt, err := tx.Prepare(`
INSERT INTO mk_sys_user_permissions
(user_id, module_code, action, allowed)
VALUES ($1,$2,$3,$4)
`)
if err != nil {
log.Println("❌ PREPARE ERROR:", err)
return err
}
defer stmt.Close()
for _, p := range list {
if strings.TrimSpace(p.Module) == "" {
log.Printf("⚠️ SKIP EMPTY MODULE user=%d action=%s",
userID,
p.Action,
)
continue
}
_, err := stmt.Exec(
userID,
p.Module,
p.Action,
p.Allowed,
)
if err != nil {
log.Println("❌ INSERT ERROR:", err)
return err
}
}
if err := tx.Commit(); err != nil {
log.Println("❌ COMMIT ERROR:", err)
return err
}
log.Println("✅ REPO SaveUserOverrides DONE")
return nil
}
/* =====================================================
RESOLUTION HELPERS (middleware için)
===================================================== */
// user override var mı? varsa *bool döner, yoksa nil
func (r *PermissionRepository) HasUserOverride(
userID int64,
module string,
action string,
) (*bool, error) {
var allowed bool
err := r.DB.QueryRow(`
SELECT allowed
FROM mk_sys_user_permissions
WHERE user_id=$1
AND module_code=$2
AND action=$3
`,
userID,
module,
action,
).Scan(&allowed)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &allowed, nil
}
// roleIDs içinden OR logic: herhangi biri allowed=true ise true
func (r *PermissionRepository) HasRoleAccess(
roleIDs []int,
module string,
action string,
) (bool, error) {
if len(roleIDs) == 0 {
return false, nil
}
var count int
err := r.DB.QueryRow(`
SELECT COUNT(*)
FROM mk_sys_role_permissions
WHERE role_id = ANY($1)
AND module_code=$2
AND action=$3
AND allowed=true
`,
pq.Array(roleIDs),
module,
action,
).Scan(&count)
if err != nil {
return false, err
}
return count > 0, nil
}
// Final decision: user override varsa onu uygula, yoksa role bazlı
func (r *PermissionRepository) ResolvePermission(
userID int64,
roleIDs []int,
module string,
action string,
) (bool, error) {
override, err := r.HasUserOverride(userID, module, action)
if err != nil {
return false, err
}
if override != nil {
return *override, nil
}
return r.HasRoleAccess(roleIDs, module, action)
}
func (r *PermissionRepository) GetUserOverrides(
userID int64,
) ([]UserPermissionOverride, error) {
rows, err := r.DB.Query(`
SELECT module_code, action, allowed
FROM mk_sys_user_permissions
WHERE user_id = $1
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var list []UserPermissionOverride
for rows.Next() {
var o UserPermissionOverride
if err := rows.Scan(
&o.Module,
&o.Action,
&o.Allowed,
); err != nil {
return nil, err
}
list = append(list, o)
}
return list, nil
}
func (r *PermissionRepository) UpdateUserOverrides(
list []UserPermission,
) error {
tx, err := r.DB.Begin()
if err != nil {
return err
}
defer tx.Rollback()
stmt, err := tx.Prepare(`
INSERT INTO mk_sys_user_permissions
(user_id, module_code, action, allowed)
VALUES ($1,$2,$3,$4)
ON CONFLICT (user_id, module_code, action)
DO UPDATE SET allowed = EXCLUDED.allowed
`)
if err != nil {
return err
}
defer stmt.Close()
for _, p := range list {
_, err := stmt.Exec(
p.UserID,
p.Module,
p.Action,
p.Allowed,
)
if err != nil {
return err
}
}
return tx.Commit()
}
func (r *PermissionRepository) ResolveEffectivePermission(
userID int64,
roleID int64,
deptCode string,
module string,
action string,
) (bool, error) {
// 1⃣ USER OVERRIDE
var allowed bool
err := r.DB.QueryRow(`
SELECT allowed
FROM mk_sys_user_permissions
WHERE user_id = $1
AND module_code = $2
AND action = $3
`,
userID, module, action,
).Scan(&allowed)
if err == nil {
return allowed, nil
}
if err != sql.ErrNoRows {
return false, err
}
// ==================================================
// 2⃣ ROLE + DEPARTMENT
// ==================================================
if len(deptCode) > 0 {
var allowed bool
err = r.DB.QueryRow(` -- 🔥 := DEĞİL =
SELECT allowed
FROM vw_role_dept_permissions
WHERE role_id = $1
AND department_code = ANY($2)
AND module_code = $3
AND action = $4
ORDER BY allowed DESC
LIMIT 1
`,
roleID,
pq.Array([]string{deptCode}),
module,
action,
).Scan(&allowed)
if err == nil {
log.Printf(
" ↳ ROLE+DEPT OVERRIDE = %v",
allowed,
)
return allowed, nil
}
if err != sql.ErrNoRows {
log.Println("❌ ROLE+DEPT ERR:", err)
return false, err
}
}
// 3⃣ ROLE DEFAULT
err = r.DB.QueryRow(`
SELECT allowed
FROM mk_sys_role_permissions
WHERE role_id = $1
AND module_code = $2
AND action = $3
`,
roleID, module, action,
).Scan(&allowed)
if err == nil {
return allowed, nil
}
if err != sql.ErrNoRows {
return false, err
}
// 4⃣ DENY
return false, nil
}
func (r *PermissionRepository) ResolvePermissionChain(
userID int64,
roleID int64,
deptCodes []string,
module string,
action string,
) (bool, error) {
log.Printf(
"🔐 PERM CHECK user=%d role=%d dept=%v %s:%s",
userID,
roleID,
deptCodes,
module,
action,
)
// ==================================================
// 1⃣ USER OVERRIDE
// ==================================================
override, err := r.HasUserOverride(userID, module, action)
if err != nil {
log.Println("❌ USER OVERRIDE ERR:", err)
return false, err
}
if override != nil {
log.Printf(
" ↳ USER OVERRIDE = %v",
*override,
)
return *override, nil
}
// ==================================================
// 2⃣ ROLE + DEPARTMENT
// ==================================================
if len(deptCodes) > 0 {
var allowed bool
err := r.DB.QueryRow(`
SELECT allowed
FROM vw_role_dept_permissions
WHERE role_id = $1
AND department_code IN (
SELECT UNNEST($2::text[])
)
AND module_code = $3
AND action = $4
ORDER BY allowed DESC
LIMIT 1
`,
roleID,
pq.Array(deptCodes),
module,
action,
).Scan(&allowed)
if err == nil {
log.Printf(
" ↳ ROLE+DEPT OVERRIDE = %v",
allowed,
)
return allowed, nil
}
if err != sql.ErrNoRows {
log.Println("❌ ROLE+DEPT ERR:", err)
return false, err
}
}
// ==================================================
// 3⃣ ROLE DEFAULT
// ==================================================
var roleAllowed bool
err = r.DB.QueryRow(`
SELECT allowed
FROM mk_sys_role_permissions
WHERE role_id = $1
AND module_code = $2
AND action = $3
`,
roleID,
module,
action,
).Scan(&roleAllowed)
if err == nil {
log.Printf(
" ↳ ROLE DEFAULT = %v",
roleAllowed,
)
return roleAllowed, nil
}
if err != sql.ErrNoRows {
log.Println("❌ ROLE DEFAULT ERR:", err)
return false, err
}
// ==================================================
// 4⃣ DENY
// ==================================================
log.Println(" ↳ NO RULE → DENY")
return false, nil
}