322 lines
8.5 KiB
PHP
322 lines
8.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../db.php';
|
|
require_once __DIR__ . '/../auth.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
function jsonResponse(array $data, int $status = 200): void
|
|
{
|
|
http_response_code($status);
|
|
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
function getInputData(): array
|
|
{
|
|
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
|
|
|
if (str_contains($contentType, 'application/json')) {
|
|
$raw = file_get_contents('php://input');
|
|
$data = json_decode($raw, true);
|
|
|
|
return is_array($data) ? $data : [];
|
|
}
|
|
|
|
return $_POST;
|
|
}
|
|
|
|
function normalizeDateValue(mixed $value, string $fieldName): ?string
|
|
{
|
|
$value = trim((string)$value);
|
|
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => "Invalid {$fieldName}. Use YYYY-MM-DD."
|
|
], 400);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
function requireProjectAccess(string $projectId, int $userId): void
|
|
{
|
|
requireProjectAccessForUser($userId, $projectId);
|
|
}
|
|
|
|
$user = requireApiAuth();
|
|
|
|
$api = $_GET['api'] ?? '';
|
|
|
|
switch ($api) {
|
|
|
|
case 'ListVersions': {
|
|
$projectId = strtoupper(trim($_GET['project_id'] ?? ''));
|
|
|
|
if ($projectId === '') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Missing project_id.'
|
|
], 400);
|
|
}
|
|
|
|
requireProjectAccess($projectId, (int)$user['id']);
|
|
|
|
$stmt = db()->prepare(
|
|
'SELECT id
|
|
FROM versions
|
|
WHERE project = ?
|
|
ORDER BY created_date DESC, id DESC'
|
|
);
|
|
|
|
$stmt->execute([$projectId]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'versions' => array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN))
|
|
]);
|
|
}
|
|
|
|
case 'VersionInfo': {
|
|
$versionId = (int)($_GET['version_id'] ?? 0);
|
|
|
|
if ($versionId <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Missing or invalid version_id.'
|
|
], 400);
|
|
}
|
|
|
|
$stmt = db()->prepare(
|
|
'SELECT
|
|
versions.id,
|
|
versions.name,
|
|
versions.description,
|
|
versions.created_date,
|
|
versions.due_date,
|
|
versions.released_date,
|
|
versions.project
|
|
FROM versions
|
|
INNER JOIN projects ON projects.id = versions.project
|
|
WHERE versions.id = ?
|
|
LIMIT 1'
|
|
);
|
|
|
|
$stmt->execute([$versionId]);
|
|
|
|
$version = $stmt->fetch();
|
|
|
|
if (!$version) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version not found.'
|
|
], 404);
|
|
}
|
|
|
|
requireProjectAccessForUser((int)$user['id'], (string)$version['project']);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'version' => $version
|
|
]);
|
|
}
|
|
|
|
case 'Create': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Create requires POST.'
|
|
], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
|
|
$projectId = strtoupper(trim($data['project_id'] ?? $data['project'] ?? ''));
|
|
$name = trim($data['name'] ?? '');
|
|
$description = array_key_exists('description', $data) ? trim((string)$data['description']) : null;
|
|
$dueDate = array_key_exists('due_date', $data) ? normalizeDateValue($data['due_date'], 'due_date') : null;
|
|
$releasedDate = array_key_exists('released_date', $data) ? normalizeDateValue($data['released_date'], 'released_date') : null;
|
|
|
|
if ($projectId === '') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Project id is required.'
|
|
], 400);
|
|
}
|
|
|
|
requireProjectRight((int)$user['id'], $projectId, 'Create Versions');
|
|
|
|
if ($name === '') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version name is required.'
|
|
], 400);
|
|
}
|
|
|
|
if (strlen($name) > 128) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version name is too long.'
|
|
], 400);
|
|
}
|
|
|
|
$stmt = db()->prepare(
|
|
'INSERT INTO versions
|
|
(name, description, created_date, due_date, released_date, project)
|
|
VALUES (?, ?, CURDATE(), ?, ?, ?)'
|
|
);
|
|
|
|
$stmt->execute([
|
|
$name,
|
|
$description,
|
|
$dueDate,
|
|
$releasedDate,
|
|
$projectId
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'version_id' => (int)db()->lastInsertId()
|
|
], 201);
|
|
}
|
|
|
|
case 'Edit': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Edit requires POST.'
|
|
], 405);
|
|
}
|
|
|
|
$versionId = (int)($_GET['version_id'] ?? 0);
|
|
|
|
if ($versionId <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Missing or invalid version_id.'
|
|
], 400);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$projectStmt = db()->prepare(
|
|
'SELECT project
|
|
FROM versions
|
|
WHERE id = ?
|
|
LIMIT 1'
|
|
);
|
|
$projectStmt->execute([$versionId]);
|
|
$versionProject = $projectStmt->fetchColumn();
|
|
|
|
if (!$versionProject) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version not found.'
|
|
], 404);
|
|
}
|
|
|
|
requireProjectRight((int)$user['id'], (string)$versionProject, 'Edit Versions');
|
|
|
|
$fields = [];
|
|
$values = [];
|
|
|
|
if (array_key_exists('name', $data)) {
|
|
$name = trim((string)$data['name']);
|
|
|
|
if ($name === '') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version name cannot be empty.'
|
|
], 400);
|
|
}
|
|
|
|
if (strlen($name) > 128) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version name is too long.'
|
|
], 400);
|
|
}
|
|
|
|
$fields[] = 'versions.name = ?';
|
|
$values[] = $name;
|
|
}
|
|
|
|
if (array_key_exists('description', $data)) {
|
|
$description = trim((string)$data['description']);
|
|
|
|
$fields[] = 'versions.description = ?';
|
|
$values[] = $description !== '' ? $description : null;
|
|
}
|
|
|
|
if (array_key_exists('due_date', $data)) {
|
|
$fields[] = 'versions.due_date = ?';
|
|
$values[] = normalizeDateValue($data['due_date'], 'due_date');
|
|
}
|
|
|
|
if (array_key_exists('released_date', $data)) {
|
|
$fields[] = 'versions.released_date = ?';
|
|
$values[] = normalizeDateValue($data['released_date'], 'released_date');
|
|
}
|
|
|
|
if (empty($fields)) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'No editable fields provided.'
|
|
], 400);
|
|
}
|
|
|
|
$values[] = $versionId;
|
|
|
|
$stmt = db()->prepare(
|
|
'UPDATE versions
|
|
INNER JOIN projects ON projects.id = versions.project
|
|
SET ' . implode(', ', $fields) . '
|
|
WHERE versions.id = ?'
|
|
);
|
|
|
|
$stmt->execute($values);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
$existsStmt = db()->prepare(
|
|
'SELECT versions.id
|
|
FROM versions
|
|
INNER JOIN projects ON projects.id = versions.project
|
|
WHERE versions.id = ?
|
|
LIMIT 1'
|
|
);
|
|
|
|
$existsStmt->execute([$versionId]);
|
|
|
|
if ($existsStmt->fetch()) {
|
|
jsonResponse([
|
|
'success' => true,
|
|
'version_id' => $versionId
|
|
]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Version not found.'
|
|
], 404);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'version_id' => $versionId
|
|
]);
|
|
}
|
|
|
|
default: {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Unknown API action.'
|
|
], 404);
|
|
}
|
|
}
|