This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

83
svc/permissions/models.go Normal file
View File

@@ -0,0 +1,83 @@
package permissions
/* =====================================================
ROLE PERMISSION MATRIX (READ)
- Role bazlı matrix ekranı için: role_id + role_code + module/action/allowed
- UI isterse "source" alanı ile satırın nereden geldiğini gösterebilir.
===================================================== */
type PermissionMatrixRow struct {
// Role tarafı
RoleID int `json:"role_id,omitempty"`
RoleCode string `json:"role_code,omitempty"`
// User override tarafı
UserID int64 `json:"user_id,omitempty"`
// Ortak alanlar
Module string `json:"module"`
ModuleCode string `json:"module_code"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
// role | user
Source string `json:"source"`
}
/* =====================================================
ROLE PERMISSION UPDATE (WRITE)
- /api/permissions/matrix POST gibi update endpoint'lerinde kullanılır
===================================================== */
type PermissionUpdateRequest struct {
RoleID int `json:"role_id"`
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
/* =====================================================
USER OVERRIDE MODELS
- mk_sys_user_permissions tablosu için
===================================================== */
// DBden okurken döndüğümüz satır (GET /api/users/{id}/permissions)
type UserOverrideRow struct {
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
// Middleware içinde hızlı okuma için de kullanılabilir (GetUserOverrides)
type UserPermissionOverride struct {
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
// Repo “upsert/insert” gibi operasyonlarda kullanılacak internal model
type UserPermission struct {
UserID int64 `json:"user_id"`
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
// POST payload için tip (SaveUserOverrides input)
type UserPermissionRequest struct {
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
// Role + Department Permission Row
type RoleDepartmentPermission struct {
RoleID int `json:"role_id"`
DepartmentCode string `json:"department_code"`
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
Source string `json:"source"` // "role_dept"
}

View File

@@ -0,0 +1,601 @@
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
}

View File

@@ -0,0 +1,123 @@
package permissions
import (
"bssapp-backend/queries"
"database/sql"
"log"
)
type RoleDepartmentPermissionRepo struct {
db *sql.DB
}
func NewRoleDepartmentPermissionRepo(db *sql.DB) *RoleDepartmentPermissionRepo {
return &RoleDepartmentPermissionRepo{db: db}
}
/* ======================================================
GET
====================================================== */
func (r *RoleDepartmentPermissionRepo) Get(
roleID int,
deptCode string,
) ([]RoleDepartmentPermission, error) {
rows, err := r.db.Query(
queries.GetRoleDepartmentPermissions,
roleID,
deptCode,
)
if err != nil {
return nil, err
}
defer rows.Close()
var list []RoleDepartmentPermission
for rows.Next() {
var p RoleDepartmentPermission
if err := rows.Scan(
&p.Module,
&p.Action,
&p.Allowed,
); err != nil {
return nil, err
}
p.RoleID = roleID
p.DepartmentCode = deptCode
p.Source = "role_department"
list = append(list, p)
}
return list, nil
}
/* ======================================================
SAVE
====================================================== */
func (r *RoleDepartmentPermissionRepo) Save(
roleID int,
deptCode string,
list []RoleDepartmentPermission,
) error {
tx, err := r.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
stmt, err := tx.Prepare(
queries.UpsertRoleDepartmentPermission,
)
if err != nil {
return err
}
defer stmt.Close()
for _, p := range list {
if p.Module == "" {
log.Printf(
"⚠️ SKIP EMPTY MODULE role=%v dept=%v action=%v",
roleID,
deptCode,
p.Action,
)
continue
}
_, err := stmt.Exec(
roleID,
deptCode,
p.Module,
p.Action,
p.Allowed,
)
// 🔴🔴🔴 KRİTİK DEBUG LOG
if err != nil {
log.Printf(
"❌ UPSERT FAIL role=%v dept=%v module=%v action=%v allowed=%v err=%v",
roleID,
deptCode,
p.Module,
p.Action,
p.Allowed,
err,
)
return err
}
}
return tx.Commit()
}

View File

@@ -0,0 +1,33 @@
package permissions
// 🔥 SYSTEM / ADMIN ROLE IDS
var AdminRoleIDs = map[int]struct{}{
1: {},
3: {},
}
// -------------------------------------------------------
// 🧠 ROLE POLICY
// -------------------------------------------------------
func ResolveEffectiveRoles(
roleIDs []int,
) (effectiveRoleIDs []int, isAdmin bool) {
roleMap := make(map[int]struct{})
for _, roleID := range roleIDs {
// 🔥 SYSTEM / ADMIN OVERRIDE (1,3,…)
if _, ok := AdminRoleIDs[roleID]; ok {
return []int{roleID}, true
}
roleMap[roleID] = struct{}{}
}
for id := range roleMap {
effectiveRoleIDs = append(effectiveRoleIDs, id)
}
return effectiveRoleIDs, false
}

40
svc/permissions/seed.go Normal file
View File

@@ -0,0 +1,40 @@
package permissions
import "database/sql"
func SeedAdminRoleDepartments(db *sql.DB) error {
var adminID int
var err error
// Admin role id al
err = db.QueryRow(`
SELECT id
FROM dfrole
WHERE code = 'admin'
`).Scan(&adminID)
if err != nil {
return err
}
// Seed
_, err = db.Exec(`
INSERT INTO mk_sys_role_department_permissions
(role_id, department_code, module_code, action, allowed)
SELECT
$1,
d.code,
r.module_code,
r.action,
true
FROM mk_dprt d
CROSS JOIN mk_sys_routes r
ON CONFLICT (role_id, department_code, module_code, action)
DO NOTHING
`, adminID)
return err
}