1
0
Files
TaskBoard/front_vue/src/api.js
Falknat 3bfa1e9e1b Комментарии, файлы и права проекта
- Система комментариев к задачам с вложенными ответами
- Редактирование и удаление комментариев
- Прикрепление файлов к задачам и комментариям (картинки, архивы до 10 МБ)
- Система прав проекта: админ проекта может удалять чужие комментарии и файлы
- Универсальный класс FileUpload для загрузки файлов
- Защита загрузки: только автор комментария может добавлять файлы
- Каскадное удаление: задача → комментарии → файлы
- Автообновление комментариев в реальном времени
2026-01-15 06:40:47 +07:00

206 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Базовый URL API (берётся из внешнего config.js)
const API_BASE = window.APP_CONFIG?.API_BASE || ''
// Формирование полного URL (добавляет домен к относительным путям)
export const getFullUrl = (url) => {
if (!url) return ''
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('data:')) {
return url
}
return API_BASE + url
}
// Базовая функция запроса
const request = async (endpoint, options = {}) => {
const res = await fetch(`${API_BASE}${endpoint}`, options)
return res.json()
}
// ==================== AUTH ====================
export const authApi = {
login: (username, password) => request('/api/user', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'auth_login', username, password })
}),
check: () => request('/api/user', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'check_session' })
}),
logout: () => request('/api/user', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'logout' })
})
}
// ==================== PROJECTS ====================
export const projectsApi = {
// active: ID проекта для загрузки данных (опционально)
getAll: (active = null) => {
let url = '/api/project'
if (active) url += `?active=${active}`
return request(url, { credentials: 'include' })
},
// Получение данных проекта (проект + колонки + отделы)
getData: (id_project) => request('/api/project', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'get_project_data', id_project })
})
}
// ==================== CARDS ====================
export const cardsApi = {
// id_project: ID проекта (обязательный)
// archive: 0 = неархивные (по умолчанию), 1 = архивные, 'all' = все
getAll: (id_project, archive = 0) => {
let url = `/api/task?id_project=${id_project}`
if (archive !== 0) url += `&archive=${archive}`
return request(url, { credentials: 'include' })
},
updateOrder: (id, column_id, to_index) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'update_order', id, column_id, to_index })
}),
create: (data) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'create', ...data })
}),
update: (data) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'update', ...data })
}),
delete: (id) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete', id })
}),
setArchive: (id, archive = 1) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'set_archive', id, archive })
})
}
// ==================== TASK IMAGES ====================
export const taskImageApi = {
upload: (task_id, file_data, file_name) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'upload_image', task_id, file_data, file_name })
}),
// Принимает строку (один файл) или массив (несколько файлов)
delete: (task_id, file_names) => request('/api/task', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_image', task_id, file_names })
})
}
// ==================== COMMENT IMAGES ====================
export const commentImageApi = {
upload: (comment_id, file_data, file_name) => request('/api/comment', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'upload_image', comment_id, file_data, file_name })
}),
// Принимает строку (один файл) или массив (несколько файлов)
delete: (comment_id, file_names) => request('/api/comment', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_image', comment_id, file_names })
})
}
// ==================== USERS ====================
export const usersApi = {
getAll: () => request('/api/user', { credentials: 'include' })
}
// ==================== SERVER ====================
export const serverApi = {
// Получение настроек сервера (timezone и т.д.) — публичный action
getSettings: () => request('/api/server', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'get_settings' })
})
}
// Хранилище серверных настроек
export const serverSettings = {
timezoneOffset: '+03:00', // дефолт, обновится при загрузке
// Инициализация — вызвать один раз при старте приложения
async init() {
try {
const result = await serverApi.getSettings()
if (result.success) {
this.timezoneOffset = result.data.timezone_offset
}
} catch (e) {
console.warn('Не удалось получить настройки сервера:', e)
}
},
// Парсинг даты с сервера с учётом таймзоны
parseDate(dateStr) {
if (!dateStr) return null
// Добавляем таймзону сервера для корректного парсинга
const normalized = dateStr.replace(' ', 'T')
// Если уже есть таймзона — не добавляем
if (normalized.includes('+') || normalized.includes('Z')) {
return new Date(normalized)
}
return new Date(normalized + this.timezoneOffset)
}
}
// ==================== COMMENTS ====================
export const commentsApi = {
// Получение комментариев задачи
getByTask: (id_task) => request(`/api/comment?id_task=${id_task}`, { credentials: 'include' }),
// Создание комментария (id_answer — опционально, для ответа на комментарий)
create: (id_task, text, id_answer = null) => request('/api/comment', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'create', id_task, text, id_answer })
}),
// Обновление комментария
update: (id, text) => request('/api/comment', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'update', id, text })
}),
// Удаление комментария
delete: (id) => request('/api/comment', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete', id })
})
}