← Назад к вопросам

Как делать разные отчеты для разных ролей?

1.3 Junior🔥 151 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Управление отчетами для разных ролей пользователей

Создание разных отчетов для разных ролей требует контролируемого доступа как на фронтенде, так и на бэкенде. Это многоуровневый подход, объединяющий авторизацию, условную логику и фильтрацию данных.

Архитектурный подход

Frontend отвечает за:

  • Отображение доступных отчетов в UI
  • Скрытие компонентов, к которым нет доступа
  • Улучшенный UX для каждой роли

Backend отвечает за:

  • Валидацию разрешений перед отправкой данных
  • Фильтрацию данных согласно роли
  • Безопасность (главное!)

1. Role-Based Access Control (RBAC)

// contexts/AuthContext.ts
interface User {
  id: string;
  name: string;
  role: 'admin' | 'manager' | 'analyst' | 'viewer';
  permissions: Permission[];
}

type Permission = 'view_reports' | 'create_reports' | 'delete_reports' | 'export_reports';

interface AuthContextType {
  user: User | null;
  hasPermission: (permission: Permission) => boolean;
  hasRole: (role: User['role']) => boolean;
}

export const AuthContext = createContext<AuthContextType>({
  user: null,
  hasPermission: () => false,
  hasRole: () => false,
});

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const hasPermission = useCallback((permission: Permission) => {
    return user?.permissions.includes(permission) ?? false;
  }, [user]);

  const hasRole = useCallback((role: User['role']) => {
    return user?.role === role;
  }, [user]);

  return (
    <AuthContext.Provider value={{ user, hasPermission, hasRole }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

2. Условная визуализация компонентов

// components/reports/ReportList.tsx
export function ReportList() {
  const { user, hasPermission, hasRole } = useAuth();
  const [reports, setReports] = useState<Report[]>([]);

  useEffect(() => {
    if (hasPermission('view_reports')) {
      fetchReports(user?.role);
    }
  }, [hasPermission, user?.role]);

  if (!hasPermission('view_reports')) {
    return <AccessDenied />;
  }

  return (
    <div>
      <div className="report-filters">
        {/* Только менеджеры и админы видят фильтры по подразделениям */}
        {hasRole('manager') || hasRole('admin') && (
          <DepartmentFilter />
        )}
      </div>

      <div className="report-table">
        {reports.map(report => (
          <ReportRow key={report.id} report={report}>
            {/* Кнопка удаления только для админов */}
            {hasRole('admin') && (
              <DeleteButton reportId={report.id} />
            )}

            {/* Экспорт для менеджеров и выше */}
            {(hasRole('admin') || hasRole('manager')) && (
              <ExportButton report={report} />
            )}
          </ReportRow>
        ))}
      </div>
    </div>
  );
}

3. Backend API с фильтрацией по ролям

# Backend (FastAPI)
from enum import Enum
from sqlalchemy.orm import Session
from fastapi import Depends, HTTPException

class UserRole(str, Enum):
    ADMIN = "admin"
    MANAGER = "manager"
    ANALYST = "analyst"
    VIEWER = "viewer"

@app.get("/api/v1/reports")
async def get_reports(
    user: User = Depends(get_current_user),
    department_id: str | None = None,
    db: Session = Depends(get_db)
):
    # Базовая проверка доступа
    if "view_reports" not in user.permissions:
        raise HTTPException(status_code=403, detail="Access denied")

    query = db.query(Report)

    # Фильтрация по ролям
    if user.role == UserRole.VIEWER:
        # Viewer видит только опубликованные отчеты
        query = query.filter(Report.is_published == True)

    elif user.role == UserRole.ANALYST:
        # Analyst видит свои отчеты и опубликованные
        query = query.filter(
            (Report.created_by == user.id) | (Report.is_published == True)
        )

    elif user.role == UserRole.MANAGER:
        # Manager видит отчеты своего подразделения
        query = query.filter(Report.department_id == user.department_id)

    # Admin видит ВСЕ отчеты (фильтра нет)

    # Дополнительная фильтрация по department_id (если позволяет роль)
    if department_id and user.role in (UserRole.ADMIN, UserRole.MANAGER):
        if user.role == UserRole.MANAGER and department_id != user.department_id:
            raise HTTPException(status_code=403, detail="Cannot access other departments")
        query = query.filter(Report.department_id == department_id)

    reports = query.all()
    return reports

4. Кастомный хук для управления доступом

// hooks/useReportAccess.ts
interface ReportAccessConfig {
  admin: boolean;
  manager: boolean;
  analyst: boolean;
  viewer: boolean;
}

export function useReportAccess() {
  const { user, hasRole } = useAuth();

  const canViewReport = (report: Report): boolean => {
    if (hasRole('admin')) return true;
    if (hasRole('manager')) {
      return report.departmentId === user?.departmentId || report.isPublished;
    }
    if (hasRole('analyst')) {
      return report.createdBy === user?.id || report.isPublished;
    }
    if (hasRole('viewer')) {
      return report.isPublished;
    }
    return false;
  };

  const canEditReport = (report: Report): boolean => {
    if (hasRole('admin')) return true;
    if (hasRole('manager')) {
      return report.departmentId === user?.departmentId;
    }
    if (hasRole('analyst')) {
      return report.createdBy === user?.id;
    }
    return false;
  };

  const canDeleteReport = (report: Report): boolean => {
    return hasRole('admin') || (hasRole('analyst') && report.createdBy === user?.id);
  };

  const canExportReport = (): boolean => {
    return hasRole('admin') || hasRole('manager') || hasRole('analyst');
  };

  return {
    canViewReport,
    canEditReport,
    canDeleteReport,
    canExportReport,
  };
}

5. Компонент с контролем доступа

// components/reports/ReportDetail.tsx
interface ReportDetailProps {
  reportId: string;
}

export function ReportDetail({ reportId }: ReportDetailProps) {
  const { user } = useAuth();
  const { canViewReport, canEditReport, canDeleteReport, canExportReport } = useReportAccess();
  const [report, setReport] = useState<Report | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetchReport(reportId)
      .then(data => {
        // Проверка доступа на фронтенде
        if (!canViewReport(data)) {
          throw new Error('Access denied');
        }
        setReport(data);
      })
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [reportId, canViewReport]);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!report) return null;

  return (
    <div className="report-detail">
      <div className="report-header">
        <h1>{report.title}</h1>
        <div className="report-actions">
          {canEditReport(report) && (
            <button onClick={() => openEditor(report.id)}>Edit</button>
          )}
          {canDeleteReport(report) && (
            <button onClick={() => deleteReport(report.id)}>Delete</button>
          )}
          {canExportReport() && (
            <button onClick={() => exportReport(report)}>Export</button>
          )}
        </div>
      </div>

      <div className="report-content">
        {/* Скрытие чувствительных данных для зрителей */}
        {(user?.role === 'admin' || user?.role === 'manager' || user?.role === 'analyst') && (
          <div className="sensitive-metrics">
            <p>Revenue: ${report.revenue}</p>
            <p>Cost: ${report.cost}</p>
          </div>
        )}

        {/* Доступно для всех */}
        <ReportChart data={report.data} />
      </div>
    </div>
  );
}

6. Матрица доступа (reference table)

// types/rbac.ts
const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
  admin: [
    'view_reports',
    'create_reports',
    'edit_all_reports',
    'delete_all_reports',
    'export_reports',
    'manage_users',
    'view_all_data',
  ],
  manager: [
    'view_reports',
    'create_reports',
    'edit_department_reports',
    'export_reports',
    'view_department_data',
  ],
  analyst: [
    'view_reports',
    'create_reports',
    'edit_own_reports',
    'export_reports',
  ],
  viewer: [
    'view_published_reports',
  ],
};

7. Protected Route компонент

// components/ProtectedRoute.tsx
interface ProtectedRouteProps {
  children: React.ReactNode;
  requiredRole?: UserRole | UserRole[];
  requiredPermission?: Permission;
}

export function ProtectedRoute({
  children,
  requiredRole,
  requiredPermission,
}: ProtectedRouteProps) {
  const { hasRole, hasPermission } = useAuth();

  const hasAccess = (() => {
    if (requiredRole) {
      const roles = Array.isArray(requiredRole) ? requiredRole : [requiredRole];
      return roles.some(role => hasRole(role));
    }
    if (requiredPermission) {
      return hasPermission(requiredPermission);
    }
    return true;
  })();

  if (!hasAccess) {
    return <AccessDenied />;
  }

  return <>{children}</>;
}

// Использование
<ProtectedRoute requiredRole={['admin', 'manager']}>
  <AdvancedReportBuilder />
</ProtectedRoute>

Best Practices

1. Принцип наименьших привилегий

  • Давай минимальные права по умолчанию
  • Добавляй специфичные разрешения по необходимости

2. Не полагайся только на фронтенд

  • Backend ДОЛЖЕН проверять разрешения
  • Фронтенд скрывает UI, но не обеспечивает безопасность

3. Логирование доступа

// На бэкенде
db.log_access(user_id, 'view_report', report_id, timestamp)

4. Кэширование прав

const cachedPermissions = useMemo(() => {
  return ROLE_PERMISSIONS[user.role];
}, [user.role]);

Итог

Управление отчетами для разных ролей требует:

  • Backend валидация (главное!)
  • Frontend фильтрация (UX)
  • Матрица доступа (контроль)
  • Защищённые routes (безопасность)

Это обеспечит безопасность и правильную работу приложения.