Files
digital-psychology/apps/api/internal/repository/admin_rbac_repo.go
T
jackyu66gitandCursor b5a05941d9 feat(ECR-013A): Admin RBAC 实现并 Closed
角色权限、RequirePermission、/me permissions 与 migration 000015;
Reviewer Approve → Closed。Next:ECR-013B Contract Definition。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 17:19:45 +08:00

132 lines
3.4 KiB
Go

package repository
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// AdminRole is an ops RBAC role.
type AdminRole struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
System bool `json:"system"`
CreatedAt time.Time `json:"created_at"`
}
// ListAdminRoles returns all roles.
func (r *AdminRepo) ListAdminRoles(ctx context.Context) ([]AdminRole, error) {
rows, err := r.Pool.Query(ctx, `
SELECT id, name, system, created_at FROM admin_roles ORDER BY name`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []AdminRole
for rows.Next() {
var a AdminRole
if err := rows.Scan(&a.ID, &a.Name, &a.System, &a.CreatedAt); err != nil {
return nil, err
}
out = append(out, a)
}
return out, rows.Err()
}
// GetAdminRole loads one role.
func (r *AdminRepo) GetAdminRole(ctx context.Context, id uuid.UUID) (*AdminRole, error) {
var a AdminRole
err := r.Pool.QueryRow(ctx, `
SELECT id, name, system, created_at FROM admin_roles WHERE id=$1`, id,
).Scan(&a.ID, &a.Name, &a.System, &a.CreatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, err
}
return &a, nil
}
// ListRolePermissions returns permission codes for a role.
func (r *AdminRepo) ListRolePermissions(ctx context.Context, roleID uuid.UUID) ([]string, error) {
rows, err := r.Pool.Query(ctx, `
SELECT code FROM admin_role_permissions WHERE role_id=$1 ORDER BY code`, roleID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []string
for rows.Next() {
var c string
if err := rows.Scan(&c); err != nil {
return nil, err
}
out = append(out, c)
}
return out, rows.Err()
}
// ReplaceRolePermissions replaces the full permission set for a role.
func (r *AdminRepo) ReplaceRolePermissions(ctx context.Context, roleID uuid.UUID, codes []string) error {
tx, err := r.Pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
if _, err := tx.Exec(ctx, `DELETE FROM admin_role_permissions WHERE role_id=$1`, roleID); err != nil {
return err
}
for _, code := range codes {
if _, err := tx.Exec(ctx, `
INSERT INTO admin_role_permissions(role_id, code) VALUES ($1,$2)`, roleID, code); err != nil {
return err
}
}
return tx.Commit(ctx)
}
// ListPermissionsForAdmin returns permission codes for an admin account.
func (r *AdminRepo) ListPermissionsForAdmin(ctx context.Context, adminID uuid.UUID) ([]string, error) {
rows, err := r.Pool.Query(ctx, `
SELECT p.code
FROM admin_accounts a
JOIN admin_role_permissions p ON p.role_id = a.role_id
WHERE a.id=$1 AND a.deleted_at IS NULL AND a.role_id IS NOT NULL
ORDER BY p.code`, adminID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []string
for rows.Next() {
var c string
if err := rows.Scan(&c); err != nil {
return nil, err
}
out = append(out, c)
}
return out, rows.Err()
}
// GetAdminRoleMeta returns role id/name for an account.
func (r *AdminRepo) GetAdminRoleMeta(ctx context.Context, adminID uuid.UUID) (roleID *uuid.UUID, name string, err error) {
var id uuid.UUID
err = r.Pool.QueryRow(ctx, `
SELECT r.id, r.name
FROM admin_accounts a
JOIN admin_roles r ON r.id = a.role_id
WHERE a.id=$1 AND a.deleted_at IS NULL`, adminID,
).Scan(&id, &name)
if errors.Is(err, pgx.ErrNoRows) {
return nil, "", nil
}
if err != nil {
return nil, "", err
}
return &id, name, nil
}