diff --git a/Pixel-Canvas-API/.dclignore b/Pixel-Canvas-API/.dclignore index 7499ff37a..3efd3703b 100644 --- a/Pixel-Canvas-API/.dclignore +++ b/Pixel-Canvas-API/.dclignore @@ -18,3 +18,4 @@ README.md *.zip *.rar src +server diff --git a/Pixel-Canvas-API/images/scene-thumbnail.png b/Pixel-Canvas-API/images/scene-thumbnail.png index b3dc58066..d362d6441 100644 Binary files a/Pixel-Canvas-API/images/scene-thumbnail.png and b/Pixel-Canvas-API/images/scene-thumbnail.png differ diff --git a/Pixel-Canvas-API/server/README.md b/Pixel-Canvas-API/server/README.md new file mode 100644 index 000000000..6fc2b4470 --- /dev/null +++ b/Pixel-Canvas-API/server/README.md @@ -0,0 +1,55 @@ +# Pixel API Project + +This project provides a REST API for managing pixel data. The API allows you to retrieve, add, and update pixel information in a database. The provided examples utilize restdb.io and SQLite databases. Restdb.io offers a straightforward setup, while SQLite offers more control and portability. + +## Getting Started + +These instructions will guide you through setting up the project on your local machine for development and testing purposes. + +### Prerequisites + +- Basic knowledge of REST APIs. +- A code editor like Visual Studio Code. +- [Postman](https://www.postman.com/) for testing the API. + +### Database Setup + +#### Option 1: Restdb.io + +1. Create an account on [restdb.io](https://restdb.io/). +2. Follow the instructions to set up a new database. +3. Restdb.io will automatically generate the API code for you. + +> **Note:** The free version of restdb.io has a rate limit of 1 request per second. + +#### Option 2: SQLite with PHP + +1. Ensure your server has PHP installed. +2. Copy all files from the `/php-sqlite` directory onto your server. +3. Ensure the `.htaccess` file is included as it formats the URL into arguments for the script. + +> **Note:** With SQLite, you get a lightweight compact database in a single file that you can copy and move around. You can inspect the `.sqlite` database using [SQLite Viewer](https://sqliteviewer.app/). + +### API Usage + +- **Get Pixels:** Send a GET request to the `/pixels` endpoint to retrieve all pixel data. +- **Add Pixels:** Send a POST request to the `/pixels` endpoint with pixel data in the request body to add new pixels. +- **Update Pixel:** Send a PUT request to the `/pixels/{id}` endpoint with updated pixel data in the request body to update an existing pixel. + +### Running the API Locally + +- For the restdb.io setup, no further action is needed as it's hosted on restdb.io. +- For the SQLite setup, upload the files to a PHP-enabled server, and access the API via the server's URL. + +### Additional Resources + +- [Restdb.io Documentation](https://restdb.io/docs/) +- [SQLite Documentation](https://www.sqlite.org/docs.html) + +### Contributing + +Feel free to submit issues or pull requests to improve the project. + +### License + +This project is open-source. See the LICENSE file for details. diff --git a/Pixel-Canvas-API/server/php-sqlite/ database.sqlite b/Pixel-Canvas-API/server/php-sqlite/ database.sqlite new file mode 100644 index 000000000..26ff59965 Binary files /dev/null and b/Pixel-Canvas-API/server/php-sqlite/ database.sqlite differ diff --git a/Pixel-Canvas-API/server/php-sqlite/.htaccess b/Pixel-Canvas-API/server/php-sqlite/.htaccess new file mode 100644 index 000000000..a5d35dd23 --- /dev/null +++ b/Pixel-Canvas-API/server/php-sqlite/.htaccess @@ -0,0 +1,5 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^([^/]+)/?$ rest-api.php?file=$1 [QSA,L] +RewriteRule ^([^/]+)/([^/]+)/?$ rest-api.php?file=$1&id=$2 [QSA,L] diff --git a/Pixel-Canvas-API/server/php-sqlite/rest-api.php b/Pixel-Canvas-API/server/php-sqlite/rest-api.php new file mode 100644 index 000000000..8b47edede --- /dev/null +++ b/Pixel-Canvas-API/server/php-sqlite/rest-api.php @@ -0,0 +1,177 @@ +exec("CREATE TABLE IF NOT EXISTS $table ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + posX INTEGER, + posY INTEGER, + hexColor TEXT + )"); +} + + + + +// Check for valid API token +if ($_SERVER['HTTP_X_APIKEY'] !== $apiToken) { + http_response_code(401); // Unauthorized + echo json_encode(['error' => 'Invalid API token']); + exit; +} + +// Function to sanitize file or table names +function sanitizeName($name) { + $name = preg_replace("/[^a-zA-Z0-9]/", '', $name); // Remove non-alphanumeric characters + $name = substr($name, 0, 50); // Limit the length to 50 characters + return $name; +} + +// Get and sanitize the 'table' parameter +$table = isset($_GET['table']) ? sanitizeName($_GET['table']) : 'pixels'; + +// Get and sanitize the 'id' parameter +$id = isset($_GET['id']) ? sanitizeName($_GET['id']) : null; + +// Set up the database connection +try { + $db = new PDO('sqlite: database.sqlite'); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Database connection failed: ' . $e->getMessage()]); + exit; +} + +// Handle different request methods +switch ($_SERVER['REQUEST_METHOD']) { + case 'GET': + getPixels($db, $table); + break; + case 'POST': + initPixels($db, $table); + break; + case 'PUT': + updatePixel($db, $table, $id); + break; + default: + echo json_encode(['message' => 'Method not supported']); + break; +} + +// Returns all pixel in the table, if table does not exist returns ´[]´ +function getPixels($db, $table) { + try { + $query = "SELECT * FROM $table"; + $stmt = $db->prepare($query); + $stmt->execute(); + $pixels = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode($pixels); + } catch (Exception $e) { + http_response_code(200); + echo json_encode([]); + } +} + +// Takes array of pixel data and inits the sqlLite database +// Then returns the array with _id +function initPixels($db, $table) { + ensureTableExists($db, $table); + + $inputData = json_decode(file_get_contents('php://input'), true); + + if ($inputData === null || !is_array($inputData)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid JSON data provided']); + return; + } + + $successfulEntries = []; // Array to collect successfully added entries + $errors = []; // Array to collect error messages + + foreach ($inputData as $entry) { + if (!isset($entry['posX'], $entry['posY'], $entry['hexColor'])) { + $errors[] = 'Missing required fields for a pixel entry.'; + continue; // Skip to next entry if validation fails + } + + try { + $stmt = $db->prepare("INSERT INTO $table (posX, posY, hexColor) VALUES (?, ?, ?)"); + $stmt->execute([$entry['posX'], $entry['posY'], $entry['hexColor']]); + $insertId = $db->lastInsertId(); + $entry['_id'] = intval($insertId); // Add the _id to the entry + $successfulEntries[] = $entry; // Store the entry with _id + } catch (Exception $e) { + $errors[] = $e->getMessage(); // Collect error messages + } + } + + // Check for errors + if (!empty($errors)) { + http_response_code(400); // Bad Request + echo json_encode(['errors' => $errors]); // Return the error messages + } else { + echo json_encode($successfulEntries); // Return the successfully added entries + } +} + +function updatePixel($db, $table, $id) { + + // Ensure the table exists before attempting to update data + ensureTableExists($db, $table); + + // Get input data + $inputData = json_decode(file_get_contents('php://input'), true); + + // Check if inputData is not null and is an array + if ($inputData === null || empty($id)) { + http_response_code(400); + echo json_encode(['error' => 'No JSON data provided or missing id']); + return; + } + + // Validate input data + if (!isset($inputData['posX'], $inputData['posY'], $inputData['hexColor'])) { + http_response_code(400); + echo json_encode(['error' => 'Missing required fields']); + return; + } + + try { + $query = "UPDATE $table SET posX = ?, posY = ?, hexColor = ? WHERE _id = ?"; + $stmt = $db->prepare($query); + $stmt->execute([$inputData['posX'], $inputData['posY'], $inputData['hexColor'], $id]); + + // Check if any row was updated + if ($stmt->rowCount() > 0) { + $updatedData = array_merge(['_id' => $id], $inputData); // Combine _id with input data + echo json_encode($updatedData); // Return the updated data + } else { + http_response_code(404); + echo json_encode(['error' => 'No pixel found with _id ' . $id]); + } + } catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => $e->getMessage()]); + } +} + + +?> diff --git a/Pixel-Canvas-API/src/api.ts b/Pixel-Canvas-API/src/api.ts index a031961b8..65c6b8b12 100644 --- a/Pixel-Canvas-API/src/api.ts +++ b/Pixel-Canvas-API/src/api.ts @@ -1,6 +1,6 @@ // API key here const apiKey = '6518855968885408010c01fc' -const urlRestAPI = 'https://goerliplasapixelcanv-9b7d.restdb.io/rest/pixel' +const urlRestAPI = 'https://goerliplasapixelcanv-9b7d.restdb.io/rest/pixels' // Gets all pixel from database via GET API call export function getDatabase(): Promise { @@ -37,7 +37,7 @@ export function initDatabase(canvasWidth: number, canvasHeight: number, color: s const pixelData = { posX: x, posY: y, - color: color + hexColor: color } pixels.push(pixelData) } @@ -70,7 +70,7 @@ export function updateDatabase(id: string, posX: number, posY: number, color: st const data = { posX: posX, posY: posY, - color: color + hexColor: color } // PUT overrides the pixel in database selected by id diff --git a/Pixel-Canvas-API/src/factory.ts b/Pixel-Canvas-API/src/factory.ts index a20c1a031..eaf9dc9fe 100644 --- a/Pixel-Canvas-API/src/factory.ts +++ b/Pixel-Canvas-API/src/factory.ts @@ -90,7 +90,7 @@ export function createPixel(canvas: Entity, posX: number, posY: number, hexColor // Write pixel color into database via API executeTask(async () => { const updatedPixel = await updateDatabase(id, pos.x, pos.y, playersColor) - console.log(updatedPixel) + console.log('ApiResponse:', updatedPixel) if (!updatedPixel) return // Send new pixel data to all players in scene diff --git a/Pixel-Canvas-API/src/index.ts b/Pixel-Canvas-API/src/index.ts index 0b143a015..1407bcdb7 100644 --- a/Pixel-Canvas-API/src/index.ts +++ b/Pixel-Canvas-API/src/index.ts @@ -1,6 +1,3 @@ -// color pallette -// Screenshot! - import { Entity, engine } from '@dcl/sdk/ecs' import { getDatabase, initDatabase } from './api' import { Quaternion } from '@dcl/sdk/math' @@ -54,8 +51,8 @@ export async function main() { console.log(pixelData) // Fill canvas with pixel from database - pixelData.forEach((pixel: { posX: number; posY: number; color: string; _id: string }) => { - createPixel(canvas, pixel.posX, pixel.posY, pixel.color, pixel._id) + pixelData.forEach((pixel: { posX: number; posY: number; hexColor: string; _id: string }) => { + createPixel(canvas, pixel.posX, pixel.posY, pixel.hexColor, pixel._id) }) // Create color picker diff --git a/Pixel-Canvas-API/src/messageBus.ts b/Pixel-Canvas-API/src/messageBus.ts index 978e07c59..6ca9b31b4 100644 --- a/Pixel-Canvas-API/src/messageBus.ts +++ b/Pixel-Canvas-API/src/messageBus.ts @@ -11,8 +11,7 @@ export const sceneMessageBus = new MessageBus() // So everyone can see the most recent pixels, // without calling API all the time sceneMessageBus.on('updatePixelColor', (newPixel) => { - console.log('MessageBus:') - console.log(newPixel) + console.log('MessageBus :', newPixel) // Get all entities with custom component Pixel const pixels = engine.getEntitiesWith(Pixel) for (const [entity] of pixels) {