false, 'error' => 'Invalid email']); exit; } if (strlen($password) < 8) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Password must be at least 8 characters']); exit; } // Optional username validation if ($username !== null) { if (strlen($username) < 3 || strlen($username) > 50) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Username must be between 3 and 50 characters']); exit; } // letters/numbers + . _ - ; must start/end with letter/number if (!preg_match('/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/', $username)) { http_response_code(400); echo json_encode([ 'success' => false, 'error' => 'Username may contain letters, numbers, underscore, hyphen, dot, and must start/end with a letter or number' ]); exit; } } $hash = password_hash($password, PASSWORD_DEFAULT); // Standard role id from roles table $stmt = $pdo->prepare("SELECT id FROM roles WHERE name = 'standard' LIMIT 1"); $stmt->execute(); $role_id = (int)$stmt->fetchColumn(); if ($role_id <= 0) { http_response_code(500); echo json_encode(['success' => false, 'error' => "Role 'standard' not found"]); exit; } try { $pdo->beginTransaction(); // Friendly conflict checks (DB unique constraints should still exist) $stmt = $pdo->prepare('SELECT 1 FROM users WHERE email = ? LIMIT 1'); $stmt->execute([$email]); if ($stmt->fetchColumn()) { $pdo->rollBack(); http_response_code(409); echo json_encode(['success' => false, 'error' => 'Email already in use']); exit; } if ($username !== null) { $stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = ? LIMIT 1'); $stmt->execute([$username]); if ($stmt->fetchColumn()) { $pdo->rollBack(); http_response_code(409); echo json_encode(['success' => false, 'error' => 'Username already in use']); exit; } } // Create user (username is nullable) $stmt = $pdo->prepare("INSERT INTO users (email, username, password_hash, role_id) VALUES (?, ?, ?, ?)"); $stmt->execute([$email, $username, $hash, $role_id]); $userId = (int)$pdo->lastInsertId(); // Create default profile for this user $stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)"); $stmt->execute([$userId]); $pdo->commit(); echo json_encode(['success' => true]); } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); http_response_code(409); echo json_encode(['success' => false, 'error' => 'Account already exists']); }