342 lines
8.8 KiB
Go
342 lines
8.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.haelnorr.com/h/golib/hws"
|
|
"git.haelnorr.com/h/oslstats/internal/db"
|
|
"git.haelnorr.com/h/oslstats/internal/respond"
|
|
"git.haelnorr.com/h/oslstats/internal/roles"
|
|
"git.haelnorr.com/h/oslstats/internal/validation"
|
|
adminview "git.haelnorr.com/h/oslstats/internal/view/adminview"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
// AdminRoles renders the full admin dashboard page with roles section
|
|
func AdminRoles(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rolesList *db.List[db.Role]
|
|
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
rolesList, err = db.GetRoles(ctx, tx, pageOpts)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetRoles")
|
|
}
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
if r.Method == "GET" {
|
|
renderSafely(adminview.RolesPage(rolesList), s, r, w)
|
|
} else {
|
|
renderSafely(adminview.RolesList(rolesList), s, r, w)
|
|
}
|
|
})
|
|
}
|
|
|
|
// AdminRoleCreateForm shows the create role form modal
|
|
func AdminRoleCreateForm(s *hws.Server) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
renderSafely(adminview.RoleCreateForm(), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminRoleCreate creates a new role
|
|
func AdminRoleCreate(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
getter, ok := validation.ParseFormOrNotify(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
name := getter.String("name").Required().Value
|
|
displayName := getter.String("display_name").Required().Value
|
|
description := getter.String("description").Value
|
|
|
|
if !getter.ValidateAndNotify(s, w, r) {
|
|
return
|
|
}
|
|
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rolesList *db.List[db.Role]
|
|
var newRole *db.Role
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
newRole = &db.Role{
|
|
Name: roles.Role(name),
|
|
DisplayName: displayName,
|
|
Description: description,
|
|
IsSystem: false,
|
|
CreatedAt: time.Now().Unix(),
|
|
}
|
|
|
|
err := db.CreateRole(ctx, tx, newRole, db.NewAudit(r, nil))
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.CreateRole")
|
|
}
|
|
|
|
rolesList, err = db.GetRoles(ctx, tx, pageOpts)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetRoles")
|
|
}
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
renderSafely(adminview.RolesList(rolesList), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminRoleManage shows the role management modal with details and actions
|
|
func AdminRoleManage(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
roleIDStr := r.PathValue("id")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil {
|
|
respond.BadRequest(w, err)
|
|
return
|
|
}
|
|
|
|
var role *db.Role
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
role, err = db.GetRoleByID(ctx, tx, roleID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, err)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetRoleByID")
|
|
}
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
renderSafely(adminview.RoleManageModal(role), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminRoleDeleteConfirm shows the delete confirmation dialog
|
|
func AdminRoleDeleteConfirm(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
roleIDStr := r.PathValue("id")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var role *db.Role
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
role, err = db.GetRoleByID(ctx, tx, roleID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, err)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetRoleByID")
|
|
}
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
renderSafely(adminview.ConfirmDeleteRole(roleID, role.DisplayName), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminRoleDelete deletes a role
|
|
func AdminRoleDelete(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
roleIDStr := r.PathValue("id")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil {
|
|
respond.BadRequest(w, err)
|
|
return
|
|
}
|
|
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rolesList *db.List[db.Role]
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
// First check if role exists and get its details
|
|
role, err := db.GetRoleByID(ctx, tx, roleID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, err)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetRoleByID")
|
|
}
|
|
|
|
// Check if it's a system role
|
|
if role.IsSystem {
|
|
return false, errors.New("cannot delete system roles")
|
|
}
|
|
|
|
// Delete the role with audit logging
|
|
err = db.DeleteRole(ctx, tx, roleID, db.NewAudit(r, nil))
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, err)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.DeleteRole")
|
|
}
|
|
|
|
// Reload roles
|
|
rolesList, err = db.GetRoles(ctx, tx, pageOpts)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetRoles")
|
|
}
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
renderSafely(adminview.RolesList(rolesList), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminRolePermissionsModal shows the permissions management modal for a role
|
|
func AdminRolePermissionsModal(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
roleIDStr := r.PathValue("id")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil {
|
|
respond.BadRequest(w, err)
|
|
return
|
|
}
|
|
|
|
var role *db.Role
|
|
var allPermissions []*db.Permission
|
|
var groupedPerms []adminview.PermissionsByResource
|
|
var rolePermIDs map[int]bool
|
|
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
// Load role with permissions
|
|
var err error
|
|
role, err = db.GetRoleByID(ctx, tx, roleID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, err)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetRoleByID")
|
|
}
|
|
|
|
// Load all permissions
|
|
allPermissions, err = db.ListAllPermissions(ctx, tx)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.ListAllPermissions")
|
|
}
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
// Group permissions by resource
|
|
permsByResource := make(map[string][]*db.Permission)
|
|
for _, perm := range allPermissions {
|
|
permsByResource[perm.Resource] = append(permsByResource[perm.Resource], perm)
|
|
}
|
|
|
|
// Convert to sorted slice
|
|
for resource, perms := range permsByResource {
|
|
groupedPerms = append(groupedPerms, adminview.PermissionsByResource{
|
|
Resource: resource,
|
|
Permissions: perms,
|
|
})
|
|
}
|
|
sort.Slice(groupedPerms, func(i, j int) bool {
|
|
return groupedPerms[i].Resource < groupedPerms[j].Resource
|
|
})
|
|
|
|
// Create map of current role permissions for checkbox state
|
|
rolePermIDs = make(map[int]bool)
|
|
for _, perm := range role.Permissions {
|
|
rolePermIDs[perm.ID] = true
|
|
}
|
|
|
|
renderSafely(adminview.RolePermissionsModal(role, groupedPerms, rolePermIDs), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminRolePermissionsUpdate updates the permissions for a role
|
|
func AdminRolePermissionsUpdate(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
roleIDStr := r.PathValue("id")
|
|
roleID, err := strconv.Atoi(roleIDStr)
|
|
if err != nil {
|
|
respond.BadRequest(w, err)
|
|
return
|
|
}
|
|
|
|
getter, ok := validation.ParseFormOrNotify(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Get selected permission IDs from form
|
|
permissionIDs := getter.IntList("permission_ids").Values()
|
|
if !getter.ValidateAndNotify(s, w, r) {
|
|
return
|
|
}
|
|
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rolesList *db.List[db.Role]
|
|
if ok := conn.WithWriteTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
role, err := db.GetRoleByID(ctx, tx, roleID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, err)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetRoleByID")
|
|
}
|
|
err = role.UpdatePermissions(ctx, tx, permissionIDs, db.NewAudit(r, nil))
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "role.UpdatePermissions")
|
|
}
|
|
|
|
// Reload roles
|
|
rolesList, err = db.GetRoles(ctx, tx, pageOpts)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetRoles")
|
|
}
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
renderSafely(adminview.RolesList(rolesList), s, r, w)
|
|
})
|
|
}
|