diff --git a/.htaccess b/.htaccess index 7a95090..8e35b47 100644 --- a/.htaccess +++ b/.htaccess @@ -2,6 +2,10 @@ Options +FollowSymlinks RewriteEngine On +# ! < TEAMS API > ! # +RewriteRule ^api/v1/(teams\/.*|teams)$ /api/internal/v1/teams/router.php [L,NC] +# ! ! # + RewriteBase / diff --git a/api/impl/v1/teams/create_team.php b/api/impl/v1/teams/create_team.php new file mode 100644 index 0000000..442c1cb --- /dev/null +++ b/api/impl/v1/teams/create_team.php @@ -0,0 +1,39 @@ + (new DataValidatorRule()) + ->must_be_defined() + ->must_be_string() + ->string_must_have_length(TEAM_NAME_MIN_LENGTH, TEAM_NAME_MAX_LENGTH) + ]); + + if (!$validationResult->isSuccess()) + { + return new BadArgumentsApiResult([ + "error_code" => "BODY_NOT_VALIDATED", + "error" => [ + "invalid_rule_code" => $validationResult->getInvalidRuleCode(), + "invalid_rule_details" => $validationResult->getInvalidRuleDetails() + ] + ]); + } + + // TODO: create team + + return new OkApiResult(new stdClass); + } +} + +ApiRouter::on("/api/v1/teams/create", CreateTeamApiController::class); \ No newline at end of file diff --git a/api/impl/v1/teams/router.php b/api/impl/v1/teams/router.php new file mode 100644 index 0000000..c1cbf66 --- /dev/null +++ b/api/impl/v1/teams/router.php @@ -0,0 +1,4 @@ +parameters = $parameters; + } + + protected function getParameter(mixed $key): mixed { + return $this->parameters[$key]; + } +} + class JsonBodyReader { - public static function read() { + public static function read(): array { $result = json_decode(file_get_contents('php://input'), true); if (json_last_error() !== JSON_ERROR_NONE) throw new Exception("could not decode json, code: " . json_last_error()); @@ -127,7 +139,7 @@ public function getContentType(): string { } class ApiControllerExecutor { - public static function execute(ApiController $controller, ApiResultSerializer $serializer) { + public static function execute(mixed $controller, ApiResultSerializer $serializer) { try { $method = $_SERVER['REQUEST_METHOD']; @@ -157,7 +169,7 @@ public static function execute(ApiController $controller, ApiResultSerializer $s } catch (Exception $exception) { error_log($exception->getMessage()); Logging::PutLog("Got exception in " . $controller::class . ":" . $exception->getMessage()); - $result = new UnknownErrorResult(); + $result = new UnknownErrorResult(["message" => $exception->getMessage()]); } http_response_code($result->getStatusCode()); @@ -165,3 +177,40 @@ public static function execute(ApiController $controller, ApiResultSerializer $s echo $serializer->serialize($result); } } + +class ApiRouter { + private static ?ApiResultSerializer $defaultSerializer; + + public static function getSerializer(): ApiResultSerializer { + if (!isset(self::$defaultSerializer)) + self::$defaultSerializer = new JsonApiResultSerializer; + + return self::$defaultSerializer; + } + + public static function setSerializer(ApiResultSerializer $serializer): void { + self::$defaultSerializer = $serializer; + } + + public static function on(string $regex, string $controller_class_name): void { + $params = $_SERVER['REQUEST_URI']; + $params = (stripos($params, "/") !== 0) ? "/" . $params : $params; + $regex = str_replace('/', '\/', $regex); + + $is_match = preg_match('/^' . ($regex) . '$/', $params, $matches, PREG_OFFSET_CAPTURE); + + if ($is_match) { + // first value is normally the route, lets remove it + array_shift($matches); + // Get the matches as parameters + $params = array_map(function ($param) { + return $param[0]; + }, $matches); + + $ref = new ReflectionClass($controller_class_name); + $instance = $ref->newInstanceArgs(array($params)); + + ApiControllerExecutor::execute($instance, self::getSerializer()); + } + } +} \ No newline at end of file diff --git a/global/php/data_validator.php b/global/php/data_validator.php new file mode 100644 index 0000000..04d11be --- /dev/null +++ b/global/php/data_validator.php @@ -0,0 +1,96 @@ +rules = array(); + } + + private function add_function_rule(string $rule_code, \Closure $closure, ?array $details = null) { + array_push($this->rules, [$closure, $rule_code, $details]); + } + + public function must_be_int(): DataValidatorRule { + $this->add_function_rule("MUST_BE_INT", function($v) { return is_int($v); }); + + return $this; + } + + public function must_be_string(): DataValidatorRule { + $this->add_function_rule("MUST_BE_STRING", function($v) { return is_string($v); }); + + return $this; + } + + public function string_must_have_length($minLength, $maxLength): DataValidatorRule { + $this->add_function_rule( + "STRING_MUST_HAVE_LENGTH", + function($v) use ($minLength, $maxLength) { + $len = strlen($v); + return $minLength <= $len && $len <= $maxLength; + }, + [ "min_length" => $minLength, "max_length" => $maxLength ] + ); + + return $this; + } + + public function must_be_defined(): DataValidatorRule { + $this->add_function_rule("MUST_BE_DEFINED", function($v) { + return isset($v); + }); + + return $this; + } + + public function validate($v): DataValidationResult { + foreach ($this->rules as $rule) { + if (!$rule[0]($v)) + return new DataValidationResult(false, $rule[1], $rule[2]); + } + + return new DataValidationResult(true);; + } +} + +class DataValidationResult { + private bool $success; + private ?string $invalid_rule_code; + private ?array $invalid_rule_code_details; + + public function __construct( + bool $success, + ?string $invalid_rule_code = null, + ?array $invalid_rule_code_details = null) + { + $this->success = $success; + $this->invalid_rule_code = $invalid_rule_code; + $this->invalid_rule_code_details = $invalid_rule_code_details; + } + + public function isSuccess(): bool { + return $this->success; + } + + public function getInvalidRuleCode(): ?string { + return $this->invalid_rule_code; + } + + public function getInvalidRuleDetails(): ?array { + return $this->invalid_rule_code_details; + } +} + +class DataValidator { + public static function validate_associative_array(array $array, array $rules): DataValidationResult { + foreach ($rules as $key => $rule) { + $validationResult = $rule->validate($array[$key]); + + if (!$validationResult->isSuccess()) + return $validationResult; + } + + return new DataValidationResult(true); + } +} \ No newline at end of file diff --git a/global/php/functions.php b/global/php/functions.php index d7d6906..8f073ad 100644 --- a/global/php/functions.php +++ b/global/php/functions.php @@ -756,7 +756,12 @@ function osekai_http_request() require_once($_SERVER['DOCUMENT_ROOT'] . "/global/php/osekaiHttpRequest.php"); } -function json_validator() +function data_validator() { - require_once($_SERVER['DOCUMENT_ROOT'] . "/global/php/json_validator.php"); + require_once($_SERVER['DOCUMENT_ROOT'] . "/global/php/data_validator.php"); +} + +function teams_rules() +{ + require_once($_SERVER['DOCUMENT_ROOT'] . "/global/php/rules/teams.php"); } \ No newline at end of file diff --git a/global/php/json_validator.php b/global/php/json_validator.php deleted file mode 100644 index 60ec5eb..0000000 --- a/global/php/json_validator.php +++ /dev/null @@ -1,39 +0,0 @@ -rules = array(); - } - - private function add_function_rule(\Closure $closure) { - array_push($this->rules, $closure); - } - - public function must_be_int(): JsonValidatorRule { - $this->add_function_rule(function($v) { return is_int($v); }); - - return $this; - } - - public function validate($v): bool { - foreach ($this->rules as $rule) { - if (!$rule($v)) - return false; - } - - return true; - } -} - -class JsonValidator { - public static function validate_associative_array(array $array, array $rules): bool { - foreach ($array as $key => $value) { - if (isset($rules[$key]) && !$rules[$key]->validate($value)) - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/global/php/rules/teams.php b/global/php/rules/teams.php new file mode 100644 index 0000000..437f294 --- /dev/null +++ b/global/php/rules/teams.php @@ -0,0 +1,12 @@ + (new JsonValidatorRule())->must_be_int() - ])) { + if (!DataValidator::validate_associative_array($requestJson, [ + 'medal_id' => (new DataValidatorRule())->must_be_int() + ])->isSuccess()) { return new BadArgumentsApiResult(["message" => "Invalid medal_id"]); } @@ -47,8 +47,8 @@ public function delete(): ApiResult { $requestJson = JsonBodyReader::read(); - if (!JsonValidator::validate_associative_array($requestJson, [ - 'medal_id' => (new JsonValidatorRule())->must_be_int() + if (!DataValidator::validate_associative_array($requestJson, [ + 'medal_id' => (new DataValidatorRule())->must_be_int() ])) { return new BadArgumentsApiResult(["message" => "Invalid medal_id"]); }