1
0

Большое обновление

1. Создание личных проектов
2. Управление командой
3. Приглашение участников
4. Уведомления

и многое другое...
This commit is contained in:
2026-01-18 20:17:02 +07:00
parent 250eac70a7
commit 190b4d0a5e
51 changed files with 6179 additions and 426 deletions

View File

@@ -12,9 +12,25 @@ if ($method === 'POST') {
// Создание комментария
if ($action === 'create') {
$comment->id_task = $data['id_task'] ?? null;
$id_task = $data['id_task'] ?? null;
// Проверяем доступ к проекту через задачу
$taskData = Task::check_task($id_task);
ProjectAccess::requireAccess($taskData['id_project'], $current_user_id);
// Проверяем право на создание комментариев (с учётом create_comment_own_task_only)
if (!ProjectAccess::canCreateComment(
$taskData['id_project'],
$current_user_id,
(int)($taskData['id_account'] ?? 0),
(int)($taskData['create_id_account'] ?? 0)
)) {
RestApi::response(['success' => false, 'errors' => ['access' => 'Нет прав на создание комментария']], 403);
}
$comment->id_task = $id_task;
$comment->id_accounts = $current_user_id;
$comment->id_answer = $data['id_answer'] ?? null; // Ответ на комментарий
$comment->id_answer = $data['id_answer'] ?? null;
$comment->text = $data['text'] ?? '';
$result = $comment->create();
@@ -23,7 +39,14 @@ if ($method === 'POST') {
// Обновление комментария
if ($action === 'update') {
$comment->id = $data['id'] ?? null;
$comment_id = $data['id'] ?? null;
// Проверяем доступ к проекту через комментарий -> задачу
$commentData = Comment::checkComment($comment_id);
$taskData = Task::check_task($commentData['id_task']);
ProjectAccess::requireAccess($taskData['id_project'], $current_user_id);
$comment->id = $comment_id;
$comment->id_accounts = $current_user_id;
$comment->text = $data['text'] ?? '';
@@ -34,26 +57,80 @@ if ($method === 'POST') {
// Удаление комментария
if ($action === 'delete') {
$id = $data['id'] ?? null;
$result = Comment::delete($id, $current_user_id);
// Проверяем доступ к проекту через комментарий -> задачу
$commentData = Comment::checkComment($id);
$taskData = Task::check_task($commentData['id_task']);
$project_id = $taskData['id_project'];
ProjectAccess::requireAccess($project_id, $current_user_id);
// Проверяем права на удаление
$isAuthor = (int)$commentData['id_accounts'] === (int)$current_user_id;
$isAdmin = ProjectAccess::isAdmin($project_id, $current_user_id);
$canDeleteAll = ProjectAccess::can($project_id, $current_user_id, 'delete_all_comments');
$canDeleteOwn = ProjectAccess::can($project_id, $current_user_id, 'delete_own_comments');
if (!$isAdmin && !$canDeleteAll && !($isAuthor && $canDeleteOwn)) {
RestApi::response([
'success' => false,
'errors' => ['access' => 'Нет прав на удаление комментария']
], 403);
}
$result = Comment::deleteWithAccess($id);
RestApi::response($result);
}
// Загрузка файла к комментарию (только автор)
// Загрузка файла к комментарию
if ($action === 'upload_image') {
$comment_id = $data['comment_id'] ?? null;
$file_base64 = $data['file_data'] ?? null;
$file_name = $data['file_name'] ?? null;
// Проверяем доступ к проекту
$commentData = Comment::checkComment($comment_id);
$taskData = Task::check_task($commentData['id_task']);
$project_id = $taskData['id_project'];
ProjectAccess::requireAccess($project_id, $current_user_id);
// Проверяем право на загрузку картинок
ProjectAccess::requirePermission($project_id, $current_user_id, 'upload_images');
// Только автор может загружать к своему комментарию
if ((int)$commentData['id_accounts'] !== (int)$current_user_id) {
RestApi::response([
'success' => false,
'errors' => ['access' => 'Вы можете загружать файлы только к своим комментариям']
], 403);
}
$result = Comment::uploadFile($comment_id, $file_base64, $file_name, $current_user_id);
$result = Comment::uploadFileSimple($comment_id, $file_base64, $file_name);
RestApi::response($result);
}
// Удаление файлов комментария (автор или админ проекта)
// Удаление файлов комментария
if ($action === 'delete_image') {
$comment_id = $data['comment_id'] ?? null;
$file_names = $data['file_names'] ?? $data['file_name'] ?? null;
// Проверяем доступ к проекту
$commentData = Comment::checkComment($comment_id);
$taskData = Task::check_task($commentData['id_task']);
$project_id = $taskData['id_project'];
ProjectAccess::requireAccess($project_id, $current_user_id);
// Проверка прав: автор комментария ИЛИ админ проекта
$isAuthor = (int)$commentData['id_accounts'] === (int)$current_user_id;
$isAdmin = ProjectAccess::isAdmin($project_id, $current_user_id);
if (!$isAuthor && !$isAdmin) {
RestApi::response([
'success' => false,
'errors' => ['access' => 'Нет прав на удаление файлов']
], 403);
}
$result = Comment::deleteFile($comment_id, $file_names, $current_user_id);
$result = Comment::deleteFileSimple($comment_id, $file_names);
RestApi::response($result);
}
@@ -66,12 +143,17 @@ if ($method === 'POST') {
if ($method === 'GET') {
// Получение комментариев задачи
// ?id_task=X (обязательный)
$current_user_id = RestApi::getCurrentUserId();
$id_task = $_GET['id_task'] ?? null;
if (!$id_task) {
RestApi::response(['success' => false, 'errors' => ['id_task' => 'Задача не указана']], 400);
}
// Проверяем доступ к проекту через задачу
$taskData = Task::check_task($id_task);
ProjectAccess::requireAccess($taskData['id_project'], $current_user_id);
$comment = new Comment();
$comments = $comment->getByTask($id_task);

View File

@@ -10,6 +10,10 @@ if ($method === 'POST') {
// Получение данных проекта (проект + колонки + отделы)
if ($action === 'get_project_data') {
$project_id = $data['id_project'] ?? null;
// Проверяем доступ к проекту
ProjectAccess::requireAccess($project_id, $user_id);
$result = Project::getProjectData($project_id);
if ($result) {
RestApi::response(['success' => true, 'data' => $result]);
@@ -136,16 +140,23 @@ if ($method === 'POST') {
}
if ($method === 'GET') {
// Получение всех проектов
// Получение всех проектов (только те, где пользователь участник)
// ?active=ID — дополнительно вернуть данные активного проекта
$user_id = RestApi::getCurrentUserId();
$project = new Project();
$projects = $project->getAll();
$active_id = $_GET['active'] ?? null;
if ($active_id) {
// Возвращаем список проектов + данные активного
$activeData = null;
if ($active_id && ProjectAccess::isMember((int)$active_id, $user_id)) {
// Есть доступ — возвращаем данные активного проекта
$activeData = Project::getProjectData((int)$active_id);
}
if ($activeData) {
RestApi::response([
'success' => true,
'data' => [
@@ -154,7 +165,7 @@ if ($method === 'GET') {
]
]);
} else {
// Только список проектов
// Нет active или нет доступа — возвращаем только список
RestApi::response(['success' => true, 'data' => $projects]);
}
}

View File

@@ -0,0 +1,85 @@
<?php
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'POST') {
$data = RestApi::getInput();
$action = $data['action'] ?? null;
$current_user_id = RestApi::getCurrentUserId();
// Приглашение участника в проект (только админ)
if ($action === 'add_member') {
$project_id = (int)($data['project_id'] ?? 0);
$user_id = (int)($data['user_id'] ?? 0);
$is_admin = (bool)($data['is_admin'] ?? false);
$permissions = $data['permissions'] ?? null;
if (!$project_id || !$user_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите project_id и user_id']], 400);
}
// Приглашать участников могут только админы
ProjectAccess::requireAdmin($project_id, $current_user_id);
$result = ProjectAccess::addMember($project_id, $user_id, $current_user_id, $is_admin, $permissions);
RestApi::response($result, $result['success'] ? 200 : 400);
}
// Обновление прав участника (только админ)
if ($action === 'update_member') {
$project_id = (int)($data['project_id'] ?? 0);
$user_id = (int)($data['user_id'] ?? 0);
$is_admin = isset($data['is_admin']) ? (bool)$data['is_admin'] : null;
$permissions = $data['permissions'] ?? null;
if (!$project_id || !$user_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите project_id и user_id']], 400);
}
// Редактировать права могут только админы
ProjectAccess::requireAdmin($project_id, $current_user_id);
// Обновляем is_admin если передан
if ($is_admin !== null) {
$result = ProjectAccess::setAdmin($project_id, $user_id, $is_admin, $current_user_id);
if (!$result['success']) {
RestApi::response($result, 400);
}
}
// Обновляем права если переданы
if ($permissions !== null) {
$result = ProjectAccess::setPermissions($project_id, $user_id, $permissions, $current_user_id);
if (!$result['success']) {
RestApi::response($result, 400);
}
}
RestApi::response(['success' => true]);
}
// Удаление участника из проекта (право remove_members или админ, или сам себя)
if ($action === 'remove_member') {
$project_id = (int)($data['project_id'] ?? 0);
$user_id = (int)($data['user_id'] ?? 0);
if (!$project_id || !$user_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите project_id и user_id']], 400);
}
// Пользователь может удалить сам себя, иначе нужно право remove_members
if ((int)$user_id !== (int)$current_user_id) {
ProjectAccess::requirePermission($project_id, $current_user_id, 'remove_members');
}
$result = ProjectAccess::removeMember($project_id, $user_id, $current_user_id);
RestApi::response($result, $result['success'] ? 200 : 400);
}
// Метод не указан
if (!$action) {
RestApi::response(['success' => false, 'error' => 'Укажите метод'], 400);
}
}
?>

View File

@@ -0,0 +1,134 @@
<?php
$method = $_SERVER['REQUEST_METHOD'];
// GET — получить свои pending-приглашения
if ($method === 'GET') {
$current_user_id = RestApi::getCurrentUserId();
if (!$current_user_id) {
RestApi::response(['success' => false, 'errors' => ['auth' => 'Требуется авторизация']], 401);
}
$invites = ProjectInvite::getMyPending($current_user_id);
$count = count($invites);
RestApi::response([
'success' => true,
'data' => $invites,
'count' => $count
]);
}
// POST — действия с приглашениями
if ($method === 'POST') {
$data = RestApi::getInput();
$action = $data['action'] ?? null;
$current_user_id = RestApi::getCurrentUserId();
if (!$current_user_id) {
RestApi::response(['success' => false, 'errors' => ['auth' => 'Требуется авторизация']], 401);
}
// Отправить приглашение (только админ проекта)
if ($action === 'send') {
$project_id = (int)($data['project_id'] ?? 0);
$user_id = (int)($data['user_id'] ?? 0);
$is_admin = (bool)($data['is_admin'] ?? false);
$permissions = $data['permissions'] ?? null;
if (!$project_id || !$user_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите project_id и user_id']], 400);
}
// Только админ может приглашать
ProjectAccess::requireAdmin($project_id, $current_user_id);
$result = ProjectInvite::create($project_id, $user_id, $current_user_id, $is_admin, $permissions);
RestApi::response($result, $result['success'] ? 200 : 400);
}
// Проверить, есть ли pending-приглашение для пользователя в проект
if ($action === 'check_pending') {
$project_id = (int)($data['project_id'] ?? 0);
$user_id = (int)($data['user_id'] ?? 0);
if (!$project_id || !$user_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите project_id и user_id']], 400);
}
// Проверяем доступ к проекту
ProjectAccess::requireAccess($project_id, $current_user_id);
$hasPending = ProjectInvite::hasPending($project_id, $user_id);
RestApi::response(['success' => true, 'has_pending' => $hasPending]);
}
// Получить pending-приглашения проекта (для админа)
if ($action === 'get_project_pending') {
$project_id = (int)($data['project_id'] ?? 0);
if (!$project_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите project_id']], 400);
}
// Только админ может видеть
ProjectAccess::requireAdmin($project_id, $current_user_id);
$invites = ProjectInvite::getProjectPending($project_id);
RestApi::response(['success' => true, 'data' => $invites]);
}
// Получить количество pending-приглашений для текущего пользователя
if ($action === 'get_count') {
$count = ProjectInvite::getPendingCount($current_user_id);
RestApi::response(['success' => true, 'count' => $count]);
}
// Принять приглашение
if ($action === 'accept') {
$invite_id = (int)($data['invite_id'] ?? 0);
if (!$invite_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите invite_id']], 400);
}
$result = ProjectInvite::accept($invite_id, $current_user_id);
RestApi::response($result, $result['success'] ? 200 : 400);
}
// Отклонить приглашение
if ($action === 'decline') {
$invite_id = (int)($data['invite_id'] ?? 0);
if (!$invite_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите invite_id']], 400);
}
$result = ProjectInvite::decline($invite_id, $current_user_id);
RestApi::response($result, $result['success'] ? 200 : 400);
}
// Отменить приглашение (только админ)
if ($action === 'cancel') {
$invite_id = (int)($data['invite_id'] ?? 0);
$project_id = (int)($data['project_id'] ?? 0);
if (!$invite_id || !$project_id) {
RestApi::response(['success' => false, 'errors' => ['data' => 'Укажите invite_id и project_id']], 400);
}
// Только админ может отменять
ProjectAccess::requireAdmin($project_id, $current_user_id);
$result = ProjectInvite::cancel($invite_id, $project_id);
RestApi::response($result, $result['success'] ? 200 : 400);
}
// Метод не указан
if (!$action) {
RestApi::response(['success' => false, 'error' => 'Укажите action'], 400);
}
}
?>

View File

@@ -5,6 +5,7 @@ $method = $_SERVER['REQUEST_METHOD'];
if ($method === 'POST') {
$data = RestApi::getInput();
$action = $data['action'] ?? null;
$user_id = RestApi::getCurrentUserId();
$task = new Task();
// Загрузка изображения
@@ -13,15 +14,23 @@ if ($method === 'POST') {
$file_base64 = $data['file_data'] ?? null;
$file_name = $data['file_name'] ?? null;
// Проверяем право на редактирование задачи
$taskData = Task::check_task($task_id);
ProjectAccess::requireEditTask($taskData['id_project'], $user_id, (int)($taskData['id_account'] ?? 0), (int)($taskData['create_id_account'] ?? 0));
$result = Task::uploadFile($task_id, $file_base64, $file_name);
RestApi::response($result);
}
// Удаление изображений (принимает file_names массив или file_name строку)
// Удаление изображений
if ($action === 'delete_image') {
$task_id = $data['task_id'] ?? null;
$file_names = $data['file_names'] ?? $data['file_name'] ?? null;
// Проверяем право на редактирование задачи
$taskData = Task::check_task($task_id);
ProjectAccess::requireEditTask($taskData['id_project'], $user_id, (int)($taskData['id_account'] ?? 0), (int)($taskData['create_id_account'] ?? 0));
$result = Task::deleteFile($task_id, $file_names);
RestApi::response($result);
}
@@ -32,6 +41,10 @@ if ($method === 'POST') {
$column_id = $data['column_id'] ?? null;
$to_index = $data['to_index'] ?? 0;
// Проверяем право на перемещение (с учётом move_own_task_only)
$taskData = Task::check_task($id);
ProjectAccess::requireMoveTask($taskData['id_project'], $user_id, (int)($taskData['id_account'] ?? 0), (int)($taskData['create_id_account'] ?? 0));
$result = Task::updateOrder($id, $column_id, $to_index);
RestApi::response($result);
}
@@ -39,6 +52,11 @@ if ($method === 'POST') {
// Обновление задачи
if ($action === 'update') {
$task->id = $data['id'] ?? null;
// Проверяем право на редактирование
$taskData = Task::check_task($task->id);
ProjectAccess::requireEditTask($taskData['id_project'], $user_id, (int)($taskData['id_account'] ?? 0), (int)($taskData['create_id_account'] ?? 0));
$task->id_department = $data['id_department'] ?? null;
$task->id_label = $data['id_label'] ?? null;
$task->id_account = $data['id_account'] ?? null;
@@ -55,10 +73,16 @@ if ($method === 'POST') {
// Создание задачи
if ($action === 'create') {
$task->id_project = $data['id_project'] ?? null;
$project_id = $data['id_project'] ?? null;
// Проверяем право на создание задач
ProjectAccess::requirePermission($project_id, $user_id, 'create_task');
$task->id_project = $project_id;
$task->id_department = $data['id_department'] ?? null;
$task->id_label = $data['id_label'] ?? null;
$task->id_account = $data['id_account'] ?? null;
$task->create_id_account = $user_id; // Создатель задачи
$task->column_id = $data['column_id'] ?? null;
$task->order = $data['order'] ?? 0;
$task->date = $data['date'] ?? null;
@@ -74,6 +98,11 @@ if ($method === 'POST') {
// Удаление задачи
if ($action === 'delete') {
$id = $data['id'] ?? null;
// Проверяем право на удаление
$taskData = Task::check_task($id);
ProjectAccess::requirePermission($taskData['id_project'], $user_id, 'delete_task');
$result = Task::delete($id);
RestApi::response($result);
}
@@ -82,6 +111,11 @@ if ($method === 'POST') {
if ($action === 'set_archive') {
$id = $data['id'] ?? null;
$archive = $data['archive'] ?? 1;
// Проверяем право на архивирование
$taskData = Task::check_task($id);
ProjectAccess::requirePermission($taskData['id_project'], $user_id, 'archive_task');
$result = Task::setArchive($id, $archive);
RestApi::response($result);
}
@@ -96,12 +130,16 @@ if ($method === 'GET') {
// Получение задач проекта
// ?id_project=1 (обязательный)
// ?archive=0 (неархивные, по умолчанию), ?archive=1 (архивные), ?archive=all (все)
$user_id = RestApi::getCurrentUserId();
$id_project = $_GET['id_project'] ?? null;
if (!$id_project) {
RestApi::response(['success' => false, 'errors' => ['id_project' => 'Проект не указан']], 400);
}
// Проверяем доступ к проекту
ProjectAccess::requireAccess((int)$id_project, $user_id);
$archive = $_GET['archive'] ?? 0;
if ($archive === 'all') {
$archive = null;

View File

@@ -6,13 +6,6 @@ if ($method === 'POST') {
$data = RestApi::getInput();
$action = $data['action'] ?? null;
// Получение конфигурации приложения
if ($action === 'get_config') {
RestApi::response(['success' => true, 'data' => [
'COLUMN_DONE_ID' => COLUMN_DONE_ID
]]);
}
// Авторизация
if ($action === 'auth_login') {
$account = new Account();
@@ -53,6 +46,27 @@ if ($method === 'POST') {
RestApi::response($result);
}
// Поиск пользователя по логину
if ($action === 'search') {
$current_user_id = RestApi::getCurrentUserId();
$username = trim($data['username'] ?? '');
if (!$username) {
RestApi::response(['success' => false, 'errors' => ['username' => 'Введите логин']], 400);
}
// Ищем пользователя по username
$user = Database::get('accounts', ['id', 'name', 'username', 'avatar_url'], [
'username' => $username
]);
if (!$user) {
RestApi::response(['success' => false, 'errors' => ['username' => 'Пользователь не найден']]);
}
RestApi::response(['success' => true, 'data' => $user]);
}
// Проверяем, что метод не пустой
if (!$action) {
RestApi::response(['success' => false, 'error' => 'Укажите метод'], 400);
@@ -60,11 +74,22 @@ if ($method === 'POST') {
}
if ($method === 'GET') {
// Получение всех пользователей
$account = new Account();
$users = $account->getAll();
// Получение участников проекта
// ?id_project=X (обязательный)
$current_user_id = RestApi::getCurrentUserId();
$id_project = $_GET['id_project'] ?? null;
RestApi::response(['success' => true, 'data' => $users]);
if (!$id_project) {
RestApi::response(['success' => false, 'errors' => ['id_project' => 'Проект не указан']], 400);
}
// Проверяем доступ к проекту
ProjectAccess::requireAccess((int)$id_project, $current_user_id);
// Получаем участников проекта
$members = ProjectAccess::getMembers((int)$id_project);
RestApi::response(['success' => true, 'data' => $members]);
}