561 lines
15 KiB
Markdown
561 lines
15 KiB
Markdown
# Администраторская панель Discours
|
||
|
||
## Обзор
|
||
|
||
Администраторская панель — это комплексная система управления платформой Discours, предоставляющая полный контроль над пользователями, публикациями, сообществами и их ролями.
|
||
|
||
## Архитектура системы доступа
|
||
|
||
### Уровни доступа
|
||
|
||
1. **Системные администраторы** — email в переменной `ADMIN_EMAILS` (управление системой через переменные среды)
|
||
2. **RBAC роли в сообществах** — `reader`, `author`, `artist`, `expert`, `editor`, `admin` (управляемые через админку)
|
||
|
||
**ВАЖНО**:
|
||
- Роль `admin` в RBAC — это обычная роль в сообществе, управляемая через админку
|
||
- "Системный администратор" — синтетическая роль, которая НЕ хранится в базе данных
|
||
- Синтетическая роль добавляется только в API ответы для пользователей из `ADMIN_EMAILS`
|
||
- На фронте в сообществах синтетическая роль НЕ отображается
|
||
|
||
### Декораторы безопасности
|
||
|
||
```python
|
||
@admin_auth_required # Доступ только системным админам (ADMIN_EMAILS)
|
||
@editor_or_admin_required # Доступ редакторам и админам сообщества (RBAC роли)
|
||
```
|
||
|
||
## Модули администрирования
|
||
|
||
### 1. Управление пользователями
|
||
|
||
#### Получение списка пользователей
|
||
```graphql
|
||
query AdminGetUsers(
|
||
$limit: Int = 20
|
||
$offset: Int = 0
|
||
$search: String = ""
|
||
) {
|
||
adminGetUsers(limit: $limit, offset: $offset, search: $search) {
|
||
authors {
|
||
id
|
||
email
|
||
name
|
||
slug
|
||
roles
|
||
created_at
|
||
last_seen
|
||
}
|
||
total
|
||
page
|
||
perPage
|
||
totalPages
|
||
}
|
||
}
|
||
```
|
||
|
||
**Особенности:**
|
||
- Поиск по email, имени и ID
|
||
- Пагинация с ограничением 1-100 записей
|
||
- Роли получаются из основного сообщества (ID=1)
|
||
- Автоматическое добавление синтетической роли "Системный администратор" для email из `ADMIN_EMAILS`
|
||
|
||
#### Обновление пользователя
|
||
```graphql
|
||
mutation AdminUpdateUser($user: AdminUserUpdateInput!) {
|
||
adminUpdateUser(user: $user) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Поддерживаемые поля:**
|
||
- `email` — с проверкой уникальности
|
||
- `name` — имя пользователя
|
||
- `slug` — с проверкой уникальности
|
||
- `roles` — массив ролей для основного сообщества
|
||
|
||
### 2. Система ролей и разрешений (RBAC)
|
||
|
||
#### Иерархия ролей
|
||
```
|
||
reader → author → artist → expert → editor → admin
|
||
```
|
||
|
||
Каждая роль наследует права предыдущих **только при инициализации** сообщества.
|
||
|
||
#### Получение ролей
|
||
```graphql
|
||
query AdminGetRoles($community: Int) {
|
||
adminGetRoles(community: $community) {
|
||
id
|
||
name
|
||
description
|
||
}
|
||
}
|
||
```
|
||
|
||
- Без `community` — все системные роли
|
||
- С `community` — роли конкретного сообщества + счетчик разрешений
|
||
|
||
#### Управление ролями в сообществах
|
||
|
||
**Получение ролей пользователя:**
|
||
```graphql
|
||
query AdminGetUserCommunityRoles(
|
||
$author_id: Int!
|
||
$community_id: Int!
|
||
) {
|
||
adminGetUserCommunityRoles(
|
||
author_id: $author_id
|
||
community_id: $community_id
|
||
) {
|
||
author_id
|
||
community_id
|
||
roles
|
||
}
|
||
}
|
||
```
|
||
|
||
**Назначение ролей:**
|
||
```graphql
|
||
mutation AdminSetUserCommunityRoles(
|
||
$author_id: Int!
|
||
$community_id: Int!
|
||
$roles: [String!]!
|
||
) {
|
||
adminSetUserCommunityRoles(
|
||
author_id: $author_id
|
||
community_id: $community_id
|
||
roles: $roles
|
||
) {
|
||
success
|
||
error
|
||
author_id
|
||
community_id
|
||
roles
|
||
}
|
||
}
|
||
```
|
||
|
||
**Добавление отдельной роли:**
|
||
```graphql
|
||
mutation AdminAddUserToRole(
|
||
$author_id: Int!
|
||
$role_id: String!
|
||
$community_id: Int!
|
||
) {
|
||
adminAddUserToRole(
|
||
author_id: $author_id
|
||
role_id: $role_id
|
||
community_id: $community_id
|
||
) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Удаление роли:**
|
||
```graphql
|
||
mutation AdminRemoveUserFromRole(
|
||
$author_id: Int!
|
||
$role_id: String!
|
||
$community_id: Int!
|
||
) {
|
||
adminRemoveUserFromRole(
|
||
author_id: $author_id
|
||
role_id: $role_id
|
||
community_id: $community_id
|
||
) {
|
||
success
|
||
removed
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Управление сообществами
|
||
|
||
#### Участники сообщества
|
||
```graphql
|
||
query AdminGetCommunityMembers(
|
||
$community_id: Int!
|
||
$limit: Int = 20
|
||
$offset: Int = 0
|
||
) {
|
||
adminGetCommunityMembers(
|
||
community_id: $community_id
|
||
limit: $limit
|
||
offset: $offset
|
||
) {
|
||
members {
|
||
id
|
||
name
|
||
email
|
||
slug
|
||
roles
|
||
}
|
||
total
|
||
community_id
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Настройки ролей сообщества
|
||
|
||
**Получение настроек:**
|
||
```graphql
|
||
query AdminGetCommunityRoleSettings($community_id: Int!) {
|
||
adminGetCommunityRoleSettings(community_id: $community_id) {
|
||
community_id
|
||
default_roles
|
||
available_roles
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Обновление настроек:**
|
||
```graphql
|
||
mutation AdminUpdateCommunityRoleSettings(
|
||
$community_id: Int!
|
||
$default_roles: [String!]!
|
||
$available_roles: [String!]!
|
||
) {
|
||
adminUpdateCommunityRoleSettings(
|
||
community_id: $community_id
|
||
default_roles: $default_roles
|
||
available_roles: $available_roles
|
||
) {
|
||
success
|
||
error
|
||
community_id
|
||
default_roles
|
||
available_roles
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Создание пользовательской роли
|
||
```graphql
|
||
mutation AdminCreateCustomRole($role: CustomRoleInput!) {
|
||
adminCreateCustomRole(role: $role) {
|
||
success
|
||
error
|
||
role {
|
||
id
|
||
name
|
||
description
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Удаление пользовательской роли
|
||
```graphql
|
||
mutation AdminDeleteCustomRole(
|
||
$role_id: String!
|
||
$community_id: Int!
|
||
) {
|
||
adminDeleteCustomRole(
|
||
role_id: $role_id
|
||
community_id: $community_id
|
||
) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. Управление публикациями
|
||
|
||
#### Получение списка публикаций
|
||
```graphql
|
||
query AdminGetShouts(
|
||
$limit: Int = 20
|
||
$offset: Int = 0
|
||
$search: String = ""
|
||
$status: String = "all"
|
||
$community: Int
|
||
) {
|
||
adminGetShouts(
|
||
limit: $limit
|
||
offset: $offset
|
||
search: $search
|
||
status: $status
|
||
community: $community
|
||
) {
|
||
shouts {
|
||
id
|
||
title
|
||
slug
|
||
body
|
||
lead
|
||
subtitle
|
||
# ... остальные поля
|
||
created_by {
|
||
id
|
||
email
|
||
name
|
||
slug
|
||
}
|
||
community {
|
||
id
|
||
name
|
||
slug
|
||
}
|
||
authors {
|
||
id
|
||
email
|
||
name
|
||
slug
|
||
}
|
||
topics {
|
||
id
|
||
title
|
||
slug
|
||
}
|
||
}
|
||
total
|
||
page
|
||
perPage
|
||
totalPages
|
||
}
|
||
}
|
||
```
|
||
|
||
**Статусы публикаций:**
|
||
- `all` — все публикации (включая удаленные)
|
||
- `published` — опубликованные
|
||
- `draft` — черновики
|
||
- `deleted` — удаленные
|
||
|
||
#### Операции с публикациями
|
||
|
||
**Обновление:**
|
||
```graphql
|
||
mutation AdminUpdateShout($shout: AdminShoutUpdateInput!) {
|
||
adminUpdateShout(shout: $shout) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Удаление (мягкое):**
|
||
```graphql
|
||
mutation AdminDeleteShout($shout_id: Int!) {
|
||
adminDeleteShout(shout_id: $shout_id) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Восстановление:**
|
||
```graphql
|
||
mutation AdminRestoreShout($shout_id: Int!) {
|
||
adminRestoreShout(shout_id: $shout_id) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. Управление приглашениями
|
||
|
||
#### Получение списка приглашений
|
||
```graphql
|
||
query AdminGetInvites(
|
||
$limit: Int = 20
|
||
$offset: Int = 0
|
||
$search: String = ""
|
||
$status: String = "all"
|
||
) {
|
||
adminGetInvites(
|
||
limit: $limit
|
||
offset: $offset
|
||
search: $search
|
||
status: $status
|
||
) {
|
||
invites {
|
||
inviter_id
|
||
author_id
|
||
shout_id
|
||
status
|
||
inviter {
|
||
id
|
||
email
|
||
name
|
||
slug
|
||
}
|
||
author {
|
||
id
|
||
email
|
||
name
|
||
slug
|
||
}
|
||
shout {
|
||
id
|
||
title
|
||
slug
|
||
created_by {
|
||
id
|
||
email
|
||
name
|
||
slug
|
||
}
|
||
}
|
||
}
|
||
total
|
||
page
|
||
perPage
|
||
totalPages
|
||
}
|
||
}
|
||
```
|
||
|
||
**Статусы приглашений:**
|
||
- `PENDING` — ожидает ответа
|
||
- `ACCEPTED` — принято
|
||
- `REJECTED` — отклонено
|
||
|
||
#### Операции с приглашениями
|
||
|
||
**Обновление статуса:**
|
||
```graphql
|
||
mutation AdminUpdateInvite($invite: AdminInviteUpdateInput!) {
|
||
adminUpdateInvite(invite: $invite) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Удаление:**
|
||
```graphql
|
||
mutation AdminDeleteInvite(
|
||
$inviter_id: Int!
|
||
$author_id: Int!
|
||
$shout_id: Int!
|
||
) {
|
||
adminDeleteInvite(
|
||
inviter_id: $inviter_id
|
||
author_id: $author_id
|
||
shout_id: $shout_id
|
||
) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
**Пакетное удаление:**
|
||
```graphql
|
||
mutation AdminDeleteInvitesBatch($invites: [AdminInviteIdInput!]!) {
|
||
adminDeleteInvitesBatch(invites: $invites) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6. Переменные окружения
|
||
|
||
Системные администраторы могут управлять переменными окружения:
|
||
|
||
```graphql
|
||
query GetEnvVariables {
|
||
getEnvVariables {
|
||
name
|
||
description
|
||
variables {
|
||
key
|
||
value
|
||
description
|
||
type
|
||
isSecret
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```graphql
|
||
mutation UpdateEnvVariable($key: String!, $value: String!) {
|
||
updateEnvVariable(key: $key, value: $value) {
|
||
success
|
||
error
|
||
}
|
||
}
|
||
```
|
||
|
||
## Особенности реализации
|
||
|
||
### Принцип DRY
|
||
- Переиспользование логики из `reader.py`, `editor.py`
|
||
- Общие утилиты в `_get_user_roles()`
|
||
- Централизованная обработка ошибок
|
||
|
||
### Новая RBAC система
|
||
- Роли хранятся в CSV формате в `CommunityAuthor.roles`
|
||
- Методы модели: `add_role()`, `remove_role()`, `set_roles()`, `has_role()`
|
||
- Права наследуются **только при инициализации**
|
||
- Redis кэширование развернутых прав
|
||
|
||
### Синтетические роли
|
||
- **"Системный администратор"** — добавляется автоматически для пользователей из `ADMIN_EMAILS`
|
||
- НЕ хранится в базе данных, только в API ответах
|
||
- НЕ отображается на фронте в интерфейсах управления сообществами
|
||
- Используется только для индикации системных прав доступа
|
||
|
||
### Безопасность
|
||
- Валидация всех входных данных
|
||
- Проверка существования сущностей
|
||
- Контроль доступа через декораторы
|
||
- Логирование всех административных действий
|
||
|
||
### Производительность
|
||
- Пагинация для всех списков
|
||
- Индексы по ключевым полям
|
||
- Ограничения на размер выборки (max 100)
|
||
- Оптимизированные SQL запросы с `joinedload`
|
||
|
||
## Миграция данных
|
||
|
||
При переходе на новую RBAC систему используется функция:
|
||
|
||
```python
|
||
from orm.community import migrate_old_roles_to_community_author
|
||
migrate_old_roles_to_community_author()
|
||
```
|
||
|
||
Функция автоматически переносит роли из старых таблиц в новый формат CSV.
|
||
|
||
## Мониторинг и логирование
|
||
|
||
Все административные действия логируются с уровнем INFO:
|
||
- Изменение ролей пользователей
|
||
- Обновление настроек сообществ
|
||
- Операции с публикациями
|
||
- Управление приглашениями
|
||
|
||
Ошибки логируются с уровнем ERROR и полным стектрейсом.
|
||
|
||
## Лучшие практики
|
||
|
||
1. **Всегда проверяйте роли перед назначением**
|
||
2. **Используйте транзакции для групповых операций**
|
||
3. **Логируйте критические изменения**
|
||
4. **Валидируйте права доступа на каждом этапе**
|
||
5. **Применяйте принцип минимальных привилегий**
|
||
|
||
## Расширение функциональности
|
||
|
||
Для добавления новых административных функций:
|
||
|
||
1. Создайте резолвер с соответствующим декоратором
|
||
2. Добавьте GraphQL схему в `schema/admin.graphql`
|
||
3. Реализуйте логику с переиспользованием существующих компонентов
|
||
4. Добавьте тесты и документацию
|
||
5. Обновите права доступа при необходимости
|