569 lines
13 KiB
Go
569 lines
13 KiB
Go
package routes
|
||
|
||
import (
|
||
"bssapp-backend/auth"
|
||
"bssapp-backend/internal/auditlog"
|
||
"bssapp-backend/permissions"
|
||
"bssapp-backend/queries"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/gorilla/mux"
|
||
)
|
||
|
||
type IdTitleOption struct {
|
||
ID string `json:"id"`
|
||
Title string `json:"title"`
|
||
}
|
||
type Row struct {
|
||
Route string `json:"route"`
|
||
CanAccess bool `json:"can_access"`
|
||
}
|
||
|
||
type RoleDeptPermissionSummary struct {
|
||
RoleID int `json:"role_id"`
|
||
RoleTitle string `json:"role_title"`
|
||
DepartmentCode string `json:"department_code"`
|
||
DepartmentTitle string `json:"department_title"`
|
||
ModuleFlags map[string]bool `json:"module_flags"`
|
||
}
|
||
|
||
type ModuleLookupOption struct {
|
||
Value string `json:"value"`
|
||
Label string `json:"label"`
|
||
}
|
||
|
||
type ModuleActionLookupOption struct {
|
||
ModuleCode string `json:"module_code"`
|
||
Action string `json:"action"`
|
||
}
|
||
|
||
type RoleDeptPermissionListResponse struct {
|
||
Modules []ModuleLookupOption `json:"modules"`
|
||
ModuleActions []ModuleActionLookupOption `json:"module_actions"`
|
||
Rows []RoleDeptPermissionSummary `json:"rows"`
|
||
}
|
||
|
||
type RoleDepartmentPermissionHandler struct {
|
||
DB *sql.DB
|
||
Repo *permissions.RoleDepartmentPermissionRepo
|
||
}
|
||
|
||
func NewRoleDepartmentPermissionHandler(db *sql.DB) *RoleDepartmentPermissionHandler {
|
||
|
||
return &RoleDepartmentPermissionHandler{
|
||
DB: db, // ✅ EKLENDİ
|
||
Repo: permissions.NewRoleDepartmentPermissionRepo(db),
|
||
}
|
||
}
|
||
|
||
/* ======================================================
|
||
LIST
|
||
====================================================== */
|
||
|
||
func (h *RoleDepartmentPermissionHandler) List(w http.ResponseWriter, r *http.Request) {
|
||
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || claims == nil {
|
||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
|
||
modRows, err := h.DB.Query(queries.GetModuleLookup)
|
||
if err != nil {
|
||
http.Error(w, "module lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer modRows.Close()
|
||
|
||
modules := make([]ModuleLookupOption, 0, 32)
|
||
for modRows.Next() {
|
||
var m ModuleLookupOption
|
||
if err := modRows.Scan(&m.Value, &m.Label); err != nil {
|
||
http.Error(w, "module lookup scan error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
modules = append(modules, m)
|
||
}
|
||
|
||
if err := modRows.Err(); err != nil {
|
||
http.Error(w, "module lookup rows error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
actionRows, err := h.DB.Query(queries.GetModuleActionLookup)
|
||
if err != nil {
|
||
http.Error(w, "module action lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer actionRows.Close()
|
||
|
||
moduleActions := make([]ModuleActionLookupOption, 0, 128)
|
||
for actionRows.Next() {
|
||
var a ModuleActionLookupOption
|
||
if err := actionRows.Scan(&a.ModuleCode, &a.Action); err != nil {
|
||
http.Error(w, "module action scan error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
moduleActions = append(moduleActions, a)
|
||
}
|
||
|
||
if err := actionRows.Err(); err != nil {
|
||
http.Error(w, "module action rows error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
rows, err := h.DB.Query(queries.ListRoleDepartmentPermissionSets, search)
|
||
if err != nil {
|
||
http.Error(w, "db error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]RoleDeptPermissionSummary, 0, 128)
|
||
for rows.Next() {
|
||
var item RoleDeptPermissionSummary
|
||
var rawFlags []byte
|
||
if err := rows.Scan(
|
||
&item.RoleID,
|
||
&item.RoleTitle,
|
||
&item.DepartmentCode,
|
||
&item.DepartmentTitle,
|
||
&rawFlags,
|
||
); err != nil {
|
||
http.Error(w, "scan error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
item.ModuleFlags = map[string]bool{}
|
||
if len(rawFlags) > 0 {
|
||
if err := json.Unmarshal(rawFlags, &item.ModuleFlags); err != nil {
|
||
http.Error(w, "module flags parse error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
list = append(list, item)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
http.Error(w, "rows error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
_ = json.NewEncoder(w).Encode(RoleDeptPermissionListResponse{
|
||
Modules: modules,
|
||
ModuleActions: moduleActions,
|
||
Rows: list,
|
||
})
|
||
}
|
||
|
||
/* ======================================================
|
||
GET
|
||
====================================================== */
|
||
|
||
func (h *RoleDepartmentPermissionHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || claims == nil {
|
||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
vars := mux.Vars(r)
|
||
|
||
roleID, err := strconv.Atoi(vars["roleId"])
|
||
if err != nil || roleID <= 0 {
|
||
http.Error(w, "invalid roleId", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
deptCode := vars["deptCode"]
|
||
if deptCode == "" {
|
||
http.Error(w, "invalid deptCode", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
list, err := h.Repo.Get(roleID, deptCode)
|
||
if err != nil {
|
||
http.Error(w, "db error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
|
||
/* ======================================================
|
||
POST
|
||
====================================================== */
|
||
|
||
func (h *RoleDepartmentPermissionHandler) Save(w http.ResponseWriter, r *http.Request) {
|
||
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || claims == nil {
|
||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
vars := mux.Vars(r)
|
||
|
||
roleID, err := strconv.Atoi(vars["roleId"])
|
||
if err != nil || roleID <= 0 {
|
||
http.Error(w, "invalid roleId", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
deptCode := vars["deptCode"]
|
||
if deptCode == "" {
|
||
http.Error(w, "invalid deptCode", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
var list []permissions.RoleDepartmentPermission
|
||
if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
|
||
http.Error(w, "bad payload", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// ================= OLD =================
|
||
oldRows, err := h.Repo.Get(roleID, deptCode)
|
||
if err != nil {
|
||
log.Println("OLD PERM LOAD ERROR:", err)
|
||
}
|
||
|
||
oldMap := map[string]bool{}
|
||
for _, p := range oldRows {
|
||
key := p.Module + ":" + p.Action
|
||
oldMap[key] = p.Allowed
|
||
}
|
||
|
||
// ================= DIFF =================
|
||
var changes []map[string]any
|
||
for _, p := range list {
|
||
key := p.Module + ":" + p.Action
|
||
oldVal := oldMap[key]
|
||
if oldVal != p.Allowed {
|
||
changes = append(changes, map[string]any{
|
||
"module": p.Module,
|
||
"action": p.Action,
|
||
"before": oldVal,
|
||
"after": p.Allowed,
|
||
})
|
||
}
|
||
}
|
||
|
||
// ================= SAVE =================
|
||
if err := h.Repo.Save(roleID, deptCode, list); err != nil {
|
||
http.Error(w, "save error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// ================= AUDIT =================
|
||
if len(changes) > 0 {
|
||
auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
|
||
ActionType: "role_department_permission_change",
|
||
ActionCategory: "role_permission",
|
||
ActionTarget: fmt.Sprintf(
|
||
"/api/roles/%d/departments/%s/permissions",
|
||
roleID,
|
||
deptCode,
|
||
),
|
||
Description: "role+department permissions updated",
|
||
Username: claims.Username,
|
||
RoleCode: claims.RoleCode,
|
||
DfUsrID: int64(claims.ID),
|
||
ChangeBefore: map[string]any{
|
||
"permissions": oldRows,
|
||
},
|
||
ChangeAfter: map[string]any{
|
||
"changes": changes,
|
||
},
|
||
IsSuccess: true,
|
||
})
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
_ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
|
||
}
|
||
func GetModuleLookupRoute(db *sql.DB) http.HandlerFunc {
|
||
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
rows, err := db.Query(queries.GetModuleLookup)
|
||
if err != nil {
|
||
http.Error(w, "db error", 500)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
type Row struct {
|
||
Value string `json:"value"`
|
||
Label string `json:"label"`
|
||
}
|
||
|
||
var list []Row
|
||
|
||
for rows.Next() {
|
||
|
||
var r Row
|
||
|
||
if err := rows.Scan(
|
||
&r.Value,
|
||
&r.Label,
|
||
); err != nil {
|
||
http.Error(w, "scan error", 500)
|
||
return
|
||
}
|
||
|
||
list = append(list, r)
|
||
}
|
||
|
||
json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
func GetRolesForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
rows, err := db.Query(queries.GetRolesForPermissionSelect)
|
||
if err != nil {
|
||
http.Error(w, "roles select error", 500)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]IdTitleOption, 0)
|
||
for rows.Next() {
|
||
var o IdTitleOption
|
||
if err := rows.Scan(&o.ID, &o.Title); err == nil {
|
||
list = append(list, o)
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
func GetDepartmentsForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
rows, err := db.Query(queries.GetDepartmentsForPermissionSelect)
|
||
if err != nil {
|
||
http.Error(w, "departments select error", 500)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]IdTitleOption, 0)
|
||
for rows.Next() {
|
||
var o IdTitleOption
|
||
if err := rows.Scan(&o.ID, &o.Title); err == nil {
|
||
list = append(list, o)
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
func GetUsersForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
rows, err := db.Query(queries.GetUserLookupForPermission)
|
||
if err != nil {
|
||
http.Error(w, "users lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]IdTitleOption, 0, 128)
|
||
|
||
for rows.Next() {
|
||
|
||
var o IdTitleOption
|
||
|
||
if err := rows.Scan(&o.ID, &o.Title); err == nil {
|
||
list = append(list, o)
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Request) {
|
||
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || claims == nil {
|
||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
userID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
||
if err != nil || userID <= 0 {
|
||
http.Error(w, "invalid id", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
list, err := h.Repo.GetUserOverridesByUserID(userID)
|
||
if err != nil {
|
||
log.Println("❌ USER OVERRIDE LOAD ERROR:", err)
|
||
http.Error(w, "db error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || claims == nil {
|
||
http.Error(w, "unauthorized", 401)
|
||
return
|
||
}
|
||
|
||
repo := permissions.NewPermissionRepository(db)
|
||
|
||
// JWT’den departmanlar
|
||
depts := claims.DepartmentCodes
|
||
|
||
rows, err := db.Query(`
|
||
SELECT DISTINCT
|
||
module_code,
|
||
action,
|
||
path
|
||
FROM mk_sys_routes
|
||
`)
|
||
if err != nil {
|
||
http.Error(w, err.Error(), 500)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
type Row struct {
|
||
Route string `json:"route"`
|
||
CanAccess bool `json:"can_access"`
|
||
}
|
||
|
||
list := make([]Row, 0, 64)
|
||
|
||
for rows.Next() {
|
||
|
||
var module, action, path string
|
||
|
||
if err := rows.Scan(
|
||
&module,
|
||
&action,
|
||
&path,
|
||
); err != nil {
|
||
continue
|
||
}
|
||
|
||
allowed, err := repo.ResolvePermissionChain(
|
||
int64(claims.ID),
|
||
int64(claims.RoleID),
|
||
depts,
|
||
module,
|
||
action,
|
||
)
|
||
|
||
if err != nil {
|
||
log.Println("PERM RESOLVE ERROR:", err)
|
||
continue
|
||
}
|
||
|
||
list = append(list, Row{
|
||
Route: path,
|
||
CanAccess: allowed,
|
||
})
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || claims == nil {
|
||
http.Error(w, "unauthorized", 401)
|
||
return
|
||
}
|
||
|
||
repo := permissions.NewPermissionRepository(db)
|
||
|
||
// ✅ JWT'DEN DEPARTMENTS
|
||
depts := claims.DepartmentCodes
|
||
|
||
log.Printf("🧪 EFFECTIVE PERM | user=%d role=%d depts=%v",
|
||
claims.ID,
|
||
claims.RoleID,
|
||
depts,
|
||
)
|
||
|
||
// all system perms
|
||
all, err := db.Query(`
|
||
SELECT DISTINCT module_code, action
|
||
FROM mk_sys_routes
|
||
`)
|
||
if err != nil {
|
||
http.Error(w, err.Error(), 500)
|
||
return
|
||
}
|
||
defer all.Close()
|
||
|
||
type Row struct {
|
||
Module string `json:"module"`
|
||
Action string `json:"action"`
|
||
Allowed bool `json:"allowed"`
|
||
}
|
||
|
||
list := make([]Row, 0, 128)
|
||
|
||
for all.Next() {
|
||
|
||
var m, a string
|
||
if err := all.Scan(&m, &a); err != nil {
|
||
continue
|
||
}
|
||
|
||
allowed, err := repo.ResolvePermissionChain(
|
||
int64(claims.ID),
|
||
int64(claims.RoleID),
|
||
depts,
|
||
m,
|
||
a,
|
||
)
|
||
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
list = append(list, Row{
|
||
Module: m,
|
||
Action: a,
|
||
Allowed: allowed,
|
||
})
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|