refactoring

This commit is contained in:
root
2026-01-12 18:15:12 +03:00
parent 077b79b8f7
commit c55264cf42
5 changed files with 274 additions and 422 deletions
-39
View File
@@ -93,45 +93,6 @@ class Auth extends BaseController
}
}
/**
* Форматирование времени блокировки для отображения
*
* @param int $seconds Секунды
* @return string
*/
protected function formatBlockTime(int $seconds): string
{
if ($seconds >= 60) {
$minutes = ceil($seconds / 60);
return $minutes . ' ' . $this->pluralize($minutes, ['минуту', 'минуты', 'минут']);
}
return $seconds . ' ' . $this->pluralize($seconds, ['секунду', 'секунды', 'секунд']);
}
/**
* Склонение окончаний для чисел
*
* @param int $number
* @param array $forms [одна, две, пять]
* @return string
*/
protected function pluralize(int $number, array $forms): string
{
$abs = abs($number);
$mod = $abs % 10;
if ($abs % 100 >= 11 && $abs % 100 <= 19) {
return $forms[2];
}
if ($mod === 1) {
return $forms[0];
}
if ($mod >= 2 && $mod <= 4) {
return $forms[1];
}
return $forms[2];
}
public function register()
{
if ($this->request->getMethod() === 'POST') {
+212 -20
View File
@@ -2,12 +2,12 @@
namespace App\Controllers;
use App\Models\OrganizationUserModel;
use App\Services\AccessService;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use App\Models\OrganizationModel;
use App\Services\AccessService;
/**
* BaseController provides a convenient place for loading components
@@ -29,6 +29,7 @@ abstract class BaseController extends Controller
protected $session;
protected AccessService $access;
protected ?OrganizationUserModel $orgUserModel = null;
/**
* @return void
@@ -51,11 +52,54 @@ abstract class BaseController extends Controller
}
/**
* Проверка права на действие ( shortcut для $this->access->can() )
*
* @param string $action
* @param string $resource
* @return bool
* Получение лениво инициализированной модели OrganizationUserModel
*/
protected function getOrgUserModel(): OrganizationUserModel
{
if ($this->orgUserModel === null) {
$this->orgUserModel = new OrganizationUserModel();
}
return $this->orgUserModel;
}
// ========================================
// Методы для работы с пользователем и организацией
// ========================================
/**
* Получение ID текущего пользователя
*/
protected function getCurrentUserId(): ?int
{
$userId = $this->session->get('user_id');
return $userId ? (int) $userId : null;
}
/**
* Получение данных текущего пользователя
*/
protected function getCurrentUser(): ?array
{
$userId = $this->getCurrentUserId();
if (!$userId) {
return null;
}
$userModel = new \App\Models\UserModel();
return $userModel->find($userId);
}
/**
* Получение ID активной организации
*/
protected function getActiveOrgId(): ?int
{
$orgId = $this->session->get('active_org_id');
return $orgId ? (int) $orgId : null;
}
/**
* Проверка права на действие (shortcut для $this->access->can())
*/
protected function can(string $action, string $resource): bool
{
@@ -63,16 +107,165 @@ abstract class BaseController extends Controller
}
/**
* Проверка роли (shortcut для $this->access->isRole() )
*
* @param string|array $roles
* @return bool
* Проверка роли (shortcut для $this->access->isRole())
*/
protected function isRole($roles): bool
{
return $this->access->isRole($roles);
}
/**
* Получение membership пользователя для организации
*
* @param int $orgId
* @return array|null
*/
protected function getMembership(int $orgId): ?array
{
$userId = $this->getCurrentUserId();
if (!$userId || !$orgId) {
return null;
}
return $this->getOrgUserModel()
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
}
/**
* Получение membership с требованием наличия доступа
* Бросает исключение если доступ запрещён
*
* @param int $orgId
* @return array
*/
protected function requireMembership(int $orgId): array
{
$membership = $this->getMembership($orgId);
if (!$membership) {
throw new \RuntimeException('Доступ запрещён');
}
return $membership;
}
/**
* Получение ID активной организации с требованием
* Бросает исключение если организация не выбрана
*
* @return int
*/
protected function requireActiveOrg(): int
{
$orgId = $this->getActiveOrgId();
if (!$orgId) {
throw new \RuntimeException('Организация не выбрана');
}
return $orgId;
}
// ========================================
// Методы для редиректов и ответов
// ========================================
/**
* Редирект с сообщением об ошибке
*/
protected function redirectWithError(string $message, string $redirectUrl): ResponseInterface
{
if ($this->request->isAJAX()) {
return service('response')
->setStatusCode(403)
->setJSON(['error' => $message]);
}
$this->session->setFlashdata('error', $message);
return redirect()->to($redirectUrl);
}
/**
* Редирект с сообщением об успехе
*/
protected function redirectWithSuccess(string $message, string $redirectUrl): ResponseInterface
{
if ($this->request->isAJAX()) {
return service('response')
->setStatusCode(200)
->setJSON(['success' => true, 'message' => $message]);
}
$this->session->setFlashdata('success', $message);
return redirect()->to($redirectUrl);
}
/**
* Ответ для AJAX запросов с ошибкой доступа
*/
protected function forbiddenResponse(string $message = 'Доступ запрещён'): ResponseInterface
{
return service('response')
->setStatusCode(403)
->setJSON(['error' => $message]);
}
/**
* Ответ для AJAX запросов с ошибкой валидации
*/
protected function validationErrorResponse(string $message = 'Ошибка валидации', array $errors = []): ResponseInterface
{
return service('response')
->setStatusCode(422)
->setJSON([
'success' => false,
'message' => $message,
'errors' => $errors,
]);
}
// ========================================
// Утилиты для вывода
// ========================================
/**
* Форматирование времени блокировки для отображения
*/
protected function formatBlockTime(int $seconds): string
{
if ($seconds >= 60) {
$minutes = ceil($seconds / 60);
return $minutes . ' ' . $this->pluralize($minutes, ['минуту', 'минуты', 'минут']);
}
return $seconds . ' ' . $this->pluralize($seconds, ['секунду', 'секунды', 'секунд']);
}
/**
* Склонение окончаний для чисел
*
* @param int $number
* @param array $forms [одна, две, пять]
* @return string
*/
protected function pluralize(int $number, array $forms): string
{
$abs = abs($number);
$mod = $abs % 10;
if ($abs % 100 >= 11 && $abs % 100 <= 19) {
return $forms[2];
}
if ($mod === 1) {
return $forms[0];
}
if ($mod >= 2 && $mod <= 4) {
return $forms[1];
}
return $forms[2];
}
// ========================================
// Рендеринг Twig
// ========================================
public function renderTwig($template, $data = [])
{
helper('csrf');
@@ -154,7 +347,7 @@ abstract class BaseController extends Controller
// Старый способ извлечения фильтров для совместимости
foreach ($this->request->getGet() as $key => $value) {
if (str_starts_with($key, 'filters[') && str_ends_with($key, ']')) {
$field = substr($key, 8, -1); // Исправлено: было 9, должно быть 8
$field = substr($key, 8, -1);
$filters[$field] = $value;
}
}
@@ -185,7 +378,7 @@ abstract class BaseController extends Controller
if ($value === '') {
continue;
}
// Сначала проверяем fieldMap (алиасы) — они имеют приоритет
if (isset($config['fieldMap']) && isset($config['fieldMap'][$filterKey])) {
$realField = $config['fieldMap'][$filterKey];
@@ -222,7 +415,7 @@ abstract class BaseController extends Controller
];
$data = [
'items' => $items, // Алиас для универсального шаблона
'items' => $items,
'pagerDetails' => $pagerData,
'perPage' => $perPage,
'sort' => $sort,
@@ -259,7 +452,6 @@ abstract class BaseController extends Controller
$tableData['actions'] = $config['actions'] ?? false;
$tableData['actionsConfig'] = $config['actionsConfig'] ?? [];
$tableData['columns'] = $config['columns'] ?? [];
$tableData['onRowClick'] = $config['onRowClick'] ?? null;
// Параметры для пустого состояния
$tableData['emptyMessage'] = $config['emptyMessage'] ?? 'Нет данных';
@@ -287,17 +479,17 @@ abstract class BaseController extends Controller
public function table(?array $config = null, ?string $pageUrl = null)
{
$isPartial = $this->request->getGet('format') === 'partial' || $this->isAjax();
// Если это частичный запрос (AJAX) — возвращаем только таблицу
if ($isPartial) {
return $this->renderTable($config, true);
}
// Прямой запрос к /table — редиректим на основную страницу
// Сохраняем все параметры: page, perPage, sort, order, filters
$params = $this->request->getGet();
unset($params['format']); // Убираем format=partial если был
if ($pageUrl) {
$redirectUrl = $pageUrl;
} else {
@@ -305,11 +497,11 @@ abstract class BaseController extends Controller
$tableUrl = $config['url'] ?? '/table';
$redirectUrl = $tableUrl;
}
if (!empty($params)) {
$redirectUrl .= '?' . http_build_query($params);
}
return redirect()->to($redirectUrl);
}
}
+49 -99
View File
@@ -3,26 +3,18 @@
namespace App\Controllers;
use App\Models\OrganizationModel;
use App\Models\OrganizationUserModel;
use App\Models\UserModel;
use App\Services\AccessService;
class Organizations extends BaseController
{
protected OrganizationUserModel $orgUserModel;
public function __construct()
{
$this->orgUserModel = new OrganizationUserModel();
}
public function index()
{
$orgModel = new OrganizationModel();
$userId = session()->get('user_id');
$userId = $this->getCurrentUserId();
// Получаем организации пользователя через связующую таблицу
$userOrgLinks = $this->orgUserModel->where('user_id', $userId)->findAll();
$userOrgLinks = $this->getOrgUserModel()->where('user_id', $userId)->findAll();
// Нам нужно получить сами данные организаций
$orgIds = array_column($userOrgLinks, 'organization_id');
@@ -70,7 +62,7 @@ class Organizations extends BaseController
// Создаем организацию
$orgId = $orgModel->insert([
'owner_id' => session()->get('user_id'),
'owner_id' => $this->getCurrentUserId(),
'name' => $this->request->getPost('name'),
'type' => 'business',
'requisites' => json_encode($requisites),
@@ -78,17 +70,17 @@ class Organizations extends BaseController
]);
// Привязываем владельца
$this->orgUserModel->insert([
$this->getOrgUserModel()->insert([
'organization_id' => $orgId,
'user_id' => session()->get('user_id'),
'user_id' => $this->getCurrentUserId(),
'role' => 'owner',
'status' => 'active',
'joined_at' => date('Y-m-d H:i:s'),
]);
// Сразу переключаемся на неё
session()->set('active_org_id', $orgId);
session()->setFlashdata('success', 'Организация успешно создана!');
$this->session->set('active_org_id', $orgId);
$this->session->setFlashdata('success', 'Организация успешно создана!');
return redirect()->to('/');
}
@@ -119,9 +111,9 @@ class Organizations extends BaseController
// Получаем статистику организации
$stats = [
'users_total' => $this->orgUserModel->where('organization_id', $orgId)->countAllResults(),
'users_active' => $this->orgUserModel->where('organization_id', $orgId)->where('status', 'active')->countAllResults(),
'users_blocked' => $this->orgUserModel->where('organization_id', $orgId)->where('status', 'blocked')->countAllResults(),
'users_total' => $this->getOrgUserModel()->where('organization_id', $orgId)->countAllResults(),
'users_active' => $this->getOrgUserModel()->where('organization_id', $orgId)->where('status', 'active')->countAllResults(),
'users_blocked' => $this->getOrgUserModel()->where('organization_id', $orgId)->where('status', 'blocked')->countAllResults(),
];
// Проверяем права для отображения пунктов меню
@@ -194,7 +186,7 @@ class Organizations extends BaseController
'requisites' => json_encode($newRequisites),
]);
session()->setFlashdata('success', 'Организация успешно обновлена!');
$this->session->setFlashdata('success', 'Организация успешно обновлена!');
return redirect()->to('/organizations');
}
@@ -232,17 +224,17 @@ class Organizations extends BaseController
// Если это POST с подтверждением — удаляем
if ($this->request->getMethod() === 'POST') {
// Удаляем связи с пользователями через forCurrentOrg()
$this->orgUserModel->forCurrentOrg()->delete();
$this->getOrgUserModel()->forCurrentOrg()->delete();
// Мягкое удаление организации
$orgModel->delete($orgId);
// Если удаляли активную организацию — очищаем
if (session()->get('active_org_id') == $orgId) {
session()->remove('active_org_id');
if ($this->session->get('active_org_id') == $orgId) {
$this->session->remove('active_org_id');
}
session()->setFlashdata('success', 'Организация "' . $organization['name'] . '" удалена');
$this->session->setFlashdata('success', 'Организация "' . $organization['name'] . '" удалена');
return redirect()->to('/organizations');
}
@@ -254,11 +246,11 @@ class Organizations extends BaseController
public function switch($orgId)
{
$userId = session()->get('user_id');
$userId = $this->getCurrentUserId();
$orgId = (int) $orgId;
// Проверяем доступ
$membership = $this->orgUserModel
$membership = $this->getOrgUserModel()
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
@@ -267,11 +259,11 @@ class Organizations extends BaseController
// Сбрасываем кэш AccessService при смене организации
$this->access->resetCache();
session()->set('active_org_id', $orgId);
session()->setFlashdata('success', 'Организация изменена');
$this->session->set('active_org_id', $orgId);
$this->session->setFlashdata('success', 'Организация изменена');
return redirect()->to('/');
} else {
session()->setFlashdata('error', 'Доступ запрещен');
$this->session->setFlashdata('error', 'Доступ запрещен');
return redirect()->to('/organizations');
}
}
@@ -309,14 +301,14 @@ class Organizations extends BaseController
$tableHtml = $this->renderTable($this->getUsersTableConfig($orgId));
// Получаем данные пользователей для статистики
$users = $this->orgUserModel->getOrganizationUsers($orgId);
$users = $this->getOrgUserModel()->getOrganizationUsers($orgId);
return $this->renderTwig('organizations/users', [
'organization' => $organization,
'organization_id' => $orgId,
'tableHtml' => $tableHtml,
'users' => $users,
'current_user_id' => session()->get('user_id'),
'current_user_id' => $this->getCurrentUserId(),
'can_manage_users' => $this->access->canManageUsers(),
'current_role' => $membership['role'],
]);
@@ -329,11 +321,11 @@ class Organizations extends BaseController
{
// Проверяем права для кнопок действий
$canManage = $this->access->canManageUsers();
return [
'id' => 'users-table',
'url' => '/organizations/' . $orgId . '/users/table',
'model' => $this->orgUserModel,
'model' => $this->getOrgUserModel(),
'columns' => [
'user_email' => [
'label' => 'Пользователь',
@@ -495,7 +487,7 @@ class Organizations extends BaseController
$orgId,
$email,
$role,
session()->get('user_id')
$this->getCurrentUserId()
);
return $this->response->setJSON($result);
@@ -519,7 +511,7 @@ class Organizations extends BaseController
}
// Нельзя заблокировать владельца
$targetMembership = $this->orgUserModel
$targetMembership = $this->getOrgUserModel()
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
@@ -532,9 +524,9 @@ class Organizations extends BaseController
return $this->redirectWithError('Нельзя заблокировать владельца', "/organizations/users/{$orgId}");
}
$this->orgUserModel->blockUser($targetMembership['id']);
$this->getOrgUserModel()->blockUser($targetMembership['id']);
session()->setFlashdata('success', 'Пользователь заблокирован');
$this->session->setFlashdata('success', 'Пользователь заблокирован');
return redirect()->to("/organizations/users/{$orgId}");
}
@@ -551,7 +543,7 @@ class Organizations extends BaseController
return $this->redirectWithError('Доступ запрещен', '/organizations');
}
$targetMembership = $this->orgUserModel
$targetMembership = $this->getOrgUserModel()
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
@@ -560,9 +552,9 @@ class Organizations extends BaseController
return $this->redirectWithError('Пользователь не найден', "/organizations/users/{$orgId}");
}
$this->orgUserModel->unblockUser($targetMembership['id']);
$this->getOrgUserModel()->unblockUser($targetMembership['id']);
session()->setFlashdata('success', 'Пользователь разблокирован');
$this->session->setFlashdata('success', 'Пользователь разблокирован');
return redirect()->to("/organizations/users/{$orgId}");
}
@@ -584,21 +576,21 @@ class Organizations extends BaseController
}
// Удаляем из организации
$this->orgUserModel->delete($membership['id']);
$this->getOrgUserModel()->delete($membership['id']);
// Если это была активная организация - переключаем на другую
if (session()->get('active_org_id') == $orgId) {
$userId = session()->get('user_id');
$otherOrgs = $this->orgUserModel->where('user_id', $userId)->where('status', 'active')->findAll();
if ($this->session->get('active_org_id') == $orgId) {
$userId = $this->getCurrentUserId();
$otherOrgs = $this->getOrgUserModel()->where('user_id', $userId)->where('status', 'active')->findAll();
if (!empty($otherOrgs)) {
session()->set('active_org_id', $otherOrgs[0]['organization_id']);
$this->session->set('active_org_id', $otherOrgs[0]['organization_id']);
} else {
session()->remove('active_org_id');
$this->session->remove('active_org_id');
}
}
session()->setFlashdata('success', 'Вы покинули организацию');
$this->session->setFlashdata('success', 'Вы покинули организацию');
return redirect()->to('/organizations');
}
@@ -618,9 +610,9 @@ class Organizations extends BaseController
$result = $invitationService->resendInvitation($invitationId, $orgId);
if ($result['success']) {
session()->setFlashdata('success', 'Приглашение отправлено повторно');
$this->session->setFlashdata('success', 'Приглашение отправлено повторно');
} else {
session()->setFlashdata('error', $result['message']);
$this->session->setFlashdata('error', $result['message']);
}
return redirect()->to("/organizations/users/{$orgId}");
@@ -642,9 +634,9 @@ class Organizations extends BaseController
$result = $invitationService->cancelInvitation($invitationId, $orgId);
if ($result['success']) {
session()->setFlashdata('success', 'Приглашение отозвано');
$this->session->setFlashdata('success', 'Приглашение отозвано');
} else {
session()->setFlashdata('error', $result['message']);
$this->session->setFlashdata('error', $result['message']);
}
return redirect()->to("/organizations/users/{$orgId}");
@@ -669,7 +661,7 @@ class Organizations extends BaseController
}
// Нельзя изменить роль владельца
$targetMembership = $this->orgUserModel
$targetMembership = $this->getOrgUserModel()
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
@@ -691,11 +683,11 @@ class Organizations extends BaseController
return redirect()->back()->withInput()->with('error', 'Недопустимая роль');
}
$this->orgUserModel->update($targetMembership['id'], [
$this->getOrgUserModel()->update($targetMembership['id'], [
'role' => $newRole,
]);
session()->setFlashdata('success', 'Роль изменена');
$this->session->setFlashdata('success', 'Роль изменена');
return redirect()->to("/organizations/users/{$orgId}");
}
@@ -717,7 +709,7 @@ class Organizations extends BaseController
{
$orgId = (int) $orgId;
$userId = (int) $userId;
$currentUserId = session()->get('user_id');
$currentUserId = $this->getCurrentUserId();
$membership = $this->getMembership($orgId);
if (!$membership) {
@@ -730,7 +722,7 @@ class Organizations extends BaseController
}
// Нельзя удалить владельца
$targetMembership = $this->orgUserModel
$targetMembership = $this->getOrgUserModel()
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
@@ -749,51 +741,9 @@ class Organizations extends BaseController
}
// Удаляем пользователя из организации
$this->orgUserModel->delete($targetMembership['id']);
$this->getOrgUserModel()->delete($targetMembership['id']);
session()->setFlashdata('success', 'Пользователь удалён из организации');
$this->session->setFlashdata('success', 'Пользователь удалён из организации');
return redirect()->to("/organizations/users/{$orgId}");
}
// ========================================
// Вспомогательные методы
// ========================================
/**
* Получение membership пользователя для организации
*
* @param int $orgId
* @return array|null
*/
protected function getMembership(int $orgId): ?array
{
$userId = session()->get('user_id');
if (!$userId || !$orgId) {
return null;
}
return $this->orgUserModel
->where('organization_id', $orgId)
->where('user_id', $userId)
->first();
}
/**
* Редирект с сообщением об ошибке
*
* @param string $message
* @param string $redirectUrl
* @return ResponseInterface
*/
protected function redirectWithError(string $message, string $redirectUrl)
{
if ($this->request->isAJAX()) {
return service('response')
->setStatusCode(403)
->setJSON(['error' => $message]);
}
session()->setFlashdata('error', $message);
return redirect()->to($redirectUrl);
}
}