Files
NewSzxcn-Email/apps/api/internal/app/permission_group_handlers.go
T
LanQin_ 31435c1e97 feat(admin): 引入细粒度权限组管理
- 新增权限目录、系统权限组与用户权限组分配,后台路由改为按权限粒度控制。
- 支持创建、编辑、查看和删除自定义权限组,并为用户绑定权限组。
- 前端后台界面按权限动态展示菜单与操作项,补充用户/权限组相关类型与交互。
2026-06-22 14:31:10 +08:00

202 lines
6.7 KiB
Go

package app
import (
"errors"
"net/http"
"strings"
"time"
"github.com/go-chi/chi/v5"
)
func (a *App) handlePermissionCatalog(w http.ResponseWriter, r *http.Request) {
respondJSON(w, http.StatusOK, map[string]any{"items": permissionCatalog()})
}
func (a *App) handleListPermissionGroups(w http.ResponseWriter, r *http.Request) {
rows, err := a.db.QueryContext(r.Context(), `SELECT id,name,description,permissions_json,system,created_at,updated_at
FROM permission_groups
ORDER BY created_at ASC,name ASC`)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to list permission groups")
return
}
items := []PermissionGroup{}
for rows.Next() {
var item PermissionGroup
var raw, created, updated string
var system int
if err := rows.Scan(&item.ID, &item.Name, &item.Description, &raw, &system, &created, &updated); err != nil {
rows.Close()
respondError(w, http.StatusInternalServerError, "failed to scan permission groups")
return
}
item.Permissions = decodeStoredPermissions(raw)
item.System = intBool(system)
item.CreatedAt = parseTime(created)
item.UpdatedAt = parseTime(updated)
items = append(items, item)
}
if err := rows.Err(); err != nil {
rows.Close()
respondError(w, http.StatusInternalServerError, "failed to list permission groups")
return
}
if err := rows.Close(); err != nil {
respondError(w, http.StatusInternalServerError, "failed to list permission groups")
return
}
var adminCount, regularCount int
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM users WHERE role='admin'`).Scan(&adminCount); err != nil {
respondError(w, http.StatusInternalServerError, "failed to count users")
return
}
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM users u
WHERE u.role<>'admin'
AND NOT EXISTS (
SELECT 1 FROM user_permission_groups upg
WHERE upg.user_id=u.id AND upg.group_id NOT IN (?,?)
)`, PermissionGroupSuperAdmin, PermissionGroupRegular,
).Scan(&regularCount); err != nil {
respondError(w, http.StatusInternalServerError, "failed to count users")
return
}
for i := range items {
switch items[i].ID {
case PermissionGroupSuperAdmin:
items[i].UserCount = adminCount
case PermissionGroupRegular:
items[i].UserCount = regularCount
default:
var count int
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM user_permission_groups WHERE group_id=?`, items[i].ID).Scan(&count); err != nil {
respondError(w, http.StatusInternalServerError, "failed to count users")
return
}
items[i].UserCount = count
}
}
sortPermissionGroups(items)
respondJSON(w, http.StatusOK, map[string]any{"items": items, "catalog": permissionCatalog()})
}
func (a *App) handleCreatePermissionGroup(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Description string `json:"description"`
Permissions []string `json:"permissions"`
}
if err := decodeJSON(r, &req); err != nil {
badRequest(w, err)
return
}
name := strings.TrimSpace(req.Name)
if name == "" {
badRequest(w, errors.New("name is required"))
return
}
permissions, err := normalizePermissionList(req.Permissions)
if err != nil {
badRequest(w, err)
return
}
if !actorCanGrantPermissions(currentUser(r), permissions) {
respondError(w, http.StatusForbidden, "cannot grant permissions you do not hold")
return
}
id := newID("pg")
now := a.now().UTC().Format(time.RFC3339Nano)
if _, err := a.db.ExecContext(r.Context(), `INSERT INTO permission_groups(id,name,description,permissions_json,system,created_at,updated_at)
VALUES(?,?,?,?,0,?,?)`, id, name, strings.TrimSpace(req.Description), encodePermissions(permissions), now, now); err != nil {
badRequest(w, err)
return
}
group, err := a.permissionGroupByID(r.Context(), id)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to load permission group")
return
}
respondJSON(w, http.StatusCreated, group)
}
func (a *App) handleUpdatePermissionGroup(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var existingSystem int
if err := a.db.QueryRowContext(r.Context(), `SELECT system FROM permission_groups WHERE id=?`, id).Scan(&existingSystem); err != nil {
respondError(w, http.StatusNotFound, "permission group not found")
return
}
if intBool(existingSystem) {
respondError(w, http.StatusForbidden, "system permission groups cannot be edited")
return
}
var req struct {
Name string `json:"name"`
Description string `json:"description"`
Permissions []string `json:"permissions"`
}
if err := decodeJSON(r, &req); err != nil {
badRequest(w, err)
return
}
name := strings.TrimSpace(req.Name)
if name == "" {
badRequest(w, errors.New("name is required"))
return
}
permissions, err := normalizePermissionList(req.Permissions)
if err != nil {
badRequest(w, err)
return
}
if !actorCanGrantPermissions(currentUser(r), permissions) {
respondError(w, http.StatusForbidden, "cannot grant permissions you do not hold")
return
}
if _, err := a.db.ExecContext(r.Context(), `UPDATE permission_groups SET name=?,description=?,permissions_json=?,updated_at=? WHERE id=?`,
name, strings.TrimSpace(req.Description), encodePermissions(permissions), a.now().UTC().Format(time.RFC3339Nano), id); err != nil {
badRequest(w, err)
return
}
group, err := a.permissionGroupByID(r.Context(), id)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to load permission group")
return
}
respondJSON(w, http.StatusOK, group)
}
func (a *App) handleDeletePermissionGroup(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var system int
if err := a.db.QueryRowContext(r.Context(), `SELECT system FROM permission_groups WHERE id=?`, id).Scan(&system); err != nil {
respondError(w, http.StatusNotFound, "permission group not found")
return
}
if intBool(system) {
respondError(w, http.StatusForbidden, "system permission groups cannot be deleted")
return
}
var userCount int
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM user_permission_groups WHERE group_id=?`, id).Scan(&userCount); err != nil {
respondError(w, http.StatusInternalServerError, "failed to check permission group")
return
}
if userCount > 0 {
badRequest(w, errors.New("permission group is assigned to users"))
return
}
res, err := a.db.ExecContext(r.Context(), `DELETE FROM permission_groups WHERE id=?`, id)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to delete permission group")
return
}
affected, _ := res.RowsAffected()
if affected == 0 {
respondError(w, http.StatusNotFound, "permission group not found")
return
}
respondJSON(w, http.StatusOK, map[string]any{"ok": true})
}