Обновление бека+фронт
MVP версия - которая уже готова к работе
This commit is contained in:
112
backend/api/task.php
Normal file
112
backend/api/task.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'POST') {
|
||||
$data = RestApi::getInput();
|
||||
$action = $data['action'] ?? null;
|
||||
$task = new Task();
|
||||
|
||||
// Получение колонок
|
||||
if ($action === 'get_columns') {
|
||||
$result = $task->getColumns();
|
||||
RestApi::response(['success' => true, 'data' => $result]);
|
||||
}
|
||||
|
||||
// Получение департаментов
|
||||
if ($action === 'get_departments') {
|
||||
$result = $task->getDepartments();
|
||||
RestApi::response(['success' => true, 'data' => $result]);
|
||||
}
|
||||
|
||||
// Получение меток
|
||||
if ($action === 'get_labels') {
|
||||
$result = $task->getLabels();
|
||||
RestApi::response(['success' => true, 'data' => $result]);
|
||||
}
|
||||
|
||||
// Загрузка изображения
|
||||
if ($action === 'upload_image') {
|
||||
$task_id = $data['task_id'] ?? null;
|
||||
$file_base64 = $data['file_data'] ?? null;
|
||||
$file_name = $data['file_name'] ?? null;
|
||||
|
||||
$result = TaskImage::upload($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;
|
||||
|
||||
$result = TaskImage::delete($task_id, $file_names);
|
||||
RestApi::response($result);
|
||||
}
|
||||
|
||||
// Изменение порядка и колонки задачи
|
||||
if ($action === 'update_order') {
|
||||
$id = $data['id'] ?? null;
|
||||
$column_id = $data['column_id'] ?? null;
|
||||
$to_index = $data['to_index'] ?? 0;
|
||||
|
||||
$result = Task::updateOrder($id, $column_id, $to_index);
|
||||
RestApi::response($result);
|
||||
}
|
||||
|
||||
// Обновление задачи
|
||||
if ($action === 'update') {
|
||||
$task->id = $data['id'] ?? null;
|
||||
$task->id_department = $data['id_department'] ?? null;
|
||||
$task->id_label = $data['id_label'] ?? null;
|
||||
$task->id_account = $data['id_account'] ?? null;
|
||||
$task->column_id = $data['column_id'] ?? null;
|
||||
$task->order = $data['order'] ?? null;
|
||||
$task->date = $data['date'] ?? null;
|
||||
$task->title = $data['title'] ?? '';
|
||||
$task->descript = $data['descript'] ?? '';
|
||||
$task->descript_full = $data['descript_full'] ?? '';
|
||||
|
||||
$result = $task->update();
|
||||
RestApi::response($result);
|
||||
}
|
||||
|
||||
// Создание задачи
|
||||
if ($action === 'create') {
|
||||
$task->id_department = $data['id_department'] ?? null;
|
||||
$task->id_label = $data['id_label'] ?? null;
|
||||
$task->id_account = $data['id_account'] ?? null;
|
||||
$task->column_id = $data['column_id'] ?? null;
|
||||
$task->order = $data['order'] ?? 0;
|
||||
$task->date = $data['date'] ?? null;
|
||||
$task->title = $data['title'] ?? '';
|
||||
$task->descript = $data['descript'] ?? '';
|
||||
$task->descript_full = $data['descript_full'] ?? '';
|
||||
$files = $data['files'] ?? [];
|
||||
|
||||
$result = $task->create($files);
|
||||
RestApi::response($result);
|
||||
}
|
||||
|
||||
// Удаление задачи
|
||||
if ($action === 'delete') {
|
||||
$id = $data['id'] ?? null;
|
||||
$result = Task::delete($id);
|
||||
RestApi::response($result);
|
||||
}
|
||||
|
||||
// Метод не указан
|
||||
if (!$action) {
|
||||
RestApi::response(['success' => false, 'error' => 'Укажите метод'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
// Получение всех задач
|
||||
$task = new Task();
|
||||
$tasks = $task->getAll();
|
||||
|
||||
RestApi::response(['success' => true, 'data' => $tasks]);
|
||||
}
|
||||
|
||||
?>
|
||||
248
backend/app/class/enity/class_task.php
Normal file
248
backend/app/class/enity/class_task.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
class Task extends BaseEntity {
|
||||
|
||||
protected $db_name = 'cards_task';
|
||||
|
||||
// Свойства задачи
|
||||
public $id;
|
||||
public $id_department;
|
||||
public $id_label;
|
||||
public $order;
|
||||
public $column_id;
|
||||
public $date;
|
||||
public $id_account;
|
||||
public $title;
|
||||
public $descript;
|
||||
public $descript_full;
|
||||
|
||||
// Валидация данных
|
||||
protected function validate() {
|
||||
static::$error_message = [];
|
||||
|
||||
if (!$this->id) {
|
||||
$this->addError('id', 'ID задачи не указан');
|
||||
}
|
||||
if (!$this->title) {
|
||||
$this->addError('title', 'Название не может быть пустым');
|
||||
}
|
||||
|
||||
return $this->getErrors();
|
||||
}
|
||||
|
||||
// Валидация данных (для create)
|
||||
protected function validateCreate() {
|
||||
static::$error_message = [];
|
||||
|
||||
if (!$this->title) {
|
||||
$this->addError('title', 'Название не может быть пустым');
|
||||
}
|
||||
if (!$this->column_id) {
|
||||
$this->addError('column_id', 'Колонка не указана');
|
||||
}
|
||||
if (!$this->id_department) {
|
||||
$this->addError('id_department', 'Департамент не указан');
|
||||
}
|
||||
|
||||
return $this->getErrors();
|
||||
}
|
||||
|
||||
// Создание задачи (с файлами)
|
||||
public function create($files = []) {
|
||||
// Валидация
|
||||
if ($errors = $this->validateCreate()) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Вставляем в базу
|
||||
Database::insert($this->db_name, [
|
||||
'id_department' => $this->id_department,
|
||||
'id_label' => $this->id_label,
|
||||
'order' => $this->order ?? 0,
|
||||
'column_id' => $this->column_id,
|
||||
'date' => $this->date ?: null,
|
||||
'id_account' => $this->id_account,
|
||||
'title' => $this->title,
|
||||
'descript' => $this->descript ?: null,
|
||||
'descript_full' => $this->descript_full ?: null,
|
||||
'date_create' => date('Y-m-d H:i:s'),
|
||||
'file_img' => '[]'
|
||||
]);
|
||||
|
||||
// Получаем ID созданной задачи
|
||||
$this->id = Database::id();
|
||||
|
||||
// Загружаем файлы если есть
|
||||
$uploaded_files = [];
|
||||
if (!empty($files)) {
|
||||
foreach ($files as $file) {
|
||||
$result = TaskImage::upload($this->id, $file['data'], $file['name']);
|
||||
if ($result['success']) {
|
||||
$uploaded_files[] = $result['file'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'id' => $this->id,
|
||||
'files' => $uploaded_files
|
||||
];
|
||||
}
|
||||
|
||||
// Обновление задачи
|
||||
public function update() {
|
||||
// Валидация
|
||||
if ($errors = $this->validate()) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Проверка что задача существует
|
||||
$task = Database::get($this->db_name, ['id'], ['id' => $this->id]);
|
||||
if (!$task) {
|
||||
$this->addError('task', 'Задача не найдена');
|
||||
return $this->getErrors();
|
||||
}
|
||||
|
||||
// Обновляем в БД
|
||||
Database::update($this->db_name, [
|
||||
'id_department' => $this->id_department,
|
||||
'id_label' => $this->id_label,
|
||||
'order' => $this->order,
|
||||
'column_id' => $this->column_id,
|
||||
'date' => $this->date ?: null,
|
||||
'id_account' => $this->id_account,
|
||||
'title' => $this->title,
|
||||
'descript' => $this->descript ?: null,
|
||||
'descript_full' => $this->descript_full ?: null
|
||||
], [
|
||||
'id' => $this->id
|
||||
]);
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
// Удаление задачи
|
||||
public static function delete($id) {
|
||||
// Проверка что задача существует
|
||||
self::check_task($id);
|
||||
|
||||
// Удаляем папку с файлами если есть
|
||||
$upload_dir = __DIR__ . '/../../../public/img/task/' . $id;
|
||||
if (is_dir($upload_dir)) {
|
||||
$files = glob($upload_dir . '/*');
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
rmdir($upload_dir);
|
||||
}
|
||||
|
||||
// Удаляем из базы
|
||||
Database::delete('cards_task', ['id' => $id]);
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
// Изменение порядка и колонки задачи (с пересчётом order)
|
||||
public static function updateOrder($id, $column_id, $to_index) {
|
||||
|
||||
// Проверка что задача существует
|
||||
self::check_task($id);
|
||||
|
||||
// Получаем все карточки целевой колонки (кроме перемещаемой)
|
||||
$cards = Database::select('cards_task', ['id', 'order'], [
|
||||
'column_id' => $column_id,
|
||||
'id[!]' => $id,
|
||||
'ORDER' => ['order' => 'ASC']
|
||||
]) ?? [];
|
||||
|
||||
// Вставляем перемещаемую карточку в нужную позицию
|
||||
array_splice($cards, $to_index, 0, [['id' => $id]]);
|
||||
|
||||
// Пересчитываем order для всех карточек
|
||||
foreach ($cards as $index => $card) {
|
||||
Database::update('cards_task', [
|
||||
'order' => $index,
|
||||
'column_id' => $column_id
|
||||
], [
|
||||
'id' => $card['id']
|
||||
]);
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
// Получение всех задач
|
||||
public function getAll() {
|
||||
$tasks = Database::select($this->db_name, [
|
||||
'id',
|
||||
'id_department',
|
||||
'id_label',
|
||||
'id_account',
|
||||
'order',
|
||||
'column_id',
|
||||
'date',
|
||||
'date_create',
|
||||
'file_img',
|
||||
'title',
|
||||
'descript',
|
||||
'descript_full'
|
||||
]);
|
||||
|
||||
// Декодируем JSON и получаем avatar_url из accounts
|
||||
return array_map(function($task) {
|
||||
$task['file_img'] = json_decode($task['file_img'], true) ?? [];
|
||||
|
||||
// Получаем avatar_url из accounts по id_account
|
||||
if ($task['id_account']) {
|
||||
$account = Database::get('accounts', ['avatar_url'], ['id' => $task['id_account']]);
|
||||
$task['avatar_img'] = $account['avatar_url'] ?? null;
|
||||
} else {
|
||||
$task['avatar_img'] = null;
|
||||
}
|
||||
|
||||
return $task;
|
||||
}, $tasks);
|
||||
}
|
||||
|
||||
// Получение всех колонок
|
||||
public function getColumns() {
|
||||
return Database::select('columns', [
|
||||
'id',
|
||||
'name_columns',
|
||||
'color'
|
||||
]);
|
||||
}
|
||||
|
||||
// Получение всех департаментов
|
||||
public function getDepartments() {
|
||||
return Database::select('departments', [
|
||||
'id',
|
||||
'name_departments',
|
||||
'color'
|
||||
]);
|
||||
}
|
||||
|
||||
// Получение всех меток
|
||||
public function getLabels() {
|
||||
return Database::select('labels', [
|
||||
'id',
|
||||
'name_labels',
|
||||
'icon',
|
||||
'color'
|
||||
]);
|
||||
}
|
||||
|
||||
// Проверка и получение задачи (при ошибке — сразу ответ и exit)
|
||||
public static function check_task($task_id) {
|
||||
$task = Database::get('cards_task', '*', ['id' => $task_id]);
|
||||
if (!$task_id || !$task) {
|
||||
RestApi::response(['success' => false, 'errors' => ['task' => 'Задача не найдена']], 400);
|
||||
}
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
163
backend/app/class/enity/class_taskImage.php
Normal file
163
backend/app/class/enity/class_taskImage.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
class TaskImage {
|
||||
|
||||
protected static $db_name = 'cards_task';
|
||||
protected static $upload_path = '/public/img/task/';
|
||||
|
||||
// Валидация всех данных для загрузки
|
||||
protected static function validate($task_id, $file_base64, $file_name) {
|
||||
// Проверка и получение задачи
|
||||
$task = Task::check_task($task_id);
|
||||
|
||||
// Декодируем base64
|
||||
$file_data = base64_decode(preg_replace('/^data:image\/\w+;base64,/', '', $file_base64));
|
||||
if (!$file_data) {
|
||||
return ['success' => false, 'errors' => ['file' => 'Ошибка декодирования файла']];
|
||||
}
|
||||
|
||||
// Проверка расширения
|
||||
$allowed_ext = ['png', 'jpg', 'jpeg'];
|
||||
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowed_ext)) {
|
||||
return ['success' => false, 'errors' => ['file' => 'Разрешены только PNG, JPG, JPEG']];
|
||||
}
|
||||
|
||||
// Проверка размера (10 МБ)
|
||||
$max_size = 10 * 1024 * 1024;
|
||||
if (strlen($file_data) > $max_size) {
|
||||
return ['success' => false, 'errors' => ['file' => 'Файл слишком большой. Максимум 10 МБ']];
|
||||
}
|
||||
|
||||
// Проверка MIME типа
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->buffer($file_data);
|
||||
$allowed_mime = ['image/png', 'image/jpeg'];
|
||||
if (!in_array($mime, $allowed_mime)) {
|
||||
return ['success' => false, 'errors' => ['file' => 'Недопустимый тип файла']];
|
||||
}
|
||||
|
||||
// Всё ок — возвращаем данные
|
||||
return [
|
||||
'task' => $task,
|
||||
'file_data' => $file_data
|
||||
];
|
||||
}
|
||||
|
||||
// Генерация уникального имени файла
|
||||
protected static function getUniqueName($upload_dir, $file_name) {
|
||||
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
|
||||
$base_name = pathinfo($file_name, PATHINFO_FILENAME);
|
||||
$final_name = $file_name;
|
||||
$counter = 1;
|
||||
|
||||
while (file_exists($upload_dir . '/' . $final_name)) {
|
||||
$final_name = $base_name . '_' . $counter . '.' . $ext;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $final_name;
|
||||
}
|
||||
|
||||
// Загрузка изображения
|
||||
public static function upload($task_id, $file_base64, $file_name) {
|
||||
// Валидация
|
||||
$validation = self::validate($task_id, $file_base64, $file_name);
|
||||
if (isset($validation['success'])) return $validation;
|
||||
|
||||
$task = $validation['task'];
|
||||
$file_data = $validation['file_data'];
|
||||
|
||||
// Путь к папке
|
||||
$upload_dir = __DIR__ . '/../../../public/img/task/' . $task_id;
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0755, true);
|
||||
}
|
||||
|
||||
// Уникальное имя
|
||||
$final_name = self::getUniqueName($upload_dir, $file_name);
|
||||
|
||||
// Сохранение файла
|
||||
$file_path = $upload_dir . '/' . $final_name;
|
||||
if (!file_put_contents($file_path, $file_data)) {
|
||||
return ['success' => false, 'errors' => ['file' => 'Ошибка сохранения файла']];
|
||||
}
|
||||
|
||||
// Формируем URL
|
||||
$file_url = self::$upload_path . $task_id . '/' . $final_name;
|
||||
$file_size = strlen($file_data);
|
||||
|
||||
// Обновляем file_img в базе
|
||||
$current_files = json_decode($task['file_img'], true) ?? [];
|
||||
$current_files[] = [
|
||||
'url' => $file_url,
|
||||
'name' => $final_name,
|
||||
'size' => $file_size
|
||||
];
|
||||
|
||||
Database::update(self::$db_name, [
|
||||
'file_img' => json_encode($current_files, JSON_UNESCAPED_UNICODE)
|
||||
], [
|
||||
'id' => $task_id
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'file' => [
|
||||
'url' => $file_url,
|
||||
'name' => $final_name,
|
||||
'size' => $file_size
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Удаление изображений (принимает строку или массив имён файлов)
|
||||
public static function delete($task_id, $file_names) {
|
||||
// Проверка и получение задачи
|
||||
$task = Task::check_task($task_id);
|
||||
|
||||
// Приводим к массиву если передана строка
|
||||
if (!is_array($file_names)) {
|
||||
$file_names = [$file_names];
|
||||
}
|
||||
|
||||
// Получаем текущие файлы
|
||||
$current_files = json_decode($task['file_img'], true) ?? [];
|
||||
$upload_dir = __DIR__ . '/../../../public/img/task/' . $task_id;
|
||||
$deleted = [];
|
||||
|
||||
// Удаляем каждый файл
|
||||
foreach ($file_names as $file_name) {
|
||||
// Ищем файл в массиве
|
||||
foreach ($current_files as $index => $file) {
|
||||
if ($file['name'] === $file_name) {
|
||||
// Удаляем файл с диска
|
||||
$file_path = $upload_dir . '/' . $file_name;
|
||||
if (file_exists($file_path)) {
|
||||
unlink($file_path);
|
||||
}
|
||||
// Удаляем из массива
|
||||
array_splice($current_files, $index, 1);
|
||||
$deleted[] = $file_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем в базе
|
||||
Database::update(self::$db_name, [
|
||||
'file_img' => json_encode($current_files, JSON_UNESCAPED_UNICODE)
|
||||
], [
|
||||
'id' => $task_id
|
||||
]);
|
||||
|
||||
// Удаляем папку если она пустая
|
||||
if (is_dir($upload_dir) && count(scandir($upload_dir)) === 2) {
|
||||
rmdir($upload_dir);
|
||||
}
|
||||
|
||||
return ['success' => true, 'deleted' => $deleted];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -58,6 +58,7 @@ class Account extends BaseEntity {
|
||||
// Получение всех пользователей
|
||||
public function getAll() {
|
||||
return Database::select($this->db_name, [
|
||||
'id',
|
||||
'id_department',
|
||||
'name',
|
||||
'username',
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
require_once __DIR__ . '/restAPI/class_restApi.php';
|
||||
require_once __DIR__ . '/class/enity/class_base.php';
|
||||
require_once __DIR__ . '/class/enity/class_user.php';
|
||||
require_once __DIR__ . '/class/enity/class_task.php';
|
||||
require_once __DIR__ . '/class/enity/class_taskImage.php';
|
||||
|
||||
// Данные подключения к БД
|
||||
define('DB_HOST', '192.168.1.9');
|
||||
@@ -26,6 +28,9 @@
|
||||
|
||||
$routes = [
|
||||
'/api/user' => 'api/user.php',
|
||||
'/api/task' => 'api/task.php',
|
||||
];
|
||||
|
||||
$publicActions = ['auth_login', 'check_session'];
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,34 @@
|
||||
<?php
|
||||
|
||||
// Игнорирование статических файлов
|
||||
function ignore_favicon() {
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
if (preg_match('/\.(ico|png|jpg|jpeg|gif|css|js|svg|woff|woff2|ttf|eot)$/i', $requestUri)) {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка авторизации для API
|
||||
function check_ApiAuth($publicActions = []) {
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
|
||||
if (strpos($requestUri, '/api/') !== false) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$action = $input['action'] ?? null;
|
||||
|
||||
// Публичные действия — без авторизации
|
||||
|
||||
if (!in_array($action, $publicActions)) {
|
||||
$account = new Account();
|
||||
$result = $account->check_session($_COOKIE['session'] ?? null);
|
||||
if (!$result['success']) {
|
||||
RestApi::response($result, 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Функция роутинга API
|
||||
function handleRouting($routes = []) {
|
||||
|
||||
|
||||
@@ -15,14 +15,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Игнорируем запросы к favicon.ico и другим статическим файлам
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
if (preg_match('/\.(ico|png|jpg|jpeg|gif|css|js|svg|woff|woff2|ttf|eot)$/i', $requestUri)) {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
ignore_favicon();
|
||||
|
||||
check_ApiAuth($publicActions);
|
||||
handleRouting($routes);
|
||||
|
||||
?>
|
||||
|
||||
BIN
backend/public/img/task/10/345.png
Normal file
BIN
backend/public/img/task/10/345.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
backend/public/img/task/12/345.png
Normal file
BIN
backend/public/img/task/12/345.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
backend/public/img/task/8/345.png
Normal file
BIN
backend/public/img/task/8/345.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Reference in New Issue
Block a user