瀏覽代碼

feat: add request validators to controllers

Fernando 3 周之前
父節點
當前提交
906aaa2091

+ 7 - 0
bin/setup

@@ -49,6 +49,13 @@ fi
 
 # 3) Seed inicial (idempotente)
 echo "[setup] Inserindo dados iniciais (seed)..."
+
+PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}" -v ON_ERROR_STOP=1 -c "
+INSERT INTO \"company\" (company_name, company_flag)
+SELECT 'LumyonTech', 'a'
+WHERE NOT EXISTS (SELECT 1 FROM \"company\" WHERE company_name = 'LumyonTech');
+"
+
 PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}" -v ON_ERROR_STOP=1 -c "
 INSERT INTO \"role\" (company_id, role_name, role_permission, role_flag)
 SELECT c.company_id, 'Admin', '{}'::jsonb, 'a'

+ 2 - 1
composer.json

@@ -20,6 +20,7 @@
     "require": {
         "clue/framework-x": "^0.17.0",
         "vlucas/phpdotenv": "^5.6",
-        "firebase/php-jwt": "^6.11"
+        "firebase/php-jwt": "^6.11",
+        "respect/validation": "^2.4"
     }
 }

+ 123 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "290dc0f613a520d4fde731b332c80e13",
+    "content-hash": "c4f488cdc234020e7137afdb7ac742d8",
     "packages": [
         {
             "name": "clue/framework-x",
@@ -1100,6 +1100,128 @@
             ],
             "time": "2024-06-11T12:45:25+00:00"
         },
+        {
+            "name": "respect/stringifier",
+            "version": "0.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Respect/Stringifier.git",
+                "reference": "e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Respect/Stringifier/zipball/e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59",
+                "reference": "e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.8",
+                "malukenho/docheader": "^0.1.7",
+                "phpunit/phpunit": "^6.4"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/stringify.php"
+                ],
+                "psr-4": {
+                    "Respect\\Stringifier\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Respect/Stringifier Contributors",
+                    "homepage": "https://github.com/Respect/Stringifier/graphs/contributors"
+                }
+            ],
+            "description": "Converts any value to a string",
+            "homepage": "http://respect.github.io/Stringifier/",
+            "keywords": [
+                "respect",
+                "stringifier",
+                "stringify"
+            ],
+            "support": {
+                "issues": "https://github.com/Respect/Stringifier/issues",
+                "source": "https://github.com/Respect/Stringifier/tree/0.2.0"
+            },
+            "time": "2017-12-29T19:39:25+00:00"
+        },
+        {
+            "name": "respect/validation",
+            "version": "2.4.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Respect/Validation.git",
+                "reference": "f13f10f19978aea33af2a102a2f58f2db1e63619"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Respect/Validation/zipball/f13f10f19978aea33af2a102a2f58f2db1e63619",
+                "reference": "f13f10f19978aea33af2a102a2f58f2db1e63619",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "respect/stringifier": "^0.2.0",
+                "symfony/polyfill-mbstring": "^1.2"
+            },
+            "require-dev": {
+                "egulias/email-validator": "^3.0",
+                "giggsey/libphonenumber-for-php-lite": "^8.13 || ^9.0",
+                "malukenho/docheader": "^1.0",
+                "mikey179/vfsstream": "^1.6",
+                "phpstan/phpstan": "^1.9",
+                "phpstan/phpstan-deprecation-rules": "^1.1",
+                "phpstan/phpstan-phpunit": "^1.3",
+                "phpunit/phpunit": "^9.6",
+                "psr/http-message": "^1.0",
+                "respect/coding-standard": "^4.0",
+                "squizlabs/php_codesniffer": "^3.7"
+            },
+            "suggest": {
+                "egulias/email-validator": "Improves the Email rule if available",
+                "ext-bcmath": "Arbitrary Precision Mathematics",
+                "ext-fileinfo": "File Information",
+                "ext-mbstring": "Multibyte String Functions",
+                "giggsey/libphonenumber-for-php-lite": "Enables the phone rule if available"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Respect\\Validation\\": "library/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Respect/Validation Contributors",
+                    "homepage": "https://github.com/Respect/Validation/graphs/contributors"
+                }
+            ],
+            "description": "The most awesome validation engine ever created for PHP",
+            "homepage": "http://respect.github.io/Validation/",
+            "keywords": [
+                "respect",
+                "validation",
+                "validator"
+            ],
+            "support": {
+                "issues": "https://github.com/Respect/Validation/issues",
+                "source": "https://github.com/Respect/Validation/tree/2.4.4"
+            },
+            "time": "2025-06-07T00:07:21+00:00"
+        },
         {
             "name": "symfony/polyfill-ctype",
             "version": "v1.32.0",

+ 12 - 1
controllers/CommoditiesGetController.php

@@ -5,12 +5,23 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\CommodityModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class CommoditiesGetController
 {
     public function __invoke(ServerRequestInterface $request)
     {
         $query = $request->getQueryParams();
+
+        try {
+            val::arrayVal()
+                ->key('flag', val::optional(val::stringType()), false)
+                ->assert($query);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
+        }
+
         $flag = 'a';
         if (array_key_exists('flag', $query)) {
             $v = (string)$query['flag'];
@@ -28,4 +39,4 @@ class CommoditiesGetController
         }
         return ResponseLib::sendOk($rows);
     }
-}
+}

+ 24 - 7
controllers/CommodityCreateController.php

@@ -5,22 +5,39 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\CommodityModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class CommodityCreateController
 {
+    private CommodityModel $model;
+
+    public function __construct()
+    {
+        $this->model = new CommodityModel();
+    }
+
     public function __invoke(ServerRequestInterface $request)
     {
         $body = json_decode((string)$request->getBody(), true) ?? [];
-        $name = trim((string)($body['name'] ?? ''));
-        $flag = (string)($body['flag'] ?? 'a');
 
-        if ($name === '') {
-            return ResponseLib::sendFail('Validation failed: name is required', [], 'E_VALIDATE')->withStatus(400);
+        try {
+            val::key('name', val::stringType()->notEmpty()->length(1, 255))
+                ->key('flag', val::optional(val::stringType()->length(1, 10)), false)
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
 
-        $model = new CommodityModel();
-        $created = $model->create($name, $flag);
+        $name = trim($body['name']);
+        $flag = $body['flag'] ?? 'a';
+
+        $created = $this->model->create($name, $flag);
+
+        if (!$created) {
+            return ResponseLib::sendFail('Failed to create Commodity', [], 'E_DATABASE')->withStatus(500);
+        }
 
         return ResponseLib::sendOk($created, 'S_CREATED');
     }
-}
+}

+ 21 - 6
controllers/CommodityDeleteController.php

@@ -5,20 +5,35 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\CommodityModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class CommodityDeleteController
 {
+    private CommodityModel $model;
+
+    public function __construct()
+    {
+        $this->model = new CommodityModel();
+    }
+
     public function __invoke(ServerRequestInterface $request)
     {
         $body = json_decode((string)$request->getBody(), true) ?? [];
-        $id = isset($body['commodities_id']) ? (int)$body['commodities_id'] : 0;
-        if ($id <= 0) {
-            return ResponseLib::sendFail('Validation failed: invalid commodities_id', [], 'E_VALIDATE')->withStatus(400);
+
+        try {
+            val::key('commodities_id', val::intType()->positive())
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
-        $model = new CommodityModel();
-        $deleted = $model->delete($id);
+
+        $id = (int)$body['commodities_id'];
+
+        $deleted = $this->model->delete($id);
+
         return $deleted
             ? ResponseLib::sendOk(['deleted' => true], 'S_DELETED')
             : ResponseLib::sendFail('Commodity Not Found', [], 'E_DATABASE')->withStatus(204);
     }
-}
+}

+ 23 - 11
controllers/CommodityUpdateController.php

@@ -5,31 +5,43 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\CommodityModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class CommodityUpdateController
 {
+    private CommodityModel $model;
+
+    public function __construct()
+    {
+        $this->model = new CommodityModel();
+    }
+
     public function __invoke(ServerRequestInterface $request)
     {
         $body = json_decode((string)$request->getBody(), true) ?? [];
-        $id = isset($body['commodities_id']) ? (int)$body['commodities_id'] : 0;
-        $name = array_key_exists('name', $body) ? trim((string)$body['name']) : null;
-        $flag = array_key_exists('flag', $body) ? (string)$body['flag'] : null;
 
-        if ($id <= 0) {
-            return ResponseLib::sendFail('Validation failed: invalid commodities_id', [], 'E_VALIDATE')->withStatus(400);
+        try {
+            val::key('commodities_id', val::intType()->positive())
+                ->key('name', val::optional(val::stringType()->notEmpty()->length(1, 255)), false)
+                ->key('flag', val::optional(val::stringType()->length(1, 10)), false)
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
+
+        $id = (int)$body['commodities_id'];
+        $name = array_key_exists('name', $body) ? trim($body['name']) : null;
+        $flag = array_key_exists('flag', $body) ? $body['flag'] : null;
+
         if ($name === null && $flag === null) {
             return ResponseLib::sendFail('Validation failed: nothing to update', [], 'E_VALIDATE')->withStatus(400);
         }
-        if ($name !== null && $name === '') {
-            return ResponseLib::sendFail('Validation failed: name cannot be empty', [], 'E_VALIDATE')->withStatus(400);
-        }
 
-        $model = new CommodityModel();
-        $updated = $model->update($id, $name, $flag);
+        $updated = $this->model->update($id, $name, $flag);
         if (!$updated) {
             return ResponseLib::sendFail('Commodity Not Found or Not Updated', [], 'E_DATABASE')->withStatus(204);
         }
         return ResponseLib::sendOk($updated, 'S_UPDATED');
     }
-}
+}

+ 32 - 22
controllers/CompanyWithUserController.php

@@ -7,36 +7,48 @@ use Libs\ResponseLib;
 use Models\CompanyModel;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class CompanyWithUserController
 {
+    private CompanyModel $companyModel;
+    private UserModel $userModel;
+
+    public function __construct()
+    {
+        $this->companyModel = new CompanyModel();
+        $this->userModel = new UserModel();
+    }
+
     public function __invoke(ServerRequestInterface $request)
     {
         $body = json_decode((string)$request->getBody(), true) ?? [];
 
-        $required = [
-            'company_name',
-            'username','email','password','phone','address','city','state','zip','country',
-            'kyc','birthdate','cpf'
-        ];
-        foreach ($required as $field) {
-            if (!isset($body[$field]) || $body[$field] === '') {
-                return ResponseLib::sendFail("Missing field: $field", [], "E_VALIDATE")->withStatus(400);
-            }
-        }
-        if (!filter_var($body['email'], FILTER_VALIDATE_EMAIL)) {
-            return ResponseLib::sendFail("Invalid email format", [], "E_VALIDATE")->withStatus(400);
-        }
-        if (strlen($body['password']) < 8) {
-            return ResponseLib::sendFail("Password must be at least 8 characters", [], "E_VALIDATE")->withStatus(400);
+        try {
+            val::key('company_name', val::stringType()->notEmpty()->length(1, 255))
+                ->key('username', val::stringType()->notEmpty()->length(1, 100))
+                ->key('email', val::email())
+                ->key('password', val::stringType()->length(8, null))
+                ->key('phone', val::stringType()->notEmpty()->length(1, 50))
+                ->key('address', val::stringType()->notEmpty()->length(1, 255))
+                ->key('city', val::stringType()->notEmpty()->length(1, 100))
+                ->key('state', val::stringType()->notEmpty()->length(1, 100))
+                ->key('zip', val::stringType()->notEmpty()->length(1, 20))
+                ->key('country', val::stringType()->notEmpty()->length(1, 100))
+                ->key('kyc', val::intType())
+                ->key('birthdate', val::intType())
+                ->key('cpf', val::stringType()->notEmpty()->length(1, 50))
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
 
         try {
             $pdo = $GLOBALS['pdo'];
             $pdo->beginTransaction();
 
-            $companyModel = new CompanyModel();
-            $companyId = $companyModel->createCompany($body['company_name'], 'a');
+            $companyId = $this->companyModel->createCompany($body['company_name'], 'a');
             $roleId = 1;
             $chk = $pdo->prepare('SELECT 1 FROM "role" WHERE role_id = :rid');
             $chk->execute(['rid' => $roleId]);
@@ -45,7 +57,6 @@ class CompanyWithUserController
                 return ResponseLib::sendFail('Default role_id 1 not found', [], 'E_DATABASE')->withStatus(500);
             }
 
-            $userModel = new UserModel();
             $userPayload = [
                 'username' => $body['username'],
                 'email' => $body['email'],
@@ -62,7 +73,7 @@ class CompanyWithUserController
                 'company_id' => $companyId,
                 'role_id' => $roleId
             ];
-            $userData = $userModel->createUser($userPayload);
+            $userData = $this->userModel->createUser($userPayload);
             if (!$userData) {
                 $pdo->rollBack();
                 return ResponseLib::sendFail("Email already exists or creation failed", [], "E_VALIDATE")->withStatus(400);
@@ -88,7 +99,7 @@ class CompanyWithUserController
             }
 
             $stmt = $pdo->prepare('SELECT chain_id FROM "chain" WHERE chain_name = :name');
-            $stmt->execute(['name' => 'primalchain']);
+            $stmt->execute(['name' => 'polygon']);
             $chainId = $stmt->fetchColumn();
             if (!$chainId) {
                 $pdo->rollBack();
@@ -121,5 +132,4 @@ class CompanyWithUserController
             return ResponseLib::sendFail($e->getMessage(), [], 'E_DATABASE')->withStatus(500);
         }
     }
-}
-
+}

+ 23 - 11
controllers/LoginController.php

@@ -6,35 +6,47 @@ use Firebase\JWT\JWT;
 use Libs\ResponseLib;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class LoginController
 {
-    public function __invoke(ServerRequestInterface $request)
+    private UserModel $userModel;
+
+    public function __construct()
     {
-        $body = json_decode((string) $request->getBody(), true);
-        $email = $body['email'] ?? '';
-        $password = $body['password'] ?? '';
+        $this->userModel = new UserModel();
+    }
 
-        if (empty($email) || empty($password)) {
-            return ResponseLib::sendFail("Missing email or password", [], "E_VALIDATE")->withStatus(401);
+    public function __invoke(ServerRequestInterface $request)
+    {
+        $body = json_decode((string) $request->getBody(), true) ?? [];
+
+        try {
+            val::key('email', val::email())
+                ->key('password', val::stringType()->notEmpty()->length(8, null))
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(401);
         }
 
-        $userModel = new UserModel();
-        $user = $userModel->validateLogin($email, $password);
+        $email = $body['email'];
+        $password = $body['password'];
+
+        $user = $this->userModel->validateLogin($email, $password);
 
         if (!$user) {
             return ResponseLib::sendFail("Invalid credentials", [], "E_VALIDATE")->withStatus(401);
         }
 
-        // Gera JWT
         $payload = [
             'sub' => $user['user_id'],
             'email' => $user['user_email'],
             'iat' => time(),
-            'exp' => time() + 3600  // 1 hora
+            'exp' => time() + 3600
         ];
         $jwt = JWT::encode($payload, $_ENV['JWT_SECRET'], 'HS256');
 
         return ResponseLib::sendOk(['token' => $jwt, 'user_id' => $user['user_id'], 'company_id' => $user['company_id']]);
     }
-}   
+}

+ 30 - 20
controllers/RegisterController.php

@@ -5,33 +5,43 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class RegisterController
 {
-    public function __invoke(ServerRequestInterface $request)
-    {
-        $body = json_decode((string) $request->getBody(), true);
-        
-        $required = [
-            'username','email','password','phone','address','city','state','zip','country',
-            'kyc','birthdate','cpf','company_id','role_id'
-        ];
-        foreach ($required as $field) {
-            if (!isset($body[$field]) || $body[$field] === '') {
-                return ResponseLib::sendFail("Missing field: $field", [], "E_VALIDATE")->withStatus(400);
-            }
-        }
+    private UserModel $userModel;
 
-        if (!filter_var($body['email'], FILTER_VALIDATE_EMAIL)) {
-            return ResponseLib::sendFail("Invalid email format", [], "E_VALIDATE")->withStatus(400);
-        }
+    public function __construct()
+    {
+        $this->userModel = new UserModel();
+    }
 
-        if (strlen($body['password']) < 8) {
-            return ResponseLib::sendFail("Password must be at least 8 characters", [], "E_VALIDATE")->withStatus(400);
+    public function __invoke(ServerRequestInterface $request)
+    {
+        $body = json_decode((string) $request->getBody(), true) ?? [];
+
+        try {
+            val::key('username', val::stringType()->notEmpty()->length(1, 100))
+                ->key('email', val::email())
+                ->key('password', val::stringType()->length(8, null))
+                ->key('phone', val::stringType()->notEmpty()->length(1, 50))
+                ->key('address', val::stringType()->notEmpty()->length(1, 255))
+                ->key('city', val::stringType()->notEmpty()->length(1, 100))
+                ->key('state', val::stringType()->notEmpty()->length(1, 100))
+                ->key('zip', val::stringType()->notEmpty()->length(1, 20))
+                ->key('country', val::stringType()->notEmpty()->length(1, 100))
+                ->key('kyc', val::intType())
+                ->key('birthdate', val::intType())
+                ->key('cpf', val::stringType()->notEmpty()->length(1, 50))
+                ->key('company_id', val::intType()->positive())
+                ->key('role_id', val::intType()->positive())
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
 
-        $userModel = new UserModel();
-        $userData = $userModel->createUser($body);
+        $userData = $this->userModel->createUser($body);
 
         if (!$userData) {
             return ResponseLib::sendFail("Email already exists or creation failed", [], "E_VALIDATE")->withStatus(400);

+ 18 - 6
controllers/UserChangeEmailController.php

@@ -5,9 +5,18 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class UserChangeEmailController
 {
+    private UserModel $model;
+
+    public function __construct()
+    {
+        $this->model = new UserModel();
+    }
+
     public function __invoke(ServerRequestInterface $request)
     {
         $userId = (int)($request->getAttribute('api_user_id') ?? 0);
@@ -16,18 +25,21 @@ class UserChangeEmailController
         }
 
         $body = json_decode((string)$request->getBody(), true) ?? [];
-        $email = $body['email'] ?? '';
 
-        if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
-            return ResponseLib::sendFail('Validation failed: invalid email', [], 'E_VALIDATE')->withStatus(400);
+        try {
+            val::key('email', val::email())
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
 
-        $model = new UserModel();
-        $ok = $model->updateEmail($userId, $email);
+        $email = trim($body['email']);
+
+        $ok = $this->model->updateEmail($userId, $email);
         if (!$ok) {
             return ResponseLib::sendFail('Email already in use or update failed', [], 'E_VALIDATE')->withStatus(400);
         }
 
         return ResponseLib::sendOk(['user_id' => $userId, 'user_email' => $email], 'S_UPDATED');
     }
-}
+}

+ 21 - 7
controllers/UserChangePasswordController.php

@@ -5,9 +5,18 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class UserChangePasswordController
 {
+    private UserModel $model;
+
+    public function __construct()
+    {
+        $this->model = new UserModel();
+    }
+
     public function __invoke(ServerRequestInterface $request)
     {
         $userId = (int)($request->getAttribute('api_user_id') ?? 0);
@@ -16,22 +25,27 @@ class UserChangePasswordController
         }
 
         $body = json_decode((string)$request->getBody(), true) ?? [];
-        $current = $body['current_password'] ?? '';
-        $new = $body['new_password'] ?? '';
 
-        if ($current === '' || $new === '' || strlen($new) < 8) {
-            return ResponseLib::sendFail('Validation failed: invalid passwords', [], 'E_VALIDATE')->withStatus(400);
+        try {
+            val::key('current_password', val::stringType()->notEmpty())
+                ->key('new_password', val::stringType()->notEmpty()->length(8, null))
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
+
+        $current = $body['current_password'];
+        $new = $body['new_password'];
+
         if ($current === $new) {
             return ResponseLib::sendFail('New password must be different from current password', [], 'E_VALIDATE')->withStatus(400);
         }
 
-        $model = new UserModel();
-        $ok = $model->changePassword($userId, $current, $new);
+        $ok = $this->model->changePassword($userId, $current, $new);
         if (!$ok) {
             return ResponseLib::sendFail('Invalid current password or update failed', [], 'E_VALIDATE')->withStatus(400);
         }
 
         return ResponseLib::sendOk(['user_id' => $userId], 'S_UPDATED');
     }
-}
+}

+ 8 - 5
controllers/UserDeleteController.php

@@ -5,6 +5,8 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class UserDeleteController
 {
@@ -19,11 +21,12 @@ class UserDeleteController
     {
         $body = json_decode((string)$request->getBody(), true) ?? [];
 
-        if (!isset($body['company_id']) || !is_numeric($body['company_id']) || (int)$body['company_id'] <= 0) {
-            return ResponseLib::sendFail("Validation failed: invalid company_id", [], "E_VALIDATE")->withStatus(400);
-        }
-        if (!isset($body['user_id']) || !is_numeric($body['user_id']) || (int)$body['user_id'] <= 0) {
-            return ResponseLib::sendFail("Validation failed: invalid user_id", [], "E_VALIDATE")->withStatus(400);
+        try {
+            val::key('company_id', val::intType()->positive())
+                ->key('user_id', val::intType()->positive())
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
 
         $companyId = (int) $body['company_id'];

+ 7 - 2
controllers/UserGetController.php

@@ -5,6 +5,8 @@ namespace Controllers;
 use Libs\ResponseLib;
 use Models\UserModel;
 use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Validator as val;
+use Respect\Validation\Exceptions\ValidationException;
 
 class UserGetController
 {
@@ -19,8 +21,11 @@ class UserGetController
     {
         $body = json_decode((string)$request->getBody(), true) ?? [];
 
-        if (!isset($body['company_id']) || !is_numeric($body['company_id']) || (int)$body['company_id'] <= 0) {
-            return ResponseLib::sendFail("Validation failed: invalid company_id", [], "E_VALIDATE")->withStatus(400);
+        try {
+            val::key('company_id', val::intType()->positive())
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
         }
 
         $companyId = (int) $body['company_id'];