From 589ca0d306c8390b13970f50a4af4b1333835477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Da=20Silva?= <73810823+asilvafx@users.noreply.github.com> Date: Sun, 29 Dec 2024 01:08:33 +0100 Subject: [PATCH] crud --- .gitignore | 3 + app/autoload.php | 18 +- app/core/models/Api.php | 355 ++++++++++++++------------------------- app/core/models/Crud.php | 163 ++++++++++++++++++ 4 files changed, 305 insertions(+), 234 deletions(-) create mode 100644 app/core/models/Crud.php diff --git a/.gitignore b/.gitignore index 31246f9..3c571ec 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ package-lock.json .env *.local *.lock +app/vendor +app/vendor/* +public/manifest/* # Editor directories and files .vscode/* diff --git a/app/autoload.php b/app/autoload.php index c2793a9..00271d0 100644 --- a/app/autoload.php +++ b/app/autoload.php @@ -142,7 +142,7 @@ // Load api routes $f3->route('GET|POST|PUT|DELETE /v1/@slug', 'Api->Base'); - $f3->route('GET|PUT|DELETE /v1/@slug/@search/@value', 'Api->Base'); + $f3->route('GET|PUT|DELETE /v1/@slug/@search', 'Api->Base'); // Load WebAuthn Routes $f3->route('GET|POST /web/authn/attestation/options', 'WebAuthn->Options'); @@ -199,19 +199,23 @@ ); // Get 2 char lang code -$lang2 = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); +$lang2 = $f3->get('SITE.lang'); -// Set default language if a `$lang` version of site is not available -if (!in_array($lang2, array_keys($languages))) { - $lang2 = $f3->get('SITE.lang'); +if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){ + $lang2 = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); + // Set default language if a `$lang` version of site is not available + if (!in_array($lang2, array_keys($languages))) { + $lang2 = $f3->get('SITE.lang'); + } } -if (is_null($f3->get('SESSION.locale')) || empty($f3->get('SESSION.locale'))) { +if (empty($f3->get('SESSION.locale'))) { // Auto site translation - $f3->set('SESSION.locale', $languages[$lang2]); + $f3->set('SESSION.locale', $lang2); } $f3->set('YearNow', date("Y")); + // Load application $f3->run(); diff --git a/app/core/models/Api.php b/app/core/models/Api.php index 9495f17..b920750 100644 --- a/app/core/models/Api.php +++ b/app/core/models/Api.php @@ -2,248 +2,149 @@ class Api extends PostController { + private $crud; - function verifyKey($key) - { - global $f3; - global $db; - $response = new Response; - - if ($f3->get('SITE.enable_api') !== true) { - $response->json('error', 'API disabled.'); - return false; + public function __construct() + { + global $db; // Use the global $db for API key verification + $this->crud = new Crud($db); } - try { - // Check if there are any records in the 'api' table - $totalItems = $db->exec('SELECT COUNT(api_key) as totalItems FROM api'); - - if ($totalItems[0]['totalItems'] == 0) { - // If there are no records in the 'api' table, return false - return false; - } - - // If $key is empty, return an error - if (empty($key)) { - $response->json('error', 'API Key missing.'); - return false; - } - - // Decode the API key from the authorization header - $key = htmlspecialchars_decode($key); - - // Load the API key from the database - $api = new DB\SQL\Mapper($db, 'api'); - $api->load(array('api_key=?', $key)); - - // If the API key is not found or disabled, return an error - if ($api->dry()) { - $response->json('error', 'Invalid API Key.'); - return false; - } elseif ($api->status === 0) { - $response->json('error', 'API Key disabled.'); - return false; - } else { - $api->api_usage++; - $api->save(); - return true; - } - } catch (Exception $e) { - $response->json('error', 'Error fetching request. ' . $e->getMessage()); - return false; - } - } - - function Base($f3, $args) - { - global $siteDb; - $body = json_decode(file_get_contents('php://input'), true); - $slug = empty($args['slug']) ? '' : htmlspecialchars_decode($args['slug']); - $search = empty($args['search']) ? '' : htmlspecialchars_decode($args['search']); - $value = empty($args['value']) ? '' : htmlspecialchars_decode($args['value']); - $limit = isset($body['limit']) ? $body['limit'] : $f3->get('GET.limit'); - $data = isset($body['data']) ? $body['data'] : $f3->get('POST.data'); - $values = isset($body['values']) ? $body['values'] : $f3->get('POST.values'); - $key = isset($_SERVER['HTTP_AUTHORIZATION']) ? trim(str_replace('Bearer ', '', $_SERVER['HTTP_AUTHORIZATION'])) : $f3->get('POST.key'); - $requestData = array(); - $response = new Response; - - - switch ($_SERVER['REQUEST_METHOD']) { - case 'POST': - $requestData["method"] = 'post'; - break; - case 'GET': - $requestData["method"] = 'get'; - break; - case 'DELETE': - $requestData["method"] = 'delete'; - break; - case 'PUT': - parse_str(file_get_contents('php://input'), $requestData); - if (!is_array($requestData)) { - $requestData = array(); + function verifyKey($key): bool + { + global $f3; + global $db; // Use the global $db for API key verification + $response = new Response; + + if ($f3->get('SITE.enable_api') !== true) { + $response->json('error', 'API disabled.'); + return false; } - $requestData["method"] = 'put'; - break; - default: - $response->json('error', 'Invalid Endpoint'); - exit; - } - // Verify API Key - if (!$this->verifyKey($key)) { - exit; - } + try { + // Check if there are any records in the 'api' table + $totalItems = $this->crud->read('api'); // Use the read method from Crud - if (!empty($slug)) { - $requestData["collection"] = htmlspecialchars_decode($slug); - } else { - $response->json('error', 'Collection set missing.'); - exit; - } + if (empty($totalItems)) { + return false; + } - $set_limit = !empty($limit) ? 'LIMIT ' . intval($limit) : ""; - $fields_cast = null; - try { - if ($requestData["method"] === 'get') { + if (empty($key)) { + $response->json('error', 'API Key missing.'); + return false; + } - if ($requestData["collection"] === '@all') { - $collections = $siteDb->exec('SELECT * FROM sqlite_master WHERE type="table"'); - } else { - $collections = $siteDb->exec('SELECT * FROM sqlite_master WHERE type="table" AND name=?', [$requestData["collection"]]); - } + // Decode the API key from the authorization header + $key = htmlspecialchars_decode($key); - if (!$collections) { - $response->json('error', 'Collection not found.'); - exit; + // Load the API key from the database + $api = new DB\SQL\Mapper($db, 'api'); // Use the global $db + $api->load(array('api_key=?', $key)); + + if ($api->dry()) { + $response->json('error', 'Invalid API Key.'); + return false; + } elseif ($api->status === 0) { + $response->json('error', 'API Key disabled.'); + return false; + } else { + $api->api_usage++; + $api->save(); + return true; + } + } catch (Exception $e) { + $response->json('error', 'Error fetching request. ' . $e->getMessage()); + return false; } + } - if (!empty($search) && !empty($value)) { - $fields_cast = $siteDb->exec('SELECT * FROM ' . $requestData["collection"] . ' WHERE ' . $search . '=? ' . $set_limit, [$value]); + function Base($f3, $args) + { + global $siteDb; // Use the global $siteDb for CRUD operations + $body = json_decode(file_get_contents('php://input'), true); + $slug = empty($args['slug']) ? '' : htmlspecialchars_decode($args['slug']); + $search = empty($args['search']) ? '' : htmlspecialchars_decode($args['search']); + $key = isset($_SERVER['HTTP_AUTHORIZATION']) ? trim(str_replace('Bearer ', '', $_SERVER['HTTP_AUTHORIZATION'])) : $f3->get('POST.key'); + $response = new Response; + + // Verify API Key + if (!$this->verifyKey($key)) { + exit; + } + if (!empty($slug)) { + $requestData["collection"] = htmlspecialchars_decode($slug); } else { - $fields_cast = $siteDb->exec('SELECT * FROM ' . $requestData["collection"] . ' ' . $set_limit); + $response->json('error', 'Collection set missing.'); + exit; } - $response->json('success', $fields_cast); - - } elseif ($requestData["method"] === 'post') { - if ($requestData["collection"] === 'UPLOAD' && isset($_FILES['file'])) { - $utils = new Utils; - $dir = isset($body['dir']) ? $body['dir'] : $f3->get('POST.dir'); - $uploadDir = !empty($dir) ? $dir : 'public/uploads/'; // Directory where files will be uploaded - - $uploadResult = $utils->uploadFile($_FILES['file'], $uploadDir); // Call the uploadFile function - - if (!$uploadResult) { - $response->json('error', 'Failed to upload file.'); - } else { - $response->json('success', $uploadResult); // Return the uploaded file URL - } - exit; - } - // Validate that the body is not empty - if (!empty($body) && is_array($body)) { - $tableName = $requestData["collection"]; - - // Extract columns and values dynamically - $columns = array_keys($body); // Get column names - $values = array_values($body); // Get corresponding values - - // Quote string values manually and convert arrays/objects to JSON - $quotedValues = array_map(function($value) { - if (is_array($value) || is_object($value)) { - return "'" . addslashes(json_encode($value)) . "'"; // Convert arrays/objects to JSON - } elseif (is_string($value)) { - return "'" . addslashes($value) . "'"; // Add quotes around strings - } - return $value; // Leave other types (numbers) as is - }, $values); - - // Implode columns and quoted values to create the SQL statement - $sql = 'INSERT INTO ' . $tableName . ' (' . implode(",", $columns) . ') VALUES (' . implode(",", $quotedValues) . ')'; - - // Execute the query - $result = $siteDb->exec($sql); - - if ($result) { - $response->json('success', 'Data added successfully.'); - } else { - $response->json('error', 'Failed to add data.'); - } - } else { - $response->json('error', 'Missing or invalid data in request body.'); - exit; - } - } elseif ($requestData["method"] === 'put') { - if (!empty($search) && !empty($value) && !empty($body) && is_array($body)) { - $tableName = $requestData["collection"]; - - // Extract columns and values dynamically - $columns = array_keys($body); // Get column names - $values = array_values($body); // Get corresponding values - - // Prepare the SET part of the SQL statement - $setParts = []; - foreach ($columns as $index => $column) { - if (is_array($values[$index]) || is_object($values[$index])) { - // Convert arrays/objects to JSON - $setParts[] = "$column = '" . addslashes(json_encode($values[$index])) . "'"; - } elseif (is_string($values[$index])) { - // Quote string values - $setParts[] = "$column = '" . addslashes($values[$index]) . "'"; - } else { - // Leave other types (numbers) as is - $setParts[] = "$column = " . $values[$index]; - } - } - $setString = implode(", ", $setParts); - - // Prepare the SQL statement - $sql = "UPDATE $tableName SET $setString WHERE $search = :value"; - - // Prepare and execute the statement - $stmt = $siteDb->prepare($sql); - $stmt->bindParam(':value', $value); // Bind the search value - $result = $stmt->execute(); - - if ($result) { - $response->json('success', 'Data updated successfully.'); - } else { - $response->json('error', 'Failed to update data.'); - } - } else { - $response->json('error', 'Missing data or values.'); - } - } elseif ($requestData["method"] === 'delete') { - - if (!empty($search) && !empty($value)) { - $tableName = $requestData["collection"]; - - // Prepare the SQL statement - $sql = "DELETE FROM $tableName WHERE $search = :value"; - - // Prepare and execute the statement - $stmt = $siteDb->prepare($sql); - $stmt->bindParam(':value', $value); // Bind the search value - $result = $stmt->execute(); - - if ($result) { - $response->json('success', 'Deleted from collection successfully.'); - } else { - $response->json('error', 'Failed to delete data.'); - } - } else { - $response->json('error', 'Missing data.'); + switch ($_SERVER['REQUEST_METHOD']) { + case 'POST': + if (!empty($body) && is_array($body)) { + $tableName = $requestData["collection"]; + $crud = new Crud($siteDb); // Use the global $siteDb for CRUD operations + $id = $crud->create($tableName, $body); + $response->json('success', ['id' => $id, 'message' => 'Data added successfully.']); + } else { + $response->json('error', 'Missing or invalid data in request body.'); + } + break; + + case 'GET': + $crud = new Crud($siteDb); // Use the global $siteDb for CRUD operations + + if (!empty($search) && is_numeric($search)) { + // If search is set and is numeric, fetch the specific item by ID + $data = $crud->readById($requestData["collection"], (int)$search); + } else { + // Otherwise, fetch all items from the table + $data = $crud->read($requestData["collection"]); + } + + // Check if the response indicates an error + if ($data['status'] === 'error') { + // Return the error response directly + $response->json('error', $data['message']); + } else { + // Return the success response + $response->json('success', $data['message']); + } + break; + + case 'PUT': + if (!empty($search) && is_numeric($search) && !empty($body) && is_array($body)) { + $crud = new Crud($siteDb); // Use the global $siteDb for CRUD operations + $result = $crud->update($requestData["collection"], $search, $body); + if ($result['status'] === 'success') { + $response->json('success', $result['message']); + } else { + $response->json('error', $result['message']); + } + } else { + $response->json('error', 'Invalid data or values.'); + } + break; + + case 'DELETE': + if (!empty($search) && is_numeric($search)) { + $crud = new Crud($siteDb); // Use the global $siteDb for CRUD operations + $result = $crud->erase($requestData["collection"], $search); + if ($result['status'] === 'success') { + $response->json('success', $result['message']); + } else { + $response->json('error', $result['message']); + } + } else { + $response->json('error', 'Invalid data or values.'); + } + break; + + default: + $response->json('error', 'Invalid Endpoint'); + exit; } - exit; - } - } catch (Exception $e) { - $response->json('error', 'Invalid data. Error: ' . $e->getMessage()); + exit; } - - exit; - } -} +} \ No newline at end of file diff --git a/app/core/models/Crud.php b/app/core/models/Crud.php new file mode 100644 index 0000000..6e1f0fd --- /dev/null +++ b/app/core/models/Crud.php @@ -0,0 +1,163 @@ +db = $db; + } + + // Function to check if a column exists in a table + private function columnExists(string $tableName, string $columnName): bool + { + $sql = "PRAGMA table_info($tableName)"; + $stmt = $this->db->query($sql); + $columns = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($columns as $column) { + if ($column['name'] === $columnName) { + return true; + } + } + return false; + } + + // Function to add a new column to a table + private function addColumn(string $tableName, string $columnName, string $columnType): void + { + $sql = "ALTER TABLE $tableName ADD COLUMN $columnName $columnType"; + $this->db->exec($sql); + } + + // Function to create a table if it doesn't exist + private function createTableIfNotExists(string $tableName): void + { + $createTableSQL = "CREATE TABLE IF NOT EXISTS $tableName (id INTEGER PRIMARY KEY AUTOINCREMENT)"; + $this->db->exec($createTableSQL); + } + + // CREATE + public function create(string $tableName, array $data): int + { + // Create table if it doesn't exist + $this->createTableIfNotExists($tableName); + + // Prepare the insert statement + $columns = array_keys($data); + $placeholders = implode(", ", array_fill(0, count($columns), '?')); + $values = array_values($data); + + // Check and add columns if they don't exist + foreach ($columns as $column) { + if (!$this->columnExists($tableName, $column)) { + // Assuming all new columns are TEXT for simplicity + $this->addColumn($tableName, $column, 'TEXT'); + } + } + + // Now insert the data + $sql = "INSERT INTO $tableName (" . implode(", ", $columns) . ") VALUES ($placeholders)"; + try { + $stmt = $this->db->prepare($sql); + $stmt->execute($values); + return (int)$this->db->lastInsertId(); + } catch (PDOException $e) { + echo json_encode([ + "status" => "error", + "message" => "Error inserting data: " . $e->getMessage() + ]); + return 0; // or throw an exception + } + } + + // READ + public function read(string $tableName): array + { + $sql = "SELECT * FROM $tableName"; + try { + $stmt = $this->db->query($sql); + return [ + "status" => "success", + "message" => $stmt->fetchAll(PDO::FETCH_ASSOC) + ]; + } catch (PDOException $e) { + return [ + "status" => "error", + "message" => $e->getMessage() + ]; + } + } + + // Function to read a specific item by ID + public function readById(string $tableName, int $id): array + { + $sql = "SELECT * FROM $tableName WHERE id = ?"; + try { + $stmt = $this->db->prepare($sql); + $stmt->execute([$id]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($result) { + return [ + "status" => "success", + "message" => $result + ]; + } else { + return [ + "status" => "error", + "message" => "No item found with ID: $id" + ]; + } + } catch (PDOException $e) { + return [ + "status" => "error", + "message" => "Error reading data: " . $e->getMessage() + ]; + } + } + + // UPDATE + public function update(string $tableName, int $id, array $data): array // Change return type to array + { + $updates = implode(", ", array_map(fn($key) => "$key = ?", array_keys($data))); + $sql = "UPDATE $tableName SET $updates WHERE id = ?"; + + try { + $stmt = $this->db->prepare($sql); + $stmt->execute([...array_values($data), $id]); + return [ + "status" => "success", + "message" => "Data updated successfully." + ]; // Return success message + } catch (PDOException $e) { + return [ + "status" => "error", + "message" => $e->getMessage() + ]; // Return error message + } + } + + // DELETE + public function erase(string $tableName, int $id): array // Change return type to array + { + $sql = "DELETE FROM $tableName WHERE id = ?"; + + try { + $stmt = $this->db->prepare($sql); + $stmt->execute([$id]); + return [ + "status" => "success", + "message" => "Deleted from collection successfully." + ]; // Return success message + } catch (PDOException $e) { + return [ + "status" => "error", + "message" => $e->getMessage() + ]; // Return error message + } + } + + // Get the database connection + public function getDb() { + return $this->db; // Return the current database connection + } +} \ No newline at end of file