diff --git a/.datatable.sql b/.datatable.sql new file mode 100644 index 0000000..154732d --- /dev/null +++ b/.datatable.sql @@ -0,0 +1,159 @@ +-- MySQL dump 10.13 Distrib 8.0.41, for Win64 (x86_64) +-- +-- Host: 127.0.0.1 Database: jsonms_local +-- ------------------------------------------------------ +-- Server version 8.0.41 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `errors` +-- + +DROP TABLE IF EXISTS `errors`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `errors` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) NOT NULL, + `message` text, + `source` varchar(256) DEFAULT NULL, + `line` smallint DEFAULT NULL, + `column` smallint DEFAULT NULL, + `stack` text, + `occurred_on` datetime NOT NULL, + `last_timestamp` bigint NOT NULL, + `version` varchar(16) NOT NULL, + `route` varchar(128) NOT NULL, + `count` smallint NOT NULL DEFAULT '0', + `user_agent` varchar(255) NOT NULL, + `created_on` datetime DEFAULT (now()), + `created_by` int unsigned NOT NULL, + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `errors_pk` (`key`) +) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `history` +-- + +DROP TABLE IF EXISTS `history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `history` ( + `id` int NOT NULL AUTO_INCREMENT, + `uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL, + `content` text COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` int DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `history_interfaces_uuid_fk` (`uuid`), + KEY `history_users_id_fk` (`created_by`), + CONSTRAINT `history_interfaces_uuid_fk` FOREIGN KEY (`uuid`) REFERENCES `structures` (`uuid`) ON DELETE CASCADE, + CONSTRAINT `history_users_id_fk` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=378 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `permissions` +-- + +DROP TABLE IF EXISTS `permissions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `permissions` ( + `id` int NOT NULL AUTO_INCREMENT, + `structure_uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `type` enum('admin','interface') COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=57 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `structures` +-- + +DROP TABLE IF EXISTS `structures`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `structures` ( + `uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT (uuid()), + `hash` char(10) COLLATE utf8mb4_unicode_ci NOT NULL, + `label` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `logo` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `content` text COLLATE utf8mb4_unicode_ci NOT NULL, + `webhook` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_by` int NOT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + KEY `interfaces_users_id_fk` (`created_by`), + KEY `interfaces__hash_index` (`hash`), + KEY `interfaces_webhooks_uuid_fk` (`webhook`), + CONSTRAINT `interfaces_users_id_fk` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`), + CONSTRAINT `interfaces_webhooks_uuid_fk` FOREIGN KEY (`webhook`) REFERENCES `webhooks` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `users` +-- + +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `users` ( + `id` int NOT NULL AUTO_INCREMENT, + `google_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `avatar` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `google_id` (`google_id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `webhooks` +-- + +DROP TABLE IF EXISTS `webhooks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `webhooks` ( + `uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT (uuid()), + `url` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `secret` varchar(84) COLLATE utf8mb4_unicode_ci NOT NULL, + `cypher` varchar(84) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_by` int NOT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + KEY `interfaces_webhooks_users_id_fk` (`created_by`), + CONSTRAINT `interfaces_webhooks_users_id_fk` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2025-04-26 18:13:45 diff --git a/.env.example b/.env.example index 362b5d7..984a801 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,12 @@ # General INTERFACE_EDITOR_URL=http://localhost:3000 ACCESS_CONTROL_ALLOW_ORIGIN=http://localhost:3000 -JSONMS_CYPHER_KEY=urjMdK071cL935eKdczjEQ== +JSONMS_CYPHER_KEY= # Database DATABASE_HOST=localhost DATABASE_DBNAME= -DATABASE_USERNAME=root +DATABASE_USERNAME= DATABASE_PASSWORD= # Google OAuth diff --git a/README.md b/README.md index fcef110..a1c0980 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # @jsonms/server -The server to use with your instance of [jsonms-www](https://github.com/JSON-ms/www). +Welcome to the **@jsonms/server** project! +This package provides the server-side foundation for managing MySQL schemas and environment configurations for the JSONMS system. + +## Requirements +- PHP 8.x +- MySQL server + +## Getting Started + +Follow these steps to set up and run the project locally: + +### 1. Prepare the MySQL Database + +A `.datatable.sql` file is included in the project. +It contains the necessary schema definitions required to run the application. + +To set up your database: + +```bash +mysql -u your_user -p your_database_name < .datatable.sql +``` + +Replace your_user and your_database_name with your MySQL username and target database name. + +Make sure your MySQL server is running and accessible. + +### 2. Set Up Environment Variables +The project uses environment variables for configuration. +A sample file `.env.example` is provided. + +To create your local `.env` file: + +```bash +cp .env.example .env +``` + +Edit `.env` to match your environment settings, such as database connection credentials, ports, and other options. + +### 3. Running the Server + +You can define your own virtual host with Apache or Nginx but the fastest way to run the server is with a built-in PHP server. + +```bash +php -S localhost:9001 index.php +``` + +## Contributing +Contributions are welcome! Please feel free to submit a pull request or open an issue. + +## License +This project is licensed under the BSD-3-Clause License. See the LICENSE file for details. \ No newline at end of file diff --git a/composer.json b/composer.json index 930a198..6dd7695 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "version": "1.0.8", + "version": "1.0.12", "name": "jsonms/server", "description": "The JSON.ms Request Handler Server is a robust backend solution designed to manage and process all incoming requests from the main JSON.ms website.", "license": "BSD-3-Clause", diff --git a/src/controllers/WebhookController.php b/src/controllers/EndpointController.php similarity index 58% rename from src/controllers/WebhookController.php rename to src/controllers/EndpointController.php index ccf201f..8fa4210 100644 --- a/src/controllers/WebhookController.php +++ b/src/controllers/EndpointController.php @@ -2,29 +2,29 @@ use JSONms\Controllers\RestfulController; -class WebhookController extends RestfulController { +class EndpointController extends RestfulController { - public function saveAction($webhooks) { - foreach($webhooks as $webhook) { - if (isset($webhook->uuid)) { - $this->query('update-webhook-by-uuid', [ - 'uuid' => $webhook->uuid, - 'url' => $webhook->url, + public function saveAction($endpoints) { + foreach($endpoints as $endpoint) { + if (isset($endpoint->uuid)) { + $this->query('update-endpoint-by-uuid', [ + 'uuid' => $endpoint->uuid, + 'url' => $endpoint->url, 'userId' => $this->getCurrentUserId(), ]); } else { $cypherKey = $this->getHash(24); $serverSecret = $this->encrypt($this->getHash(24), $cypherKey); $encryptedCypherKey = $this->encrypt($cypherKey, $_ENV['JSONMS_CYPHER_KEY']); - $this->query('insert-webhook', [ - 'url' => $webhook->url, + $this->query('insert-endpoint', [ + 'url' => $endpoint->url, 'secret' => $serverSecret, 'cypher' => $encryptedCypherKey, 'created_by' => $this->getCurrentUserId(), ]); } } - $stmt = $this->query('get-all-webhooks', [ + $stmt = $this->query('get-all-endpoints', [ 'userId' => $this->getCurrentUserId(), ]); @@ -34,7 +34,7 @@ public function saveAction($webhooks) { } public function deleteAction($id) { - $stmt = $this->query('delete-webhook-by-uuid', [ + $stmt = $this->query('delete-endpoint-by-uuid', [ 'uuid' => $id, 'userId' => $this->getCurrentUserId(), ]); @@ -44,20 +44,20 @@ public function deleteAction($id) { } public function secretKeyAction($uuid) { - $webhook = $this->getWebhook($uuid); - $decryptedCypherKey = $this->decrypt($webhook->cypher, $_ENV['JSONMS_CYPHER_KEY']); - $decryptedServerKey = $this->decrypt($webhook->secret, $decryptedCypherKey); + $endpoint = $this->getEndpoint($uuid); + $decryptedCypherKey = $this->decrypt($endpoint->cypher, $_ENV['JSONMS_CYPHER_KEY']); + $decryptedServerKey = $this->decrypt($endpoint->secret, $decryptedCypherKey); $this->responseJson($decryptedServerKey); } public function cypherKeyAction($uuid) { - $webhook = $this->getWebhook($uuid); - $decryptedCypherKey = $this->decrypt($webhook->cypher, $_ENV['JSONMS_CYPHER_KEY']); + $endpoint = $this->getEndpoint($uuid); + $decryptedCypherKey = $this->decrypt($endpoint->cypher, $_ENV['JSONMS_CYPHER_KEY']); $this->responseJson($decryptedCypherKey); } - private function getWebhook($uuid, $showError = true): false | stdClass { - $stmt = $this->query('get-webhook-by-uuid', [ + private function getEndpoint($uuid, $showError = true): false | stdClass { + $stmt = $this->query('get-endpoint-by-uuid', [ 'uuid' => $uuid, 'userId' => $this->getCurrentUserId(), ]); @@ -65,7 +65,7 @@ private function getWebhook($uuid, $showError = true): false | stdClass { return $stmt->fetch(PDO::FETCH_OBJ); } if ($showError) { - throwError(403, 'You don\'t have permission to access this webhook'); + throwError(403, 'You don\'t have permission to access this endpoint'); } return false; } diff --git a/src/controllers/InterfaceController.php b/src/controllers/InterfaceController.php deleted file mode 100644 index 81ad009..0000000 --- a/src/controllers/InterfaceController.php +++ /dev/null @@ -1,131 +0,0 @@ -query('get-all-interfaces', [ - 'userId' => $this->getCurrentUserId(), - ]); - $users = $stmt->fetchAll(); - $this->responseJson($users); - } - - public function getAction($id) { - $interface = $this->getAccessibleInterface($id); - $this->responseJson($interface); - } - - public function createAction($data) { - $hash = $this->getHash(); - - $this->query('insert-interface', [ - 'hash' => $hash, - 'label' => $data->label, - 'logo' => $data->logo, - 'content' => $data->content, - 'webhook' => $data->webhook, - 'created_by' => $this->getCurrentUserId(), - ]); - - // Get inserted interface - $interface = $this->query('get-interface-by-hash', [ - 'hash' => $hash, - ])->fetch(PDO::FETCH_OBJ); - $this->preparePermissions($interface); - - $this->responseJson($interface); - } - - public function updateAction($id, $data) { - $currentInterface = $this->getAccessibleInterface($id); - if ($currentInterface) { - - // Copy current interface to history table - $this->copyToHistory($currentInterface); - - // Update interface - $this->query('update-interface-by-uuid', [ - 'uuid' => $data->uuid, - 'label' => $data->label, - 'logo' => $data->logo, - 'content' => $data->content, - 'webhook' => $data->webhook, - 'userId' => $this->getCurrentUserId(), - ]); - - // Clear all existing permissions (will be added later on) - if ($data->created_by === $this->getCurrentUserId()) { - $this->updatePermissions($data); - } - - $this->responseJson($data); - } - } - - public function deleteAction($id) { - if ($this->hasAccess($id)) { - $stmt = $this->query('delete-interface', [ - 'uuid' => $id, - 'userId' => $this->getCurrentUserId(), - ]); - if ($stmt->rowCount() > 0) { - $this->responseJson(true); - } - } - } - - private function hasAccess($uuid, $showError = true): bool { - return (bool) $this->getAccessibleInterface($uuid, $showError); - } - - private function getAccessibleInterface($uuid, $showError = true): false | stdClass { - $stmt = $this->query('get-accessible-interface-by-uuid', [ - 'uuid' => $uuid, - 'userId' => $this->getCurrentUserId(), - ]); - if ($stmt->rowCount() > 0) { - $interface = $stmt->fetch(PDO::FETCH_OBJ); - $this->preparePermissions($interface); - return $interface; - } - if ($showError) { - throwError(403, 'You don\'t have permission to view this interface'); - } - return false; - } - - private function preparePermissions(&$interface) { - $interface->permission_admin = array_filter(explode(',', $interface->permission_admin ?? '')); - $interface->permission_interface = array_filter(explode(',', $interface->permission_interface ?? '')); - return $interface; - } - - private function copyToHistory(stdClass $interface) { - $this->query('insert-history', [ - 'uuid' => $interface->uuid, - 'content' => $interface->content, - 'userId' => $this->getCurrentUserId(), - ]); - } - - private function updatePermissions($interface) { - - // Delete current permissions - $this->query('delete-user-permissions', [ - 'uuid' => $interface->uuid, - ]); - - // Update with newest permissions - foreach (['interface', 'admin'] as $type) { - foreach ($interface->{'permission_' . $type} as $email) { - $this->query('insert-permissions', [ - 'uuid' => $interface->uuid, - 'type' => $type, - 'email' => $email, - ]); - } - } - } -} diff --git a/src/controllers/SessionController.php b/src/controllers/SessionController.php index 1618233..5773745 100644 --- a/src/controllers/SessionController.php +++ b/src/controllers/SessionController.php @@ -4,22 +4,22 @@ class SessionController extends RestfulController { - private function getDemoInterface() { - $stmt = $this->query('get-demo-interface'); + private function getDemoStructure() { + $stmt = $this->query('get-demo-structure'); if ($stmt->rowCount() > 0) { $rows = $stmt->fetchAll(PDO::FETCH_OBJ); foreach ($rows as $row) { $row->permission_admin = []; - $row->permission_interface = []; - $row->type = 'interface,admin'; + $row->permission_structure = []; + $row->type = 'structure,admin'; return $row; } } return null; } - private function getWebhooks($userId) { - $stmt = $this->query('get-all-webhooks', [ + private function getEndpoints($userId) { + $stmt = $this->query('get-all-endpoints', [ 'userId' => $userId, ]); if ($stmt->rowCount() > 0) { @@ -33,13 +33,13 @@ public function indexAction() { $loggedIn = isset($_SESSION['access_token']) && $_SESSION['access_token']; $user = null; $loginUrl = null; - $interfaces = []; - $webhooks = []; + $structures = []; + $endpoints = []; - // Fetch demo interface - $demo = $this->getDemoInterface(); + // Fetch demo structure + $demo = $this->getDemoStructure(); if ($demo) { - $interfaces[] = $demo; + $structures[] = $demo; } if ($loggedIn) { @@ -88,8 +88,8 @@ public function indexAction() { 'loggedIn' => false, 'user' => $user, 'googleOAuthSignInUrl' => $loginUrl, - 'interfaces' => $interfaces, - 'webhooks' => $webhooks, + 'structures' => $structures, + 'endpoints' => $endpoints, ]); } @@ -105,33 +105,33 @@ public function indexAction() { if ($loggedIn && isset($user)) { - // Fetch all interfaces - $stmt = $this->query('get-all-interfaces', [ + // Fetch all structures + $stmt = $this->query('get-all-structures', [ 'userId' => $this->getCurrentUserId(), ]); - $interfaces = []; + $structures = []; if ($stmt->rowCount() > 0) { $rows = $stmt->fetchAll(PDO::FETCH_OBJ); foreach ($rows as $row) { $row->permission_admin = array_filter(explode(',', $row->permission_admin ?? '')); - $row->permission_interface = array_filter(explode(',', $row->permission_interface ?? '')); - $interfaces[] = $row; + $row->permission_structure = array_filter(explode(',', $row->permission_structure ?? '')); + $structures[] = $row; } } } - // Fetch demo interface + // Fetch demo structure $loggedIn = $loggedIn && isset($user); if ($loggedIn) { - $webhooks = $this->getWebhooks($user->id); + $endpoints = $this->getEndpoints($user->id); } $this->responseJson([ 'loggedIn' => $loggedIn, 'user' => $user, 'googleOAuthSignInUrl' => $loginUrl, - 'interfaces' => $interfaces, - 'webhooks' => $webhooks, + 'structures' => $structures, + 'endpoints' => $endpoints, ]); } @@ -150,11 +150,11 @@ public function logoutAction() { // Generate the login URL $loginUrl = $client->createAuthUrl(); - // Fetch demo interface - $interfaces = []; - $demo = $this->getDemoInterface(); + // Fetch demo structure + $structures = []; + $demo = $this->getDemoStructure(); if ($demo) { - $interfaces[] = $demo; + $structures[] = $demo; } // Return the JSON response @@ -162,8 +162,8 @@ public function logoutAction() { 'loggedIn' => false, 'user' => null, 'googleOAuthSignInUrl' => $loginUrl, - 'interfaces' => $interfaces, - 'webhooks' => [], + 'structures' => $structures, + 'endpoints' => [], ]); } } diff --git a/src/controllers/StructureController.php b/src/controllers/StructureController.php new file mode 100644 index 0000000..3168d50 --- /dev/null +++ b/src/controllers/StructureController.php @@ -0,0 +1,131 @@ +query('get-all-structures', [ + 'userId' => $this->getCurrentUserId(), + ]); + $users = $stmt->fetchAll(); + $this->responseJson($users); + } + + public function getAction($id) { + $structure = $this->getAccessibleStructure($id); + $this->responseJson($structure); + } + + public function createAction($data) { + $hash = $this->getHash(); + + $this->query('insert-structure', [ + 'hash' => $hash, + 'label' => $data->content->label, + 'logo' => $data->content->logo, + 'content' => $data->content->content, + 'endpoint' => $data->content->endpoint, + 'created_by' => $this->getCurrentUserId(), + ]); + + // Get inserted structure + $structure = $this->query('get-structure-by-hash', [ + 'hash' => $hash, + ])->fetch(PDO::FETCH_OBJ); + $this->preparePermissions($structure); + + $this->responseJson($structure); + } + + public function updateAction($id, $data) { + $currentStructure = $this->getAccessibleStructure($id); + if ($currentStructure) { + + // Copy current structure to history table + $this->copyToHistory($currentStructure); + + // Update structure + $this->query('update-structure-by-uuid', [ + 'uuid' => $data->content->uuid, + 'label' => $data->content->label, + 'logo' => $data->content->logo, + 'content' => $data->content->content, + 'endpoint' => $data->content->endpoint, + 'userId' => $this->getCurrentUserId(), + ]); + + // Clear all existing permissions (will be added later on) + if ($data->content->created_by === $this->getCurrentUserId()) { + $this->updatePermissions($data->content); + } + + $this->responseJson($data->content); + } + } + + public function deleteAction($id) { + if ($this->hasAccess($id)) { + $stmt = $this->query('delete-structure', [ + 'uuid' => $id, + 'userId' => $this->getCurrentUserId(), + ]); + if ($stmt->rowCount() > 0) { + $this->responseJson(true); + } + } + } + + private function hasAccess($uuid, $showError = true): bool { + return (bool) $this->getAccessibleStructure($uuid, $showError); + } + + private function getAccessibleStructure($uuid, $showError = true): false | stdClass { + $stmt = $this->query('get-accessible-structure-by-uuid', [ + 'uuid' => $uuid, + 'userId' => $this->getCurrentUserId(), + ]); + if ($stmt->rowCount() > 0) { + $structure = $stmt->fetch(PDO::FETCH_OBJ); + $this->preparePermissions($structure); + return $structure; + } + if ($showError) { + throwError(403, 'You don\'t have permission to view this structure'); + } + return false; + } + + private function preparePermissions(&$structure) { + $structure->permission_admin = array_filter(explode(',', $structure->permission_admin ?? '')); + $structure->permission_structure = array_filter(explode(',', $structure->permission_structure ?? '')); + return $structure; + } + + private function copyToHistory(stdClass $structure) { + $this->query('insert-history', [ + 'uuid' => $structure->uuid, + 'content' => $structure->content, + 'userId' => $this->getCurrentUserId(), + ]); + } + + private function updatePermissions($structure) { + + // Delete current permissions + $this->query('delete-user-permissions', [ + 'uuid' => $structure->uuid, + ]); + + // Update with newest permissions + foreach (['structure', 'admin'] as $type) { + foreach ($structure->{'permission_' . $type} as $email) { + $this->query('insert-permissions', [ + 'uuid' => $structure->uuid, + 'type' => $type, + 'email' => $email, + ]); + } + } + } +} diff --git a/src/queries/delete-webhook-by-uuid.sql b/src/queries/delete-endpoint-by-uuid.sql similarity index 68% rename from src/queries/delete-webhook-by-uuid.sql rename to src/queries/delete-endpoint-by-uuid.sql index 2f482b0..e60e542 100644 --- a/src/queries/delete-webhook-by-uuid.sql +++ b/src/queries/delete-endpoint-by-uuid.sql @@ -1,3 +1,3 @@ -DELETE FROM webhooks +DELETE FROM endpoints WHERE uuid = :uuid AND created_by = :userId diff --git a/src/queries/delete-interface.sql b/src/queries/delete-structure.sql similarity index 67% rename from src/queries/delete-interface.sql rename to src/queries/delete-structure.sql index 4d5b0ee..d08b029 100644 --- a/src/queries/delete-interface.sql +++ b/src/queries/delete-structure.sql @@ -1,3 +1,3 @@ -DELETE FROM interfaces +DELETE FROM structures WHERE uuid = :uuid AND created_by = :userId diff --git a/src/queries/delete-user-permissions.sql b/src/queries/delete-user-permissions.sql index b5d0178..438070b 100644 --- a/src/queries/delete-user-permissions.sql +++ b/src/queries/delete-user-permissions.sql @@ -1,2 +1,2 @@ DELETE FROM permissions -WHERE interface_uuid = :uuid +WHERE structure_uuid = :uuid diff --git a/src/queries/get-accessible-interface-by-uuid.sql b/src/queries/get-accessible-structure-by-uuid.sql similarity index 55% rename from src/queries/get-accessible-interface-by-uuid.sql rename to src/queries/get-accessible-structure-by-uuid.sql index 2cf1a00..ca215d4 100644 --- a/src/queries/get-accessible-interface-by-uuid.sql +++ b/src/queries/get-accessible-structure-by-uuid.sql @@ -1,19 +1,19 @@ SELECT i.*, - GROUP_CONCAT(DISTINCT(pi.email)) as permission_interface, + GROUP_CONCAT(DISTINCT(pi.email)) as permission_structure, GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, 'owner' AS type, u.name AS owner_name -FROM interfaces AS i +FROM structures AS i INNER JOIN users AS u ON u.id = i.created_by - LEFT JOIN permissions AS pa ON pa.interface_uuid = i.uuid AND pa.type = 'admin' - LEFT JOIN permissions AS pi ON pi.interface_uuid = i.uuid AND pi.type = 'interface' + LEFT JOIN permissions AS pa ON pa.structure_uuid = i.uuid AND pa.type = 'admin' + LEFT JOIN permissions AS pi ON pi.structure_uuid = i.uuid AND pi.type = 'structure' WHERE i.uuid = :uuid AND (i.created_by = :userId OR EXISTS ( SELECT 1 FROM permissions AS p JOIN users AS up ON up.email = p.email - WHERE p.interface_uuid = i.uuid AND up.id = :userId + WHERE p.structure_uuid = i.uuid AND up.id = :userId )) GROUP BY i.uuid, u.name; diff --git a/src/queries/get-all-endpoints.sql b/src/queries/get-all-endpoints.sql new file mode 100644 index 0000000..c2fd230 --- /dev/null +++ b/src/queries/get-all-endpoints.sql @@ -0,0 +1,16 @@ +SELECT DISTINCT combined_results.uuid, combined_results.* +FROM ( + SELECT e.* + FROM endpoints AS e + WHERE e.created_by = :userId + + UNION ALL + + SELECT e.* + FROM permissions AS p + INNER JOIN users AS u ON u.email = p.email + INNER JOIN structures AS i ON i.uuid = p.structure_uuid + INNER JOIN users AS owner ON owner.id = i.created_by + LEFT JOIN endpoints AS e ON e.uuid = i.endpoint + WHERE u.id = :userId + ) AS combined_results \ No newline at end of file diff --git a/src/queries/get-all-interfaces.sql b/src/queries/get-all-interfaces.sql deleted file mode 100644 index b4d3f8a..0000000 --- a/src/queries/get-all-interfaces.sql +++ /dev/null @@ -1,32 +0,0 @@ -SELECT - i.*, - GROUP_CONCAT(DISTINCT(pi.email)) as permission_interface, - GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, - 'owner' AS type, - u.name AS owner_name, - w.url AS server_url, - w.secret AS server_secret -FROM interfaces AS i - INNER JOIN users AS u ON u.id = i.created_by - LEFT JOIN permissions AS pa ON pa.interface_uuid = i.uuid AND pa.type = 'admin' - LEFT JOIN permissions AS pi ON pi.interface_uuid = i.uuid AND pi.type = 'interface' - LEFT JOIN webhooks AS w ON w.uuid = i.webhook -WHERE i.created_by = :userId -GROUP BY i.uuid - -UNION ALL - -SELECT i.*, - null AS permission_interface, - null AS permission_admin, - GROUP_CONCAT(DISTINCT(p.type)) as type, - owner.name AS owner_name, - w.url AS server_url, - w.secret AS server_secret -FROM permissions AS p - INNER JOIN users AS u ON u.email = p.email - INNER JOIN interfaces AS i ON i.uuid = p.interface_uuid - INNER JOIN users AS owner ON owner.id = i.created_by - LEFT JOIN webhooks AS w ON w.uuid = i.webhook -WHERE u.id = :userId -GROUP BY i.uuid diff --git a/src/queries/get-all-structures.sql b/src/queries/get-all-structures.sql new file mode 100644 index 0000000..2fd5e0c --- /dev/null +++ b/src/queries/get-all-structures.sql @@ -0,0 +1,32 @@ +SELECT + i.*, + GROUP_CONCAT(DISTINCT(pi.email)) as permission_structure, + GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, + 'owner' AS type, + u.name AS owner_name, + e.url AS server_url, + e.secret AS server_secret +FROM structures AS i + INNER JOIN users AS u ON u.id = i.created_by + LEFT JOIN permissions AS pa ON pa.structure_uuid = i.uuid AND pa.type = 'admin' + LEFT JOIN permissions AS pi ON pi.structure_uuid = i.uuid AND pi.type = 'structure' + LEFT JOIN endpoints AS e ON e.uuid = i.endpoint +WHERE i.created_by = :userId +GROUP BY i.uuid + +UNION ALL + +SELECT i.*, + null AS permission_structure, + null AS permission_admin, + GROUP_CONCAT(DISTINCT(p.type)) as type, + owner.name AS owner_name, + e.url AS server_url, + e.secret AS server_secret +FROM permissions AS p + INNER JOIN users AS u ON u.email = p.email + INNER JOIN structures AS i ON i.uuid = p.structure_uuid + INNER JOIN users AS owner ON owner.id = i.created_by + LEFT JOIN endpoints AS e ON e.uuid = i.endpoint +WHERE u.id = :userId +GROUP BY i.uuid diff --git a/src/queries/get-all-webhooks.sql b/src/queries/get-all-webhooks.sql deleted file mode 100644 index ddcd0d5..0000000 --- a/src/queries/get-all-webhooks.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - w.* -FROM webhooks AS w -WHERE - w.created_by = :userId diff --git a/src/queries/get-demo-interface.sql b/src/queries/get-demo-structure.sql similarity index 50% rename from src/queries/get-demo-interface.sql rename to src/queries/get-demo-structure.sql index e438369..da88dca 100644 --- a/src/queries/get-demo-interface.sql +++ b/src/queries/get-demo-structure.sql @@ -1,10 +1,10 @@ SELECT i.*, u.name AS owner_name, - w.url AS server_url, - w.secret AS server_secret -FROM interfaces AS i + e.url AS server_url, + e.secret AS server_secret +FROM structures AS i INNER JOIN users AS u ON u.id = i.created_by - LEFT JOIN webhooks AS w ON w.uuid = i.webhook + LEFT JOIN endpoints AS e ON e.uuid = i.endpoint WHERE i.hash = "demo" GROUP BY i.uuid diff --git a/src/queries/get-endpoint-by-uuid.sql b/src/queries/get-endpoint-by-uuid.sql new file mode 100644 index 0000000..364b6bd --- /dev/null +++ b/src/queries/get-endpoint-by-uuid.sql @@ -0,0 +1,6 @@ +SELECT + e.* +FROM endpoints AS e +WHERE + e.uuid = :uuid + AND e.created_by = :userId diff --git a/src/queries/get-interface-by-hash.sql b/src/queries/get-interface-by-hash.sql deleted file mode 100644 index 04ea819..0000000 --- a/src/queries/get-interface-by-hash.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - i.*, - GROUP_CONCAT(DISTINCT(pi.email)) as permission_interface, - GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, - 'owner' AS type, - u.name AS owner_name, - w.url AS server_url, - w.secret AS server_secret -FROM interfaces AS i - INNER JOIN users AS u ON u.id = i.created_by - LEFT JOIN permissions AS pa ON pa.interface_uuid = i.uuid AND pa.type = 'admin' - LEFT JOIN permissions AS pi ON pi.interface_uuid = i.uuid AND pi.type = 'interface' - LEFT JOIN webhooks AS w ON w.uuid = i.webhook -WHERE i.hash = :hash -GROUP BY - i.uuid, u.name; diff --git a/src/queries/get-interface-by-uuid.sql b/src/queries/get-interface-by-uuid.sql deleted file mode 100644 index 107721e..0000000 --- a/src/queries/get-interface-by-uuid.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - i.*, - GROUP_CONCAT(DISTINCT(pi.email)) as permission_interface, - GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, - 'owner' AS type, - u.name AS owner_name, - w.url AS server_url, - w.secret AS server_secret -FROM interfaces AS i - INNER JOIN users AS u ON u.id = i.created_by - LEFT JOIN permissions AS pa ON pa.interface_uuid = i.uuid AND pa.type = 'admin' - LEFT JOIN permissions AS pi ON pi.interface_uuid = i.uuid AND pi.type = 'interface' - LEFT JOIN webhooks AS w ON w.uuid = i.webhook -WHERE i.uuid = :uuid -GROUP BY - i.uuid, u.name; diff --git a/src/queries/get-structure-by-hash.sql b/src/queries/get-structure-by-hash.sql new file mode 100644 index 0000000..3996aa6 --- /dev/null +++ b/src/queries/get-structure-by-hash.sql @@ -0,0 +1,16 @@ +SELECT + i.*, + GROUP_CONCAT(DISTINCT(pi.email)) as permission_structure, + GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, + 'owner' AS type, + u.name AS owner_name, + e.url AS server_url, + e.secret AS server_secret +FROM structures AS i + INNER JOIN users AS u ON u.id = i.created_by + LEFT JOIN permissions AS pa ON pa.structure_uuid = i.uuid AND pa.type = 'admin' + LEFT JOIN permissions AS pi ON pi.structure_uuid = i.uuid AND pi.type = 'structure' + LEFT JOIN endpoints AS e ON e.uuid = i.endpoint +WHERE i.hash = :hash +GROUP BY + i.uuid, u.name; diff --git a/src/queries/get-structure-by-uuid.sql b/src/queries/get-structure-by-uuid.sql new file mode 100644 index 0000000..3875374 --- /dev/null +++ b/src/queries/get-structure-by-uuid.sql @@ -0,0 +1,16 @@ +SELECT + i.*, + GROUP_CONCAT(DISTINCT(pi.email)) as permission_structure, + GROUP_CONCAT(DISTINCT(pa.email)) as permission_admin, + 'owner' AS type, + u.name AS owner_name, + e.url AS server_url, + e.secret AS server_secret +FROM structures AS i + INNER JOIN users AS u ON u.id = i.created_by + LEFT JOIN permissions AS pa ON pa.structure_uuid = i.uuid AND pa.type = 'admin' + LEFT JOIN permissions AS pi ON pi.structure_uuid = i.uuid AND pi.type = 'structure' + LEFT JOIN endpoints AS e ON e.uuid = i.endpoint +WHERE i.uuid = :uuid +GROUP BY + i.uuid, u.name; diff --git a/src/queries/get-webhook-by-uuid.sql b/src/queries/get-webhook-by-uuid.sql deleted file mode 100644 index d3fbc01..0000000 --- a/src/queries/get-webhook-by-uuid.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - w.* -FROM webhooks AS w -WHERE - w.uuid = :uuid - AND w.created_by = :userId diff --git a/src/queries/insert-endpoint.sql b/src/queries/insert-endpoint.sql new file mode 100644 index 0000000..add62a2 --- /dev/null +++ b/src/queries/insert-endpoint.sql @@ -0,0 +1,2 @@ +INSERT INTO endpoints (url, secret, cypher, created_by) +VALUES (:url, :secret, :cypher, :created_by) diff --git a/src/queries/insert-interface.sql b/src/queries/insert-interface.sql deleted file mode 100644 index be1a49a..0000000 --- a/src/queries/insert-interface.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO interfaces (hash, label, logo, content, webhook, created_by) -VALUES (:hash, :label, :logo, :content, :webhook, :created_by) diff --git a/src/queries/insert-permissions.sql b/src/queries/insert-permissions.sql index 6d811b4..7c1dcdf 100644 --- a/src/queries/insert-permissions.sql +++ b/src/queries/insert-permissions.sql @@ -1,2 +1,2 @@ -INSERT INTO permissions (interface_uuid, type, email) +INSERT INTO permissions (structure_uuid, type, email) VALUES (:uuid, :type, :email) diff --git a/src/queries/insert-structure.sql b/src/queries/insert-structure.sql new file mode 100644 index 0000000..ec487c1 --- /dev/null +++ b/src/queries/insert-structure.sql @@ -0,0 +1,2 @@ +INSERT INTO structures (hash, label, logo, content, endpoint, created_by) +VALUES (:hash, :label, :logo, :content, :endpoint, :created_by) diff --git a/src/queries/insert-webhook.sql b/src/queries/insert-webhook.sql deleted file mode 100644 index 677bc26..0000000 --- a/src/queries/insert-webhook.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO webhooks (url, secret, cypher, created_by) -VALUES (:url, :secret, :cypher, :created_by) diff --git a/src/queries/update-endpoint-by-uuid.sql b/src/queries/update-endpoint-by-uuid.sql new file mode 100644 index 0000000..9b46fdf --- /dev/null +++ b/src/queries/update-endpoint-by-uuid.sql @@ -0,0 +1,4 @@ +UPDATE endpoints AS e +SET e.url = :url, e.updated_at = NOW() +WHERE e.uuid = :uuid + AND e.created_by = :userId diff --git a/src/queries/update-interface-by-uuid.sql b/src/queries/update-interface-by-uuid.sql deleted file mode 100644 index 7825d99..0000000 --- a/src/queries/update-interface-by-uuid.sql +++ /dev/null @@ -1,3 +0,0 @@ -UPDATE interfaces AS i -SET i.label = :label, i.logo = :logo, i.content = :content, i.webhook = :webhook, i.updated_at = NOW() -WHERE i.uuid = :uuid diff --git a/src/queries/update-structure-by-uuid.sql b/src/queries/update-structure-by-uuid.sql new file mode 100644 index 0000000..0e8528b --- /dev/null +++ b/src/queries/update-structure-by-uuid.sql @@ -0,0 +1,3 @@ +UPDATE structures AS i +SET i.label = :label, i.logo = :logo, i.content = :content, i.endpoint = :endpoint, i.updated_at = NOW() +WHERE i.uuid = :uuid diff --git a/src/queries/update-webhook-by-uuid.sql b/src/queries/update-webhook-by-uuid.sql deleted file mode 100644 index a8630b4..0000000 --- a/src/queries/update-webhook-by-uuid.sql +++ /dev/null @@ -1,4 +0,0 @@ -UPDATE webhooks AS w -SET w.url = :url, w.updated_at = NOW() -WHERE w.uuid = :uuid - AND w.created_by = :userId