false, 'error' => 'Project owner must be valid.' ], 400); } $stmt = db()->prepare('SELECT id FROM users WHERE id = ? LIMIT 1'); $stmt->execute([$userId]); if (!$stmt->fetch()) { jsonResponse([ 'success' => false, 'error' => 'Project owner not found.' ], 400); } } $user = requireApiAuth(); $api = $_GET['api'] ?? ''; switch ($api) { case 'ListProjects': { if (userIsAdmin((int)$user['id'])) { $stmt = db()->query( 'SELECT id FROM projects ORDER BY id ASC' ); } else { $stmt = db()->prepare( 'SELECT DISTINCT projects.id FROM projects LEFT JOIN user_access ON user_access.project_id = projects.id AND user_access.user_id = ? WHERE projects.owner = ? OR user_access.id IS NOT NULL ORDER BY projects.id ASC' ); $stmt->execute([(int)$user['id'], (int)$user['id']]); } jsonResponse([ 'success' => true, 'projects' => $stmt->fetchAll(PDO::FETCH_COLUMN) ]); } case 'ProjectInfo': { $projectId = strtoupper(trim($_GET['project_id'] ?? '')); if ($projectId === '') { jsonResponse([ 'success' => false, 'error' => 'Missing project_id.' ], 400); } requireProjectAccessForUser((int)$user['id'], $projectId); $stmt = db()->prepare( 'SELECT id, name, owner, created_date FROM projects WHERE id = ? LIMIT 1' ); $stmt->execute([$projectId]); $project = $stmt->fetch(); if (!$project) { jsonResponse([ 'success' => false, 'error' => 'Project not found.' ], 404); } jsonResponse([ 'success' => true, 'project' => $project ]); } case 'Create': { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse([ 'success' => false, 'error' => 'Create requires POST.' ], 405); } requireUserRight((int)$user['id'], 'Create Projects'); $data = getInputData(); $projectId = strtoupper(trim($data['id'] ?? '')); $name = trim($data['name'] ?? ''); $owner = isset($data['owner']) && $data['owner'] !== '' ? (int)$data['owner'] : (int)$user['id']; if (!preg_match('/^[A-Z0-9]{2,12}$/', $projectId)) { jsonResponse([ 'success' => false, 'error' => 'Project id must be 2-12 characters and contain only A-Z and 0-9.' ], 400); } if ($name === '') { jsonResponse([ 'success' => false, 'error' => 'Project name is required.' ], 400); } if (strlen($name) > 128) { jsonResponse([ 'success' => false, 'error' => 'Project name is too long.' ], 400); } requireUserExists($owner); $stmt = db()->prepare( 'SELECT id FROM projects WHERE id = ? LIMIT 1' ); $stmt->execute([$projectId]); if ($stmt->fetch()) { jsonResponse([ 'success' => false, 'error' => 'Project id already exists.' ], 409); } $stmt = db()->prepare( 'INSERT INTO projects (id, name, owner, created_date) VALUES (?, ?, ?, CURDATE())' ); $stmt->execute([ $projectId, $name, $owner ]); jsonResponse([ 'success' => true, 'project_id' => $projectId ], 201); } case 'Edit': { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse([ 'success' => false, 'error' => 'Edit requires POST.' ], 405); } $projectId = strtoupper(trim($_GET['project_id'] ?? '')); if ($projectId === '') { jsonResponse([ 'success' => false, 'error' => 'Missing project_id.' ], 400); } requireProjectRight((int)$user['id'], $projectId, 'Edit Projects'); $data = getInputData(); $fields = []; $values = []; if (array_key_exists('name', $data)) { $name = trim((string)$data['name']); if ($name === '') { jsonResponse([ 'success' => false, 'error' => 'Project name cannot be empty.' ], 400); } if (strlen($name) > 128) { jsonResponse([ 'success' => false, 'error' => 'Project name is too long.' ], 400); } $fields[] = 'name = ?'; $values[] = $name; } if (array_key_exists('owner', $data)) { $owner = (int)$data['owner']; requireUserExists($owner); $fields[] = 'owner = ?'; $values[] = $owner; } if (empty($fields)) { jsonResponse([ 'success' => false, 'error' => 'No editable fields provided.' ], 400); } $values[] = $projectId; $stmt = db()->prepare( 'UPDATE projects SET ' . implode(', ', $fields) . ' WHERE id = ?' ); $stmt->execute($values); if ($stmt->rowCount() === 0) { $existsStmt = db()->prepare('SELECT id FROM projects WHERE id = ? LIMIT 1'); $existsStmt->execute([$projectId]); if ($existsStmt->fetch()) { jsonResponse([ 'success' => true, 'project_id' => $projectId ]); } jsonResponse([ 'success' => false, 'error' => 'Project not found or nothing changed.' ], 404); } jsonResponse([ 'success' => true, 'project_id' => $projectId ]); } default: { jsonResponse([ 'success' => false, 'error' => 'Unknown API action.' ], 404); } }