{"id":18339775,"url":"https://github.com/imperfectandcompany/phastify-api-framework","last_synced_at":"2025-10-06T16:46:35.965Z","repository":{"id":103765754,"uuid":"587931705","full_name":"imperfectandcompany/Phastify-API-Framework","owner":"imperfectandcompany","description":null,"archived":false,"fork":false,"pushed_at":"2024-02-25T21:37:15.000Z","size":372,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-09T20:47:35.786Z","etag":null,"topics":["api","ig","imperfect","imperfect-gamers","imperfectandcompany","phastify","php","restful","webservice"],"latest_commit_sha":null,"homepage":"https://v2.api.postogon.com/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imperfectandcompany.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-11T23:21:19.000Z","updated_at":"2024-05-28T23:04:10.000Z","dependencies_parsed_at":"2024-02-25T22:42:55.656Z","dependency_job_id":null,"html_url":"https://github.com/imperfectandcompany/Phastify-API-Framework","commit_stats":null,"previous_names":["imperfectandcompany/phastify-api-framework"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/imperfectandcompany/Phastify-API-Framework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imperfectandcompany%2FPhastify-API-Framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imperfectandcompany%2FPhastify-API-Framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imperfectandcompany%2FPhastify-API-Framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imperfectandcompany%2FPhastify-API-Framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imperfectandcompany","download_url":"https://codeload.github.com/imperfectandcompany/Phastify-API-Framework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imperfectandcompany%2FPhastify-API-Framework/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278646753,"owners_count":26021511,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","ig","imperfect","imperfect-gamers","imperfectandcompany","phastify","php","restful","webservice"],"created_at":"2024-11-05T20:19:20.165Z","updated_at":"2025-10-06T16:46:35.927Z","avatar_url":"https://github.com/imperfectandcompany.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# postogon-api-v2\n\n\nim working on writing tests first for a feature in our integrations that doesnt exist yet.\n\nim documenting my thoughts as i go for some section or addition to the github readme idk yet. i will reply once i finish but i wanna get it to you asap to get ur mind running and thinking:\n\nWhile it sounds like a lot of work, we want to focus on test-driven development (in this case its the same as BDD) \nhttps://www.infoq.com/articles/virtual-panel-tdd-bdd/\n\n\ntestRunner is what will execute the functions in our test case, we will need to define groups and whitelist testing funcitons for them later.\n\nAccording to the link below, we should aim to test production-level concrete classes\nhttps://enterprisecraftsmanship.com/posts/how-to-unit-test-an-abstract-class/\n\nWe want two types of tests:\n1. Developer tests for fast feedback (TDD)\n2. User tests to ensure that features are built to the user tests (BDD)\n\nCurrent focus:\nBeasting out Domain Driven Development\nhttps://www.lucidchart.com/blog/domain-driven-design-introduction\nget used to gherkin style lang -\nWe want to apply this to business driven development.\n\nOur BDD tests must only be affected if there is any change in the business. In this case we have to think like a user and work past ambiguities and questions generated by user stories, since there are no users to gain this information from.\n\nBDD tests CANNOT include technical jargon. Business feature -\u003e technical use-cases.\n\nI'm going to write exactly how crazy this is for scaling features.\nI will describe the current state of our integration endpoints.\n\nWe have five endpoints currently.\n| Endpoint                        | Method | Description                                            | Parameters             | Request Body           |\n|---------------------------------|--------|--------------------------------------------------------|-------------------------|------------------------|\n| `/integrations`                 | GET    | Returns a list of all integrations for the user.      | None                    | None                   |\n| `/integrations`                 | POST   | Creates a new integration for the user.               | None                    | Integration details    |\n| `/integrations/:id`             | GET    | Returns integration details based on the provided ID. | `:id` (URL parameter)   | None                   |\n| `/integrations/:id`             | PUT    | Updates an existing integration for the user.         | `:id` (URL parameter)   | Updated integration    |\n| `/integrations/:id`             | DELETE | Deletes an existing integration for the user.         | `:id` (URL parameter)   | None                   |\n\nWe have 4 functions currently in our controller:\ngetAllIntegrations\ngetIntegration\ncreateIntegration\nupdateIntegration\n\nThe controller leverages the integration class functions:\ngetIntegrationsByUserId($userId)\ngetIntegrationsById($Id)\ncreateIntegrationForUser($userId, $data)\nupdateIntegrationById($id, $data)\ndeleteIntegrationById($id)\ndoesIntegrationBelongToUser($integrationId, $userId)\ndoesIntegrationExist($integrationId)\n\nLets observe our current `integrations` table schema:\n```sql\nCREATE TABLE `integrations` (\n`id` int(11) unsigned NOT NULL AUTO_INCREMENT,\n`user_id` int(11) NOT NULL,\n`service` varchar(255) NOT NULL,\n`client_id` varchar(255) DEFAULT NULL,\n`client_secret` varchar(255) DEFAULT NULL,\n`access_token` varchar(512) DEFAULT NULL,\n`token_type` varchar(255) DEFAULT NULL,\n`refresh_token` varchar(512) DEFAULT NULL,\n`token_expiration` timestamp NULL DEFAULT NULL,\n`status` enum('active','inactive') NOT NULL,\n`data` json DEFAULT NULL,\nPRIMARY KEY (`id`),\nKEY `user_id` (`user_id`)\n) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8\n```\n\nThis was a high level setup for the main feature \"integrations\" that took less brain power but flexibility in design,\nenough to modify and adapt.\n\nAnyway, lets try to think like a user and write a BDD test for a SPECIFIC part feature that doesn't exist yet (but should be)\n\nThe format you see below is gherkin, we will use it for writing the BDD test:\n**Feature**: As a User,\n         when I integrate a service to Postogon, I want to configure my profile w/ service visibility\n\n  **Scenario**: The user is configuring their integration\n    _Given_ a user wants to configure service visibility\n      | Show to Followers False | Show to Contacts True |\n    _When_ they configure service visibility\n    _Then_ their recent integration settings update from default\n^^^ future update will enable this format (within frameowrk) when writing tests (not a priority rn)\n\nNotice how the there is a sequence of events. I know there are rules to how behavior is handled and how this differs from unit testing and also end-to-end testing. But since I made this framework, i get to decide how to set it up efficiently.  Right now things will be fine, but i will share how everything fits together in this strategy i ended up with. Got distracted, going back to writing this test in our code....\n\n    // BDD Test: A user should be able to update an integration after adding a service\n    function testCanUserUpdateIntegrationAfterAddingService() {\n        \n    // Add a service\n    $serviceData = [\n        \"service\" =\u003e \"SomeServiceName\",\n        \"client_id\" =\u003e \"yourClientIDHere\",\n        \"client_secret\" =\u003e \"yourClientSecretHere\",\n        \"access_token\" =\u003e \"yourAccessTokenHere\",\n        \"token_type\" =\u003e \"Bearer\",\n        \"status\" =\u003e \"Active\"\n    ];\n\n    // Convert the service data to JSON\n    $serviceDataJson = json_encode($serviceData);\n\n    // Set the input stream for integration creation\n    $integrationController::setInputStream($serviceDataJson);\n\n    // Create the integration\n    $integrationController-\u003ecreateIntegration();\n\n    // Assuming you have a function to get the newly created integration ID\n    $integrationId = getNewlyCreatedIntegrationId();\n\n    // Define the updated settings\n    $updatedSettings = [\n        \"Show to Followers\" =\u003e false,\n        \"Show to Contacts\" =\u003e true\n    ];\n\n    {\n    \"service\": \"SomeServiceName\",\n    \"client_id\": \"yourClientIDHere\",\n    \"client_secret\": \"yourClientSecretHere\",\n    \"access_token\": \"yourAccessTokenHere\",\n    \"token_type\": \"Bearer\",\n    \"status\": \"Active\"\n    }\n\n        $data = (object)array(\"Visibility\" =\u003e \"Comment from a contact\");\n        $result = $integrationController-\u003eupdateIntegration($integrationId, $data);\n        customAssert($result !== false, 'User should be able to comment on the public post as a contact');\n    }\n\n\n\n\n\n\n\n\n\n\n\nsep18\nhttps://v2.api.postogon.com/list-routes\n\n# Creating and Running Tests:\nThis section introduces the test runner for the application, offering a systematic approach to validate features using a series of defined tests.\n\n**Enhancements**:\n- Introduced `TestDouble` to provide mocking capabilities for the input stream (simulating post body request), enabling more granular control during testing.\n- Enhanced the `TestRunner` class to support categorized test execution and display comprehensive metrics post-test run\n- Integrated additional tests to validate post and comment functionalities.\n\n**Fixes**:\n- Resolved issues related to global state by introducing cleanup mechanisms post-test.\n- Refined error-handling mechanisms to capture both PHP `Error` and `Exception` objects.\n\n## Table of Contents\n- [Overview](#overview)\n- [Prerequisites](#prerequisites)\n- [Setting Up the Test Environment](#setting-up-the-test-environment)\n- [Writing Tests](#writing-tests)\n- [Grouping Tests](#grouping-tests)\n- [Running Tests](#running-tests)\n- [Understanding TestDouble](#understanding-testdouble)\n- [Test Cleanup \u0026 Integrity](#test-cleanup--integrity)\n- [Debugging](#debugging)\n- [Reporting](#reporting)\n- [Delving Deeper: Writing Functions with Meaningful Feedback](#delving-deeper-writing-functions-with-meaningful-feedback)\n- [Conclusion](#conclusion)\n\n## Glossary\n**Group Tests**: Group tests based on functionality, and specify which controller they belong to.\n**Execute Tests**: The TestRunner is now initialized with all controllers. When running tests, it refers to the specified controller for each test group.\n**Initialization of Controllers**: A key-value mapping of controller names to their initialized objects is established.\n**Objective**: When implementing a new test, it's crucial to focus on the objective: validating specific functionalities within the application.\n\n\n## Quick Start\n1. Create Controller object with $dbConnection passed in for testing.\n2. Define the test function, taking into account the $objectUnderTest to access controller methods.\n3. Use the customAssert function to validate outcomes.\n4. Add the test name to the relevant test group.\n5. Ensure the test group is associated with the right controller in the $tests array.\n6. ???\n7. profit\n8. $$$$$$$$\n\u003ca name=\"overview\"\u003e\u003c/a\u003e\n\n## 1. Overview\nThis enhanced testing setup provides a robust environment to validate multiple aspects of the application across various controllers. By understanding the mechanics of accessing controllers, their underlying classes, and the strategic use of TestDouble, we can craft precise, effective tests. The structure also simplifies the addition of new test cases, making maintenance and expansion streamlined.\n\n\u003ca name=\"prerequisites\"\u003e\u003c/a\u003e\n## 2. Prerequisites\n- Ensure you've included all necessary files in your test script.\n- Familiarity with the application's logic and functionalities.\n\n\u003ca name=\"setting-up-the-test-environment\"\u003e\u003c/a\u003e\n## 3. Setting Up the Test Environment\nEnsure your test environment is ready:\n\n```php\ninclude_once($GLOBALS['config']['private_folder'].'/tests/test_case.php');\ninclude_once($GLOBALS['config']['private_folder'].'/controllers/{ControllerBeingTested}Controller.php');\ninclude_once($GLOBALS['config']['private_folder'].'/classes/class.testRunner.php');\n\n${ControllerBeingTested} = new {ControllerBeingTested}Controller($dbConnection);\n\n// Initialize the Controller object once\n$controllers = [\n\t'post' =\u003e new PostController($dbConnection),\n\t'comments' =\u003e new CommentController($dbConnection),\n\t'integrations' =\u003e new IntegrationControllerTestDouble($dbConnection)\n\t// ..etc\n];\n```\n\u003ca name=\"writing-tests\"\u003e\u003c/a\u003e\n## 4. Writing Tests\nTests are simple PHP functions. Here's how to define one:\n\n```php\nfunction testName($objectUnderTest) {\n    // Test logic here\n    // Use customAssert function to validate conditions\n    customAssert(/* condition */, \"Error Message if condition fails\");\n}\n```\n\nTo access methods within the controller being tested, use the $objectUnderTest argument. This argument contains the initialized instance of the controller, enabling direct access to its methods.\n\nFor instance, if testing the PostController:\n```\nphp\nfunction testCanViewOwnPublicPost($objectUnderTest) {\n    $result = $objectUnderTest-\u003eviewPublicPost($postId);\n    customAssert($result, \"Failed to view own public post.\");\n}\n```\n#### Accessing the Underlying Class\nControllers often utilize underlying classes to handle specific tasks. In this framework, the test environment makes these classes accessible through the controller instance.\n\nFor example, if **PostController** uses a class named **PostHandler**, it's possible to call its methods:\n\n```\nphp\nfunction anotherTestExample($objectUnderTest) {\n    $handlerResult = $objectUnderTest-\u003ePostHandler-\u003especificMethod();\n    customAssert($handlerResult, \"Specific method failed.\");\n}\n```\n\n#### Understanding getInputStream and Simulating Post Body\nDuring testing, we need to simulate post body requests. To achieve this, functions call getInputStream, allowing us to mock or replace actual HTTP post body data with predefined testing data.\n\n\n**Tips**:\n- Make test names descriptive.\n\t- E.g., **testCanViewOwnPublicPost**.\n- Use **customAssert** to verify the logic.\n\t- If the condition is false, the test will fail.\n\n\u003ca name=\"grouping-tests\"\u003e\u003c/a\u003e\n## 5. Grouping Tests\nGroup tests into categories for better organization:\n\n```php\n$testCannotViewOwnPosts = [\n  \"testCannotViewOwnUnauthorizedPost\",\n];\n$testCanViewOthersPosts = [\n  \"testCanViewPublicPostAsContact\",\n  \"testCanViewPublicPostAsNoContact\",\n  \"testCanViewPrivatePostAsContact\"\n  // ... More tests\n];\n```\n\nNow define all your test categories:\n\n```php\n$tests = [\n                \"Can View Own Posts\" =\u003e ['controller' =\u003e 'post', 'tests' =\u003e $testCanViewOwnPosts],\n                \"Cannot View Own Posts\" =\u003e ['controller' =\u003e 'post', 'tests' =\u003e $testCannotViewOwnPosts],\n                \"Can View Others' Posts\" =\u003e ['controller' =\u003e 'post', 'tests' =\u003e $testCanViewOthersPosts],\n                \"Cannot View Others' Posts\" =\u003e ['controller' =\u003e 'post', 'tests' =\u003e $testCannotViewOthersPosts],\n];\n```\n\n\u003ca name=\"running-tests\"\u003e\u003c/a\u003e\n## 6. Running Tests\nUse the TestRunner class to execute tests:\n\n```php\n$runner = new TestRunner($Controllers);\n$runner-\u003erunTests($tests);\n```\nThis class ensures each test is run and provides a summary of the results.\n\n\u003ca name=\"understanding-testdouble\"\u003e\u003c/a\u003e\n## 7. Understanding TestDouble\nThe test infrastructure uses the TestDouble pattern, which is a general strategy for replacing specific components of a system for testing purposes. Hence, controllers used in testing have names ending in \"TestDouble\" (e.g., CommentControllerTestDouble), making it clear that they are stand-ins or substitutes for the real controllers during testing.\n\nSometimes, for testing, we need to mimic the behavior of the production controller but with controlled, predictable outputs. This ensures that external factors don't interfere with our tests.\n\nIn cases where tests don't require simulation of a post body request, we directly use the actual controllers. For example, with PostController.\n\n**By using the TestDouble pattern**:\n\nWe can avoid unintended side effects, such as database writes, while testing.\nIt allows for specific behaviors to be mocked, helping isolate the functionalities being tested.\n\n**This pattern pattern ensures controlled, predictable outputs during testing**:\n- Avoids unintended side effects.\n- Mocks specific behaviors.\n\t- Introduced to simulate post requests within production method\n\nTo mock certain behaviors for testing purposes, create a class in the `/tests/controller/` following the Production Controller file name  with `TestDouble` class appended to the end before `.php`. This class will be used to extend the original `{Production}Controller` but has the added capability to override the input stream. Don't forget to include the controller we are extending.\n\n#### Usage:\n\n1. Set the mock input:\n    ```php\n    CommentControllerTestDouble::setInputStream(json_encode(['comment' =\u003e 'Mocked comment']));\n    ```\n\n2. Reset the mock input:\n    ```php\n    CommentControllerTestDouble::setInputStream('php://input');\n    ```\n\n\u003ca name=\"test-cleanup--integrity\"\u003e\u003c/a\u003e\n## 8. Test Cleanup \u0026 Integrity\n\nOur testing strategy emphasizes cleaning up and ensuring a consistent state before and after tests. The testRunner takes care of this by:\n- Resetting the user ID.\n- Restoring global state changes.\n- Maintaining data integrity with separate databases (coming soon)\n\nWhen we mock the post body, it's essential to reset it within the test or function after use, ensuring no unexpected side-effects in subsequent tests.\n\n\u003ca name=\"debugging\"\u003e\u003c/a\u003e\n## 9. Debugging\nOur application works with relational data involving entities like posts, users, and contact relationships. Currently, this data is accurate and resides in our primary database. A separate testing database is in our future plans to ensure data integrity and further decouple testing from production.\n\nWhen we mock the post body (using TestDouble), it's essential to reset it within the test or function after use, ensuring no unexpected side-effects in subsequent tests.\n\nFollow these steps when facing issues:\n\n1. Isolate the Issue\n\t- Comment out other tests and only run the problematic test.\n2. Print Debug Information\n\t- Within the application logic, you can use the global **$currentTest** variable to conditionally print information.\n\n\t```php\n\tglobal $currentTest;\n\tif ($currentTest === 'specificTestName'){\n\t\techo \"Debugging info here\";\n\t}\n\t```\n3. Check Error Message\n\t- The test runner will display a clear error message if a test fails.\n\n**throwWarning**: This method logs a warning message. It's particularly useful for conditions that might need attention but don't necessarily indicate a failure. For example, when a post is private but still accessible by a user, it throws a warning:\n  \n```php\nthrowWarning(\"Post is private\");\n```\n\n- **throwError**: Similar to **throwWarning**, but used for more critical issues that indicate a test failure.\n\nWhen using throwWarning within tests or application logic, it automatically logs the file and line number where the warning was thrown. This information is invaluable when trying to understand the context or source of a particular warning.\n\nHere is an example within our code:\n#### Understanding the `canViewPost` Function\n\nThe `canViewPost` function is crucial as it checks whether a given user can view a specific post. Here's its signature:\n\n```php\n/**\n * Checks if the current user can view a post.\n *\n * @param int $postId The ID of the post to check.\n * @param int $userId The ID of the user.\n * @return bool True if the user can view the post, false otherwise.\n */\npublic function canViewPost(int $postId, int $userId): bool { ... }\n```\n\nWithin this function, there are numerous conditions that dictate if a user has the necessary permissions to view a post. In order to provide meaningful feedback during testing, the function utilizes custom methods like **throwWarning** and **throwError**. \n\nBoth methods include contextual information from where they were invoked, such as the file and line number. This aids in debugging, especially when tests fail.\n\nFor consistency and predictability, we also ensure that every conditional path in our controllers returns true or false for our **customAssert** function. Right before these returns, we call our sendResponse function, ensuring feedback is sent to the client or caller.\n\n*If the functions being used do not include this, you will experience issues with assertion.\n*\n\u003ca name=\"reporting\"\u003e\u003c/a\u003e\n## 10. Reporting\nAfter tests, a summary displays metrics:\n- Passed and failed tests.\n- See the Delving Deeper section for more.\n\n\u003ca name=\"delving-deeper-writing-functions-with-meaningful-feedback\"\u003e\u003c/a\u003e\n## 11. Delving Deeper: Writing Functions with Meaningful Feedback\nA deeper look into specific functions and feedback mechanisms:\n- Understand the canViewPost function.\n- Utilize custom methods like throwWarning.\n\n\u003ca name=\"conclusion\"\u003e\u003c/a\u003e\n## 12. Conclusion\nAutomated testing ensures that the application works seamlessly. By understanding various mechanics and strategies, you can craft precise, effective tests and maintain the robust application. This guide is structured to cover every aspect of the testing process, ensuring clarity, coherence, and comprehensiveness. Following this guide will assist in creating, running, and maintaining tests effectively.\n\n\n## Router Class Enhancements\n\nHere's a quick overview of the new features:\n\n### Required Parameters\n\nNow, we can specify required parameters for different HTTP request methods (e.g., GET, POST, PUT) for each route. This ensures that the necessary data is present when handling requests.\n\nExample:\n```php\n// Update an existing integration for the authenticated user\n$router-\u003eenforceParameters('/integrations/:id', 'PUT', [\n    'service' =\u003e 'body',   // Service comes from the request body\n    'clientname' =\u003e 'body',   // Service comes from the request body\n]);\n```\n\n### Documentation\nWe've added support for documenting our routes comprehensively. We can include documentation for each route, describing its purpose and usage.\n\nExample:\n```php\n// Add documentation to route\n$router-\u003eaddDocumentation('/integrations/:id', 'PUT', 'Updates an existing integration for the authenticated user.');\n```\n\n### Enforcing Required Parameters\nTo ensure that required parameters are always present, we introduced a function that enforces them for a specific route and request method. This helps maintain data integrity and ensures that our routes receive the necessary input.\n\nExample:\n```php\n// Require 'service' and 'clientname' to be present in the request body for the PUT method\n$router-\u003eenforceParameters('/integrations/:id', 'PUT', [\n    'PUT:body:service,clientname',\n]);\n```\n\n### Introduction of DevMode\n\nWith the implementation of a development mode (`devmode`), our RESTful Web Service is now endowed with a mode that makes it more streamlined and hassle-free for our developers during the application development phase.\n\n### What is `devmode`?\n\n`devmode` is a feature designed to simplify the development and testing process. When activated, it avoids the need for token-based authentication for each request, making it easier for developers to test different endpoints without having to worry about providing or refreshing authentication tokens. This can significantly speed up development, but it's essential to remember that `devmode` should **never** be activated in production environments, as it bypasses certain security checks.\n\n### Endpoints:\n\n1. **Get Current DevMode Status**\n    - **Endpoint**: `/devmode`\n    - **HTTP Method**: GET\n    - **Description**: Retrieves the current status of `devmode`, returning whether it's turned on (`true`) or off (`false`).\n    - **Usage**: \n      ```http\n      GET /devmode\n      ```\n\n2. **Toggle DevMode**\n    - **Endpoint**: `/devmode/toggle`\n    - **HTTP Method**: GET\n    - **Description**: Toggles the current `devmode` status. If it's on, it will be turned off and vice versa.\n    - **Usage**:\n      ```http\n      GET /devmode/toggle\n      ```\n\n3. **Set DevMode to a Specific Value**\n    - **Endpoint**: `/devmode/toggle/:value`\n    - **HTTP Method**: GET\n    - **Description**: Sets the `devmode` status to a specific value. The `:value` parameter should be replaced with either `true` or `false`.\n    - **Usage**:\n      ```http\n      GET /devmode/toggle/true\n      ```\n      or\n      ```http\n      GET /devmode/toggle/false\n      ```\n\n### How to toggle `devmode`?\n\n- To **check the current status**, use the `/devmode` endpoint.\n  \n- To **switch the current mode**, simply call the `/devmode/toggle` endpoint. It will invert the current setting.\n  \n- To **set a specific mode** (either `true` or `false`), use the `/devmode/toggle/:value` endpoint, replacing `:value` with your desired state.\n\n---\n\n**Important**: Always ensure that `devmode` is turned off (`false`) in production environments for security reasons.\n\n\n### Implementation of Integrations in the RESTful Web Service\n\nPostogon API V2 has been expanded to include an `integrations` feature, designed to facilitate users in connecting and managing third-party service integrations.\n\n### What are `integrations`?\n\n`integrations` offers a framework for users to incorporate third-party services into the application. Each integration entails vital details like the service name, client ID, client secret, access tokens, and other service-specific credentials. This structured approach ensures efficient service interaction while preserving data safety and integrity.\n\n### Key Features:\n\n1. **Ownership Verification**: Before any update, the system verifies if the integration pertains to the authenticated user, solidifying security.\n2. **Data Validation**: Only predefined columns from the provided data are accepted. Any extraneous or unidentified columns are flagged, preventing potential mishaps.\n3. **Token Handling**: Although refresh tokens aren't yet operational, there are plans to integrate them for sustained and secure third-party service connections.\n\n### Endpoints:\n\n1. **Retrieve Integration Details**\n    - **Endpoint**: `/integrations/:id`\n    - **HTTP Method**: GET\n    - **Description**: Fetches integration details for the user\n    - **Usage**: \n      ```http\n      GET /integrations\n      ```\n\n2. **Create a New Integration**\n    - **Endpoint**: `/integrations`\n    - **HTTP Method**: POST\n    - **Description**: Adds a new integration to the user's account.\n    - **Usage**:\n      ```http\n      POST /integrations\n      ```\n      Body:\n      ```json\n      {\n          \"service\": \"ServiceName\",\n          \"client_id\": \"ClientID\",\n          ...\n      }\n      ```\n\n3. **Update Integration by ID**\n    - **Endpoint**: `/integrations/:id`\n    - **HTTP Method**: PUT\n    - **Description**: Updates an integration's details using the provided ID and data. Verifies ownership prior to allowing any update.\n    - **Usage**: \n      ```http\n      PUT /integrations/{integration_id}\n      ```\n      Body:\n      ```json\n      {\n          \"service\": \"UpdatedServiceName\",\n          ...\n      }\n      ```\n4. ### Delete Integration by ID\n\n- **Endpoint**: `/integrations/:id`\n- **HTTP Method**: DELETE\n- **Description**: Removes an existing integration from the user's account based on the provided ID. The system ensures that only the owner of the integration (or an authorized user) can delete it to maintain data integrity and security.\n- **Usage**: \n  ```http\n  DELETE /integrations/{integration_id}\n  ```\nWith this `DELETE` endpoint, users can efficiently manage their integrations, ensuring that obsolete or redundant connections are promptly removed from their accounts.\n\n---\n\n\n### Future Plans:\n\n- **Refresh Token Integration**: Plans are in the pipeline to integrate refresh tokens, ensuring continued access to third-party services without frequent reauthentication. \n- **Support for Specific Services**: In the near future, we aim to have a predefined list of supported third-party services with preset URLs, simplifying user experience and reducing error margins.\n\n**Retrieve Specific Integration Details (WIP) **\n    - **Endpoint**: `/integrations/:id`\n    - **HTTP Method**: GET\n    - **Description**: Fetches integration details based on the provided ID. \n    - **Usage**: \n      ```http\n      GET /integrations/{integration_id}\n      ```\n\nRouter class:\n## Function: add\n\nAdds a new route to the router.\n\n### Parameters:\n\n- `$uri` - The route URI\n- `$controller` - The controller name and method, separated by `@`\n- `$requestMethod` - The HTTP request method\n\n### Steps:\n\n1. Check if the URI is empty. If it is, throw an exception.\n2. Add a slash to the beginning of the URI if it is missing.\n3. Check if the URI ends with a slash and remove it if it does.\n4. Check if the controller name and method are separated by `@`. If not, throw an exception.\n5. Split the controller name and method into separate variables.\n6. Check if a method name was provided after `@`. If not, throw an exception.\n7. Check if a route with the same URI already exists. If it does, throw an exception.\n8. Check if the endpoint matches with an existing route. If it does and the request method is already registered, throw an exception.\n9. Get the list of files in the controllers directory.\n10. Check if a file with the expected name exists.\n11. If the file does not exist, throw an exception.\n12. Check if the specified method exists in the controller. If not, throw an exception.\n13. Check if the HTTP request method is valid. If not, throw an exception.\n14. Parse the URI to find any parameter placeholders and save them to an array.\n15. If the URI does not already exist in the routes array, add it along with the HTTP request method and controller name and method.\n16. If the URI already exists in the routes array, add the HTTP request method and controller name and method to the existing URI.\n\n\nnotes: gotta add validation for param in the sense that when we add a route with a param, we should make sure the method within controller being called includes that within the function param to prevent it from continuing.\n\nadd validation for this error:\nhttps://v2.api.postogon.com/authenticate?token=fawwew\n\u003cbr /\u003e\n\u003cb\u003eFatal error\u003c/b\u003e:  Uncaught Error: Call to undefined method Router::getRoutes() in /usr/www/igfastdl/postogon-api/public_html/index.php:59\nStack trace:\n#0 {main}\n  thrown in \u003cb\u003e/usr/www/igfastdl/postogon-api/public_html/index.php\u003c/b\u003e on line \u003cb\u003e59\u003c/b\u003e\u003cbr /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimperfectandcompany%2Fphastify-api-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimperfectandcompany%2Fphastify-api-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimperfectandcompany%2Fphastify-api-framework/lists"}