diff --git a/api/endpoints/coffee/GET.php b/api/endpoints/coffee/GET.php deleted file mode 100755 index 4ee9144..0000000 --- a/api/endpoints/coffee/GET.php +++ /dev/null @@ -1,39 +0,0 @@ -db->for(CoffeeModel::TABLE) - ->order([CoffeeModel::DATE_TIMESTAMP_CREATED->value => "DESC"]) - ->limit(self::LIST_LIMIT) - ->select([ - CoffeeModel::ID->value, - CoffeeModel::DATE_TIMESTAMP_CREATED->value - ]); - - return parent::is_mysqli_result($resp) - ? new Response($resp->fetch_all(MYSQLI_ASSOC)) - : $this->resp_database_error(); - } - } \ No newline at end of file diff --git a/api/endpoints/coffee/POST.php b/api/endpoints/coffee/POST.php deleted file mode 100755 index 5643942..0000000 --- a/api/endpoints/coffee/POST.php +++ /dev/null @@ -1,36 +0,0 @@ -db->for(CoffeeModel::TABLE) - ->insert([ - CoffeeModel::ID->value => $id, - CoffeeModel::DATE_TIMESTAMP_CREATED->value => time(), - ]); - - // Return 201 Created and entity id if successful - return $insert ? new Response($id, 201) : $this->resp_database_error(); - } - } \ No newline at end of file diff --git a/api/endpoints/media/GET.php b/api/endpoints/media/GET.php deleted file mode 100755 index bda4957..0000000 --- a/api/endpoints/media/GET.php +++ /dev/null @@ -1,95 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(MediaModel::ID->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(self::GET_DISPOSITION_KEY)) - ->type(Type::ENUM, MediaDispositionEnum::values()) - ->default(MediaDispositionEnum::METADATA->value) - ]); - } - - // # Helper methods - - private function fetch_srcset(string $id): array { - $resp = $this->db->for(WorkTagsModel::TABLE) - ->where([WorkTagsModel::ANCHOR->value => $id]) - ->select(WorkTagsModel::NAME->value); - - return parent::is_mysqli_result($resp) ? $resp->fetch_all(MYSQLI_ASSOC) : []; - } - - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - - public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - $resp = $this->db->for(MediaModel::TABLE) - ->where([MediaModel::ID->value => $_GET[MediaModel::ID->value]]) - ->select([ - MediaModel::ID->value, - MediaModel::NAME->value, - MediaModel::TYPE->value, - MediaModel::MIME->value, - MediaModel::EXTENSION->value, - MediaModel::SRCSET->value, - MediaModel::DATE_TIMESTAMP_CREATED->value, - ]); - - // Bail out if something went wrong retrieving rows from the database - if (!parent::is_mysqli_result($resp)) { - return $this->resp_database_error(); - } - - $media = $resp->fetch_assoc(); - $test = true; - } - } \ No newline at end of file diff --git a/api/endpoints/media/POST.php b/api/endpoints/media/POST.php deleted file mode 100755 index 1bd7916..0000000 --- a/api/endpoints/media/POST.php +++ /dev/null @@ -1,117 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(MediaModel::ID->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(parent::gen_uuid4()), - - (new Rules(MediaModel::NAME->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(null), - - (new Rules(MediaModel::TYPE->value)) - ->type(Type::ENUM, MediaTypeEnum::values()) - ->default(null), - - (new Rules(MediaModel::EXTENSION->value)) - ->type(Type::STRING) - ->min(3) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(null), - - (new Rules(MediaModel::MIME->value)) - ->type(Type::STRING) - ->min(3) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(null), - - (new Rules(MediaModel::SRCSET->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(null) - ]); - } - - // # Helper methods - - // Returns true if an srcset exists for provided key - private static function media_srcset_exists(): bool { - // No srcet get parameter has been set - if (empty($_POST[MediaModel::SRCSET->value])) { - return true; - } - - // Check if the provided srcset exists by calling the srcset endpoint - return Call("media/srcset?id={$_POST[MediaModel::SRCSET->value]}", Method::GET)->ok; - } - - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - - public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - // Bail out if an srcset doesn't exist - if (!self::media_srcset_exists()) { - return new Response("No media srcset exists with id '{$_POST[MediaModel::SRCSET->value]}'", 404); - } - - $insert = $this->db->for(MediaModel::TABLE) - ->insert([ - MediaModel::ID->value => $_POST[MediaModel::ID->value], - MediaModel::NAME->value => $_POST[MediaModel::NAME->value], - MediaModel::MIME->value => $_POST[MediaModel::MIME->value], - // Strip dots from extension string if set - MediaModel::EXTENSION->value => $_POST[MediaModel::EXTENSION->value] - ? str_replace(".", "", $_POST[MediaModel::EXTENSION->value]) - : null, - MediaModel::SRCSET->value => $_POST[MediaModel::SRCSET->value], - MediaModel::DATE_TIMESTAMP_CREATED->value => time() - ]); - - // Return media id if insert was successful - return $insert - ? new Response($_POST[MediaModel::ID->value], 201) - : $this->resp_database_error(); - } - } \ No newline at end of file diff --git a/api/endpoints/media/srcset/GET.php b/api/endpoints/media/srcset/GET.php deleted file mode 100755 index f9a0599..0000000 --- a/api/endpoints/media/srcset/GET.php +++ /dev/null @@ -1,106 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(MediaSrcsetModel::ID->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - } - - // # Helper methods - - // Get metadata for the requested srcset - private function get_srcset(): array|false { - $srcset = $this->db->for(MediaSrcsetModel::TABLE) - ->where([MediaSrcsetModel::ID->value => $_GET[MediaSrcsetModel::ID->value]]) - ->select([MediaSrcsetModel::ANCHOR_DEFAULT->value]); - - // Something went wrong retrieving rows from the database - if (!parent::is_mysqli_result($srcset)) { - return false; - } - - // Return assoc array of srcset data if it exists - return $srcset->num_rows === 1 ? $srcset->fetch_assoc() : false; - } - - // Get all media entities that are part of the requested srcset - private function get_srcset_media(): mysqli_result|false { - $media = $this->db->for(MediaModel::TABLE) - ->where([MediaModel::SRCSET->value => $_GET[MediaSrcsetModel::ID->value]]) - ->select([ - MediaModel::ID->value, - MediaModel::TYPE->value, - MediaModel::MIME->value, - MediaModel::EXTENSION->value - ]); - - return parent::is_mysqli_result($media) ? $media : false; - } - - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - - public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - // Get srcset data - $srcset = $this->get_srcset(); - if (!$srcset) { - return new Response("No media srcset exist with id '{$_GET[MediaSrcsetModel::ID->value]}'", 404); - } - - $media = $this->get_srcset_media(); - if (!$media) { - return new Response("Failed to fetch srcset media", 500); - } - - $media_entities = $media->fetch_all(MYSQLI_ASSOC); - - // This is the id of the media entity that is considered the default or "fallback" - $srcet_default_media_id = $srcset[MediaSrcsetModel::ANCHOR_DEFAULT->value]; - - // Return assoc array of all media entities that are in this srcset - return new Response([ - // Return default media entity separately from the rest of the srcset as an assoc array - "default" => array_filter($media_entities, fn(array $entity) => $entity[MediaModel::ID->value] === $srcet_default_media_id)[0], - // Return all media that isn't default as array of assoc arrays - "srcset" => array_filter($media_entities, fn(array $entity) => $entity[MediaModel::ID->value] !== $srcet_default_media_id) - ]); - } - } \ No newline at end of file diff --git a/api/endpoints/media/srcset/POST.php b/api/endpoints/media/srcset/POST.php deleted file mode 100755 index 609624a..0000000 --- a/api/endpoints/media/srcset/POST.php +++ /dev/null @@ -1,55 +0,0 @@ -code !== 404) { - // Wow a UUID4 collision... buy a lottery ticket - if ($srcset_existing->code === 200) { - return $this->main(); - } - - // Failed to get srcset - return new Response("Something went wrong when checking if the srcset exists", 500); - } - - // Create new srcset entity - $insert = $this->db->for(MediaSrcsetModel::TABLE) - ->insert([ - MediaSrcsetModel::ID->value => $id - ]); - - // Return created srcset id if successful - return $insert - ? new Response($id, 201) - : $this->resp_database_error(); - } - } \ No newline at end of file diff --git a/api/endpoints/messages/POST.php b/api/endpoints/messages/POST.php index e2a7758..571493a 100755 --- a/api/endpoints/messages/POST.php +++ b/api/endpoints/messages/POST.php @@ -6,14 +6,11 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use Reflect\Method; - use function Reflect\Call; - use VLW\API\Databases\VLWdb\VLWdb; use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel; require_once Path::root("src/databases/VLWdb.php"); - require_once Path::root("src/databases/models/Messages.php"); + require_once Path::root("src/databases/models/Messages/Messages.php"); class POST_Messages extends VLWdb { protected Ruleset $ruleset; @@ -36,44 +33,15 @@ ]); } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - public function main(): Response { - //return new Response(["hello" => "maybe"], 500); + // Use copy of request body as entity + $entity = $_POST; - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } + $entity[MessagesModel::ID->value] = parent::gen_uuid4(); + $entity[MessagesModel::DATE_CREATED->value] = time(); - // Generate UUID for entity - $id = parent::gen_uuid4(); - - // Attempt to create new entity - $insert = $this->db->for(MessagesModel::TABLE) - ->insert([ - MessagesModel::ID->value => $id, - MessagesModel::EMAIL->value => $_POST["email"], - MessagesModel::MESSAGE->value => $_POST["message"], - MessagesModel::DATE_TIMESTAMP_CREATED->value => time(), - ]); - - // Bail out if insert failed - if (!$insert) { - return $this->resp_database_error(); - } - - // Return 201 Created and entity id - return new Response($id, 201); + return $this->db->for(MessagesModel::TABLE)->insert($_POST) === true + ? new Response($entity[MessagesModel::ID->value], 201) + : new Response("Failed to create message", 500); } } \ No newline at end of file diff --git a/api/endpoints/releases/POST.php b/api/endpoints/releases/POST.php deleted file mode 100755 index 7160ada..0000000 --- a/api/endpoints/releases/POST.php +++ /dev/null @@ -1,223 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(ReleasesPostModel::GITHUB_USER->value)) - ->required() - ->type(Type::STRING) - ->min(1), - - (new Rules(ReleasesPostModel::GITHUB_REPO->value)) - ->required() - ->type(Type::STRING) - ->min(1), - - (new Rules(ReleasesPostModel::GITHUB_TAG->value)) - ->required() - ->type(Type::STRING) - ->type(Type::NUMBER) - ->min(1) - ]); - - $this->curl = curl_init(); - - curl_setopt($this->curl, CURLOPT_USERAGENT, $_ENV["github"]["user_agent"]); - curl_setopt($this->curl, CURLOPT_HEADER, true); - curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->curl, CURLOPT_HTTPHEADER, [ - "Accept" => "application/vnd.github+json", - "Authorization" => "token {$_ENV["github"]["api_key"]}", - "X-GitHub-Api-Version" => "2022-11-28" - ]); - } - - // # GitHub - - // Generate HTML from a GitHub "auto-generate" release body - protected static function gh_auto_release_md_to_html(string $md): string { - $output = ""; - - // Parse each line of markdown - $lines = explode(PHP_EOL, $md); - - foreach ($lines as $i => $line) { - // Ignore header line from releases - if ($i < 1) continue; - - // Replace all URLs with HTMLAnchor tags, they will be PRs - $links = []; - preg_match_all(self::REGEX_URL, $line, $links, PREG_UNMATCHED_AS_NULL); - foreach ($links as $i => $link) { - if (empty($link)) continue; - - // Last crumb from link pathname will be the PR id - $pr_id = explode("/", $link[$i]); - $pr_id = end($pr_id); - - $line = str_replace($link, "{$pr_id}", $line); - } - - // Replace all at-handles with links to GitHub user profiles - $handles = []; - preg_match_all(self::REGEX_HANDLE, $line, $handles, PREG_UNMATCHED_AS_NULL); - foreach ($handles as $i => $handle) { - if (empty($handle)) continue; - - // GitHub user URL without the "@" - $url = "https://github.com/" . substr($handle[$i], 1); - - $line = str_replace($handle, "{$handle[$i]}", $line); - } - - $output .= "

{$line}

"; - } - - return $output; - } - - // Return fully qualified URL to GitHub API releases endpoint - private static function get_url(): string { - return implode("/", [ - self::GITHUB_API, - "repos", - $_POST[ReleasesPostModel::GITHUB_USER->value], - $_POST[ReleasesPostModel::GITHUB_REPO->value], - "releases", - "tags", - $_POST[ReleasesPostModel::GITHUB_TAG->value], - ]); - } - - // Fetch release information from GitHub API - private function fetch_release_data(): array { - $url = self::get_url(); - curl_setopt($this->curl, CURLOPT_URL, self::get_url()); - - $resp = curl_exec($this->curl); - - $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE); - $header = substr($resp, 0, $header_size); - $body = substr($resp, $header_size); - - return json_decode($body, true); - } - - // # Sup - - private function create_link_to_release_page(string $id, string $href): Response { - return Call("work/actions?id={$id}", Method::POST, [ - WorkActionsModel::DISPLAY_TEXT->value => "Release details", - WorkActionsModel::HREF->value => $href, - WorkActionsModel::EXTERNAL->value => true - ]); - } - - // Create a tag for entity - private function create_tag(string $id, WorkTagsNameEnum $tag): Response { - return Call("work/tags?id={$id}", Method::POST, [ - // Set "RELEASE" tag on new entity - WorkTagsModel::NAME->value => $tag->value - ]); - } - - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - $data = $this->fetch_release_data(); - if (!$data) { - return new Response("Failed to fetch release data", 500); - } - - - // Transform repo name to lowercase for summary title - $title = strtolower($_POST["repo"]); - - // Use repo name and tag name as heading for summary - $summary = "

Release {$title}@{$data["name"]}

"; - // Append HTML-ified release notes from GitHub to summary - $summary .= self::gh_auto_release_md_to_html($data["body"]); - - $date_published = new \DateTime($data["published_at"], new \DateTimeZone("UTC")); - - // Create work entity - $work_entity = Call("work", Method::POST, [ - WorkModel::SUMMARY->value => $summary, - // Convert time created to Unix timestamp for work endpoint - WorkModel::DATE_TIMESTAMP_CREATED->value => $date_published->format("U"), - ]); - - // Bail out if creating the work entity failed - if (!$work_entity->ok) { - return new Response("Failed to create work entity for release", 500); - } - - $work_entity_id = $work_entity->output(); - - // Create entity tags for release - $tags = [ - WorkTagsNameEnum::VLW, - WorkTagsNameEnum::RELEASE - ]; - foreach ($tags as $tag) { - // Create entity tag for release or exit if failed to create - if (!$this->create_tag($work_entity_id, $tag)->ok) { - return new Response("Failed to create {$tag->name} tag for release entity", 500); - } - } - - // Create link to release page on GitHub - if (!$this->create_link_to_release_page($work_entity_id, $data["html_url"])) { - return new Response("Failed to create link to release page on GitHub", 500); - } - - return new Response($work_entity_id, 201); - } - } \ No newline at end of file diff --git a/api/endpoints/work/DELETE.php b/api/endpoints/work/DELETE.php index d197646..d67c0ce 100755 --- a/api/endpoints/work/DELETE.php +++ b/api/endpoints/work/DELETE.php @@ -1,15 +1,16 @@ ruleset = new Ruleset(strict: true); - $this->ruleset->GET([ - (new Rules("id")) - ->required() + $this->ruleset->POST([ + (new Rules(WorkModel::ID->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkModel::TITLE->value)) + ->type(Type::STRING) + ->min(3) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkModel::SUMMARY->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_TEXT_MAX_LENGTH), + + (new Rules(WorkModel::IS_LISTABLE->value)) + ->type(Type::BOOLEAN), + + (new Rules(WorkModel::IS_READABLE->value)) + ->type(Type::BOOLEAN), + + (new Rules(WorkModel::DATE_MODIFIED->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGHT), + + (new Rules(WorkModel::DATE_CREATED->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGHT) ]); - } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to delete work data, please try again later", 503); + parent::__construct($this->ruleset); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - // Attempt to update the entity - $update = $this->db->for(WorkModel::TABLE) - ->where([WorkModel::ID->value => $_GET["id"]]) - ->update([ - WorkModel::IS_LISTABLE->value => false, - WorkModel::IS_READABLE->value => false - ]); - - return $update ? new Response($_GET["id"]) : $this->resp_database_error(); + return $this->db->for(FieldsEnumsModel::TABLE)->delete($_POST) === true + ? new Response(RESP_DELETE_OK) + : new Response("Failed to delete work entity", 500); } } \ No newline at end of file diff --git a/api/endpoints/work/GET.php b/api/endpoints/work/GET.php index 3a2fe0e..14b289b 100755 --- a/api/endpoints/work/GET.php +++ b/api/endpoints/work/GET.php @@ -1,136 +1,76 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules("id")) + (new Rules(WorkModel::ID->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(null) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkModel::TITLE->value)) + ->type(Type::STRING) + ->min(3) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkModel::SUMMARY->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_TEXT_MAX_LENGTH), + + (new Rules(WorkModel::IS_LISTABLE->value)) + ->type(Type::BOOLEAN) + ->default(true), + + (new Rules(WorkModel::IS_READABLE->value)) + ->type(Type::BOOLEAN) + ->default(true), + + (new Rules(WorkModel::DATE_MODIFIED->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGHT), + + (new Rules(WorkModel::DATE_CREATED->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGHT) ]); - } - // # Helper methods - - private function fetch_row_tags(string $id): array { - $resp = $this->db->for(WorkTagsModel::TABLE) - ->where([WorkTagsModel::ANCHOR->value => $id]) - ->select(WorkTagsModel::NAME->value); - - return parent::is_mysqli_result($resp) ? $resp->fetch_all(MYSQLI_ASSOC) : []; - } - - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - - private function resp_item_details(string $id): Response { - $resp = $this->db->for(WorkModel::TABLE) - ->where([ - WorkModel::ID->value => $id, - WorkModel::IS_READABLE->value => true - ]) - ->limit(1) - ->select([ - WorkModel::ID->value, - WorkModel::TITLE->value, - WorkModel::SUMMARY->value, - WorkModel::COVER_SRCSET->value, - WorkModel::DATE_YEAR->value, - WorkModel::DATE_MONTH->value, - WorkModel::DATE_DAY->value, - WorkModel::DATE_TIMESTAMP_MODIFIED->value, - WorkModel::DATE_TIMESTAMP_CREATED->value - ]); - - // Bail out if something went wrong retrieving rows from the database - if (!parent::is_mysqli_result($resp)) { - return $this->resp_database_error(); - } - - return $resp->num_rows === 1 - ? new Response($resp->fetch_assoc()) - : new Response("No entity with id '{$id}' was found", 404); + parent::__construct($this->ruleset); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - // Return details about a specific item by id - if (!empty($_GET["id"])) { - return $this->resp_item_details($_GET["id"]); - } - - $resp = $this->db->for(WorkModel::TABLE) - ->where([WorkModel::IS_LISTABLE->value => true]) - ->order([WorkModel::DATE_TIMESTAMP_CREATED->value => "DESC"]) + $response = $this->db->for(WorkModel::TABLE) + ->where($_GET) ->select([ WorkModel::ID->value, WorkModel::TITLE->value, WorkModel::SUMMARY->value, - WorkModel::COVER_SRCSET->value, - WorkModel::DATE_YEAR->value, - WorkModel::DATE_MONTH->value, - WorkModel::DATE_DAY->value, - WorkModel::DATE_TIMESTAMP_MODIFIED->value, - WorkModel::DATE_TIMESTAMP_CREATED->value + WorkModel::IS_LISTABLE->value, + WorkModel::IS_READABLE->value, + WorkModel::DATE_MODIFIED->value, + WorkModel::DATE_CREATED->value ]); - // Bail out if something went wrong retrieving rows from the database - if (!parent::is_mysqli_result($resp)) { - return $this->resp_database_error(); - } - - // Resolve foreign keys - $rows = []; - while ($row = $resp->fetch_assoc()) { - $row["tags"] = $this->fetch_row_tags($row["id"]); - - // Fetch actions for work entity by id from endpoint - $row["actions"] = (new Call(Endpoints::WORK_ACTIONS->value)) - ->params([WorkActionsModel::ANCHOR->value => $row[WorkModel::ID->value]]) - ->get()->output(); - - $rows[] = $row; - } - - return new Response($rows); + return $response->num_rows > 0 + ? new Response($response->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); } } \ No newline at end of file diff --git a/api/endpoints/work/PATCH.php b/api/endpoints/work/PATCH.php index e8e9be1..ef2c1ca 100755 --- a/api/endpoints/work/PATCH.php +++ b/api/endpoints/work/PATCH.php @@ -1,30 +1,28 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ @@ -52,19 +50,19 @@ (new Rules(WorkModel::IS_READABLE->value)) ->type(Type::BOOLEAN), - (new Rules(WorkModel::DATE_TIMESTAMP_CREATED->value)) + (new Rules(WorkModel::DATE_MODIFIED->value)) ->type(Type::NUMBER) - ->min(0) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGHT) + ->default(time()), + + (new Rules(WorkModel::DATE_CREATED->value)) + ->type(Type::NUMBER) + ->min(1) ->max(parent::MYSQL_INT_MAX_LENGHT) ]); - $this->get_existing_entity(); - - // Copy all provided post data into a new array - $this->updated_entity = $_POST; - - // Set date modified timestamp - $this->updated_entity[WorkModel::DATE_TIMESTAMP_MODIFIED->value] = time(); + parent::__construct(); } // Generate a slug URL from string @@ -72,130 +70,46 @@ return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input))); } - // # Helper methods - - private function get_existing_entity(): Response { - // Check if an entity already exists with slugified title from GET endpoint - $this->current_entity = Call("work?id={$_GET["id"]}", Method::GET); - - // Response is not 404 (Not found) so we can't create the entity - if ($this->current_entity->code !== 200) { - // Response is not a valid entity, something went wrong - if ($this->current_entity->code !== 404) { - return $this->resp_database_error(); - } - - // Return 402 Conflict - return new Response("No entity with id '{$_GET["id"]}' was found", 404); - } - - return $this->current_entity; + // Compute and return modeled year, month, and day from Unix timestamp in request body + private static function gen_date_created(): array { + return [ + WorkModel::DATE_YEAR->value => date("Y", $_POST[WorkModel::DATE_CREATED->value]), + WorkModel::DATE_MONTH ->value => date("n", $_POST[WorkModel::DATE_CREATED->value]), + WorkModel::DATE_DAY->value => date("j", $_POST[WorkModel::DATE_CREATED->value]) + ]; } - // Create new permalink for entity slug - private function create_permalink(string $slug): bool { - $create = Call("work/permalinks", Method::POST, [ - WorkPermalinksModel::SLUG->value => $slug, - WorkPermalinksModel::ANCHOR->value => $slug - ]); - - return $create->ok; - } - - // ## Updated entity - - private function change_slug(): bool { - if (!array_key_exists(WorkModel::ID->value, $this->updated_entity)) { - return true; - } - - // Generate new permalink for entity id - return $this->create_permalink($this->updated_entity[WorkModel::ID->value]); - } - - private function timestamp_to_dates(): void { - if (!array_key_exists(WorkModel::DATE_TIMESTAMP_CREATED->value, $this->updated_entity)) { - return; - } - - // Get timestamp from post data - $timestamp = $this->updated_entity[WorkModel::DATE_TIMESTAMP_CREATED->value]; - - // Update fractured dates from timestamp - $this->updated_entity[WorkModel::DATE_YEAR->value] = date("Y", $timestamp); - $this->updated_entity[WorkModel::DATE_MONTH ->value] = date("n", $timestamp); - $this->updated_entity[WorkModel::DATE_DAY->value] = date("j", $timestamp); - } - - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - - // Return a 422 Unprocessable Entity if there is nothing to change - private function resp_no_changes(): Response { - return new Response("No columns to update", 422); - } - - // Rollback changes and return error response - private function resp_permalink_error_rollback(): Response { - $update = $this->db->for(WorkModel::TABLE) - ->where([WorkModel::ID->value => $_GET["id"]]) - ->update($this->current_entity->output()); - - return $update - ? new Response("Failed to create new permalink for updated entity. Changes have been rolled back", 500) - : new Reponse("Failed to create new permalink for updated entity. Changes failed to rollback, this is bad.", 500); + private function get_entity_by_id(string $id): Response { + return (new Call(Endpoints::WORK->value))->params([ + WorkModel::ID->value => $id + ])->get(); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } + // Use copy of request body as entity + $entity = $_POST; - // Empty payload, nothing to do - if (empty($_POST)) { - return $this->resp_no_changes(); - } + // Generate a new slug id from title if changed + if ($_POST[WorkModel::TITLE->value]) { + $slug = $_POST[WorkModel::TITLE->value]; - // Generate new slug for entity if title is updated - if (array_key_exists(WorkModel::TITLE->value, $_POST)) { - // Generate URL slug from title text or UUID if undefined - $slug = self::gen_slug($_POST["title"]); - - // Save generated slug from title if it's different from existing slug - if ($slug !== $this->current_entity->output()[WorkModel::ID->value]) { - $this->updated_entity[WorkModel::ID->value] = $slug; + // Bail out if the slug generated from the new tite already exist + if ($this->get_entity_by_id($slug)) { + return new Response("An entity with this title already exist", 409); } + + // Add the new slug to update entity + $entity[WorkModel::ID] = $slug; } - // Update fractured dates from timestamp - $this->timestamp_to_dates(); - - // Attempt to update the entity - $update = $this->db->for(WorkModel::TABLE) - ->where([WorkModel::ID->value => $_GET["id"]]) - ->update($this->updated_entity); - - // Bail out if update failed - if (!$update) { - return $this->resp_database_error(); - } - - // Create new slug for entity if title was changed - if (!$this->change_slug()) { - return $this->resp_permalink_error_rollback(); + // Generate new work date fields from timestamp + if ($_POST[WorkModel::DATE_CREATED->value]) { + array_merge($entity, self::gen_date_created()); } - // Return 200 OK and new or existing entity slug as body - return new Response($this->current_entity->output()[WorkModel::ID->value]); + // Update entity by existing id + return $this->db->for(WorkModel::TABLE)->where([WorkModel::ID->value => $_GET[WorkModel::ID->value]])->update($entity) === true + ? new Response($_GET[WorkModel::ID->value]) + : new Response("Failed to update entity", 500); } } \ No newline at end of file diff --git a/api/endpoints/work/POST.php b/api/endpoints/work/POST.php index 90d0f0c..143cc4e 100755 --- a/api/endpoints/work/POST.php +++ b/api/endpoints/work/POST.php @@ -1,27 +1,28 @@ ruleset = new Ruleset(strict: true); $this->ruleset->POST([ @@ -37,12 +38,22 @@ ->max(parent::MYSQL_TEXT_MAX_LENGTH) ->default(null), - (new Rules(WorkModel::DATE_TIMESTAMP_CREATED->value)) + (new Rules(WorkModel::IS_LISTABLE->value)) + ->type(Type::BOOLEAN) + ->default(false), + + (new Rules(WorkModel::IS_READABLE->value)) + ->type(Type::BOOLEAN) + ->default(false), + + (new Rules(WorkModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) ->max(parent::MYSQL_INT_MAX_LENGHT) - ->default(null) + ->default(time()) ]); + + parent::__construct($this->ruleset); } // Generate a slug URL from string @@ -50,84 +61,51 @@ return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input))); } - // Create permalink for entity slug - private function create_permalink(string $slug): bool { - $create = Call("work/permalinks", Method::POST, [ - WorkPermalinksModel::SLUG->value => $slug, - WorkPermalinksModel::ANCHOR->value => $slug - ]); + // Compute and return modeled year, month, and day from a Unix timestamp + private static function gen_date_created(): array { + // Use provided timestamp in request + $date_created = $_POST[WorkModel::DATE_CREATED->value]; - return $create->ok; + return [ + WorkModel::DATE_YEAR->value => date("Y", $date_created), + WorkModel::DATE_MONTH ->value => date("n", $date_created), + WorkModel::DATE_DAY->value => date("j", $date_created) + ]; } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); + private function get_entity_by_id(string $id): Response { + return (new Call(Endpoints::WORK->value))->params([ + WorkModel::ID->value => $id + ])->get(); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } + // Use copy of request body as entity + $entity = $_POST; // Generate URL slug from title text or UUID if undefined - $slug = !empty($_POST["title"]) ? self::gen_slug($_POST["title"]) : parent::gen_uuid4(); + $entity[WorkModel::ID->value] = $_POST[WorkModel::TITLE->value] + ? self::gen_slug($_POST[WorkModel::TITLE->value]) + : parent::gen_uuid4(); - // Check if an entity already exists with slugified title from GET endpoint - $existing_entity = Call("work?id={$slug}", Method::GET); - // Response is not 404 (Not found) so we can't create the entity - if ($existing_entity->code !== 404) { - // Response is not a valid entity, something went wrong - if ($existing_entity->code !== 200) { - return $this->resp_database_error(); - } - - // Return 402 Conflict - return new Response("Entity with id '{$slug}' already exists", 402); + // Bail out here if a work entry with id had been created already + if ($this->get_entity_by_id($entity[WorkModel::ID->value])->ok) { + return new Response("An entity with id '{$slug}' already exist", 409); } - // Get created timestamp from payload or use current time if not specified - $created_timestamp = $_POST[WorkModel::DATE_TIMESTAMP_CREATED->value] - ? $_POST[WorkModel::DATE_TIMESTAMP_CREATED->value] - : time(); + // Generate the necessary date fields + array_merge($entity, self::gen_date_created()); - // Attempt to create new entity - $insert = $this->db->for(WorkModel::TABLE) - ->insert([ - WorkModel::ID->value => $slug, - WorkModel::TITLE->value => $_POST["title"], - WorkModel::SUMMARY->value => $_POST["summary"], - WorkModel::IS_LISTABLE->value => true, - WorkModel::IS_READABLE->value => true, - WorkModel::DATE_YEAR->value => date("Y", $created_timestamp), - WorkModel::DATE_MONTH ->value => date("n", $created_timestamp), - WorkModel::DATE_DAY->value => date("j", $created_timestamp), - WorkModel::DATE_TIMESTAMP_MODIFIED->value => null, - WorkModel::DATE_TIMESTAMP_CREATED->value => $created_timestamp, - ]); - - // Bail out if insert failed - if (!$insert) { - return $this->resp_database_error(); + // Let's try to insert the new entity + if (!$this->db->for(WorkModel::TABLE)->insert($entity)) { + return new Response("Failed to insert work entry", 500); } - // Create permalink for new entity - if (!$this->create_permalink($slug)) { - // Rollback created entity if permalink creation failed - Call("work", Method::DELETE, [WorkModel::ID->value => $slug]); - - return new Response("Failed to create permalink", 500); - } - - // Return 201 Created and entity slug as body - return new Response($slug, 201); + // Generate permalink for new entity + return (new Call(Endpoints::WORK_PERMALINKS->value))->post([ + WorkPermalinksModel::ID => $entity[WorkModel::ID->value], + WorkPermalinksModel::REF_WORK_ID => $entity[WorkModel::ID->value], + WorkPermalinksModel::DATE_CREATED => time() + ]); } } \ No newline at end of file diff --git a/api/endpoints/work/actions/DELETE.php b/api/endpoints/work/actions/DELETE.php index 2eb1e82..b64194b 100755 --- a/api/endpoints/work/actions/DELETE.php +++ b/api/endpoints/work/actions/DELETE.php @@ -6,9 +6,7 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use Reflect\Method; - use function Reflect\Call; - + use const VLW\API\RESP_DELETE_OK; use VLW\API\Databases\VLWdb\VLWdb; use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel; @@ -23,51 +21,15 @@ $this->ruleset = new Ruleset(strict: true); $this->ruleset->POST([ - (new Rules("id")) - ->required() - ->type(Type::STRING) + (new Rules(WorkActionsModel::REF_WORK_ID->value)) ->min(1) ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ]); } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); - } - public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - // Ensure the action exists by id - $existing_action = $this->db->for(WorkActionsModel::TABLE) - ->where([ - WorkActionsModel::ID->value => $_POST["id"] - ]) - ->select(null); - - // Return idempotent deletion if the action does not exist - if ($existing_action->num_rows === 0) { - return new Response($_POST["id"]); - } - - // Attempt to delete action by id - $delete = $this->db->for(WorkActionsModel::TABLE) - ->delete([ - WorkActionsModel::ID->value => $_POST["id"] - ]); - - // Return 201 Created and entity id as body if insert was successful - return $delete === true ? new Response($_POST["id"], 201) : $this->resp_database_error(); + return $this->db->for(WorkActionsModel::TABLE)->delete($_POST) === true + ? new Response(RESP_DELETE_OK) + : new Response("Failed to delete action for work entity", 500); } } \ No newline at end of file diff --git a/api/endpoints/work/actions/GET.php b/api/endpoints/work/actions/GET.php index 616dff9..6694c92 100644 --- a/api/endpoints/work/actions/GET.php +++ b/api/endpoints/work/actions/GET.php @@ -16,52 +16,31 @@ protected Ruleset $ruleset; public function __construct() { - parent::__construct(); $this->ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(WorkActionsModel::ANCHOR->value)) - ->required() + (new Rules(WorkActionsModel::REF_WORK_ID->value)) ->type(Type::STRING) ->min(1) ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ]); - } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); + parent::__construct($this->ruleset); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - $resp = $this->db->for(WorkActionsModel::TABLE) - ->where([WorkActionsModel::ANCHOR->value => $_GET[WorkActionsModel::ANCHOR->value]]) + $response = $this->db->for(WorkActionsModel::TABLE) + ->where($_GET) ->select([ + WorkActionsModel::REF_WORK_ID->value, WorkActionsModel::DISPLAY_TEXT->value, WorkActionsModel::HREF->value, WorkActionsModel::CLASS_LIST->value, WorkActionsModel::EXTERNAL->value ]); - // Bail out if something went wrong retrieving rows from the database - if (!parent::is_mysqli_result($resp)) { - return $this->resp_database_error(); - } - - return $resp->num_rows > 0 - ? new Response($resp->fetch_all(MYSQLI_ASSOC)) - : new Response([]); + return $response->num_rows > 0 + ? new Response($response->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); } } \ No newline at end of file diff --git a/api/endpoints/work/actions/POST.php b/api/endpoints/work/actions/POST.php index acca855..5587ddc 100755 --- a/api/endpoints/work/actions/POST.php +++ b/api/endpoints/work/actions/POST.php @@ -1,36 +1,36 @@ ruleset = new Ruleset(strict: true); - $this->ruleset->GET([ - (new Rules("id")) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - $this->ruleset->POST([ + (new Rules(WorkActionsModel::REF_WORK_ID->value)) + ->required() + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + (new Rules(WorkActionsModel::DISPLAY_TEXT->value)) ->required() ->type(Type::STRING) @@ -47,56 +47,31 @@ (new Rules(WorkActionsModel::CLASS_LIST->value)) ->type(Type::ARRAY) ->min(1) - ->max(4) ->default([]), (new Rules(WorkActionsModel::EXTERNAL->value)) ->type(Type::BOOLEAN) ->default(false) ]); + + parent::__construct($this->ruleset); } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); + private static function get_entity(): Response { + return (new Call(Endpoints::WORK->value))->params([ + WorkModel::ID->value => $_POST[WorkActionsModel::REF_WORK_ID->value] + ])->get(); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); + // Bail out if work entity could not be fetched + $entity = self::get_entity(); + if (!$entity->ok) { + return $entity; } - // Ensure an entity with the provided id exists - $entity = Call("work?id={$_GET["id"]}", Method::GET); - if ($entity->code !== 200) { - // Response from endpoint is not 404, something went wrong - if ($entity->code !== 404) { - return $this->resp_database_error(); - } - - return new Response("No entity with id '{$_GET["id"]}' was found", 404); - } - - // Attempt to create action for entity - $insert = $this->db->for(WorkActionsModel::TABLE) - ->insert([ - WorkActionsModel::ID->value => parent::gen_uuid4(), - WorkActionsModel::ANCHOR->value => $_GET["id"], - WorkActionsModel::DISPLAY_TEXT->value => $_POST[WorkActionsModel::DISPLAY_TEXT->value], - WorkActionsModel::HREF->value => $_POST[WorkActionsModel::HREF->value], - WorkActionsModel::CLASS_LIST->value => implode(",", $_POST[WorkActionsModel::CLASS_LIST->value]), - WorkActionsModel::EXTERNAL->value => $_POST[WorkActionsModel::EXTERNAL->value], - ]); - - // Return 201 Created and entity id as body if insert was successful - return $insert === true ? new Response($_GET["id"], 201) : $this->resp_database_error(); + return $this->db->for(WorkActionsModel::TABLE)->insert($_POST) === true + ? new Response($_POST[WorkActionsModel::REF_WORK_ID->value], 201) + : new Response("Failed to add action to work entity", 500); } } \ No newline at end of file diff --git a/api/endpoints/work/permalinks/GET.php b/api/endpoints/work/permalinks/GET.php index 9fe437b..4c35b6f 100755 --- a/api/endpoints/work/permalinks/GET.php +++ b/api/endpoints/work/permalinks/GET.php @@ -1,6 +1,5 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules("id")) - ->required() + (new Rules(WorkPermalinksModel::ID->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkPermalinksModel::REF_WORK_ID->value)) ->type(Type::STRING) ->min(1) ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ]); - } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to resolve permalink, please try again later", 503); + parent::__construct($this->ruleset); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } + $response = $this->db->for(WorkPermalinksModel::TABLE) + ->where($_GET) + ->select([ + WorkPermalinksModel::ID->value, + WorkPermalinksModel::REF_WORK_ID->value, + WorkPermalinksModel::DATE_CREATED->value + ]); - // Get all anchors that match the requested slug - $resolve = $this->db->for(WorkPermalinksModel::TABLE) - ->where([WorkPermalinksModel::SLUG->value => $_GET["id"]]) - ->select(WorkPermalinksModel::ANCHOR->value); - - // Return array of all matched work table ids. Or empty array if none found - return parent::is_mysqli_result($resolve) - ? new Response(array_column($resolve->fetch_all(MYSQLI_ASSOC), WorkPermalinksModel::ANCHOR->value)) - : $this->resp_database_error(); + return $response->num_rows > 0 + ? new Response($response->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); } } \ No newline at end of file diff --git a/api/endpoints/work/permalinks/POST.php b/api/endpoints/work/permalinks/POST.php index 6425316..2901217 100755 --- a/api/endpoints/work/permalinks/POST.php +++ b/api/endpoints/work/permalinks/POST.php @@ -1,83 +1,62 @@ ruleset = new Ruleset(strict: true); $this->ruleset->POST([ - (new Rules("slug")) + (new Rules(WorkPermalinksModel::ID->value)) ->required() ->type(Type::STRING) ->min(1) ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - (new Rules("anchor")) + (new Rules(WorkPermalinksModel::REF_WORK_ID->value)) ->required() ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkPermalinksModel::DATE_CREATED->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGHT) + ->default(time()) ]); + + parent::__construct($this->ruleset); } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to resolve permalink, please try again later", 503); + private static function get_entity(): Response { + return (new Call(Endpoints::WORK->value))->params([ + WorkModel::ID->value => $_POST[WorkTagsModel::REF_WORK_ID->value] + ])->get(); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); + // Bail out if work entity could not be fetched + $entity = self::get_entity(); + if (!$entity->ok) { + return $entity; } - // Check if an entity exists with slug - $existing_entity = Call("work?id={$_POST["slug"]}", Method::GET); - // Response is not 404 (Not found) so we can't create the entity - if ($existing_entity->code !== 200) { - // Response is not a valid entity, something went wrong - if ($existing_entity->code !== 404) { - return $this->resp_database_error(); - } - - // Return 402 Conflict - return new Response("No work entity with id '{$_POST["slug"]}' was found to permalink", 404); - } - - // Attempt to create new entity - $insert = $this->db->for(WorkPermalinksModel::TABLE) - ->insert([ - WorkPermalinksModel::SLUG->value => $_POST["slug"], - WorkPermalinksModel::ANCHOR->value => $_POST["anchor"], - WorkPermalinksModel::DATE_TIMESTAMP_CREATED->value => time(), - ]); - - // Return 201 Created and entity slug as body if insert was successful - return $insert === true ? new Response($_POST["slug"], 201) : $this->resp_database_error(); + return $this->db->for(WorkPermalinksModel::TABLE)->insert($_POST) === true + ? new Response($_POST[WorkPermalinksModel::ID->value], 201) + : new Response("Failed to add permalink to work entity", 500); } } \ No newline at end of file diff --git a/api/endpoints/work/tags/DELETE.php b/api/endpoints/work/tags/DELETE.php index f674469..2699806 100755 --- a/api/endpoints/work/tags/DELETE.php +++ b/api/endpoints/work/tags/DELETE.php @@ -6,75 +6,34 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use Reflect\Method; - use function Reflect\Call; - + use const VLW\API\RESP_DELETE_OK; use VLW\API\Databases\VLWdb\VLWdb; use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel; - use VLW\API\Databases\VLWdb\Models\Work\WorkTagsNameEnum; require_once Path::root("src/databases/VLWdb.php"); - require_once Path::root("src/databases/models/WorkTags.php"); + require_once Path::root("src/databases/models/Work/WorkTags.php"); class DELETE_WorkTags extends VLWdb { - protected Ruleset $ruleset; + private Ruleset $ruleset; public function __construct() { - parent::__construct(); $this->ruleset = new Ruleset(strict: true); - $this->ruleset->POST([ - (new Rules("id")) - ->required() - ->type(Type::STRING) + $this->ruleset->GET([ + (new Rules(WorkTagsModel::REF_WORK_ID->value)) ->min(1) ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - + (new Rules(WorkTagsModel::NAME->value)) - ->required() ->type(Type::ENUM, WorkTagsNameEnum::names()) ]); - } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); + parent::__construct($this->ruleset); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); - } - - // Ensure the tag exists for entity id - $existing_tag = $this->db->for(WorkTagsModel::TABLE) - ->where([ - WorkTagsModel::ANCHOR->value => $_POST["id"], - WorkTagsModel::NAME->value => $_POST["name"] - ]) - ->select(null); - - // Return idempotent deletion if the tag does not exist - if ($existing_tag->num_rows === 0) { - return new Response($_POST["id"]); - } - - // Attempt to delete tag for entity - $delete = $this->db->for(WorkTagsModel::TABLE) - ->delete([ - WorkTagsModel::ANCHOR->value => $_POST["id"], - WorkTagsModel::NAME->value => $_POST["name"] - ]); - - // Return 201 Created and entity id as body if insert was successful - return $delete === true ? new Response($_POST["id"], 201) : $this->resp_database_error(); + return $this->db->for(WorkTagsModel::TABLE)->delete($_POST) === true + ? new Response(RESP_DELETE_OK) + : new Response("Failed to delete value from document", 500); } } \ No newline at end of file diff --git a/api/endpoints/work/tags/GET.php b/api/endpoints/work/tags/GET.php new file mode 100644 index 0000000..33ae111 --- /dev/null +++ b/api/endpoints/work/tags/GET.php @@ -0,0 +1,45 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(WorkTagsModel::REF_WORK_ID->value)) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(WorkTagsModel::NAME->value)) + ->type(Type::ENUM, WorkTagsNameEnum::names()) + ]); + + parent::__construct($this->ruleset); + } + + public function main(): Response { + $response = $this->db->for(FieldsEnumsModel::TABLE) + ->where($_GET) + ->select([ + FieldsEnumsModel::REF_WORK_ID->value, + FieldsEnumsModel::NAME->value + ]); + + return $response->num_rows > 0 + ? new Response($response->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); + } + } \ No newline at end of file diff --git a/api/endpoints/work/tags/POST.php b/api/endpoints/work/tags/POST.php index d78c03c..a99205f 100755 --- a/api/endpoints/work/tags/POST.php +++ b/api/endpoints/work/tags/POST.php @@ -1,93 +1,60 @@ ruleset = new Ruleset(strict: true); - $this->ruleset->GET([ - (new Rules("id")) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - $this->ruleset->POST([ + (new Rules(WorkTagsModel::REF_WORK_ID->value)) + ->required() + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + (new Rules(WorkTagsModel::NAME->value)) ->required() ->type(Type::ENUM, WorkTagsNameEnum::names()) ]); + + parent::__construct($this->ruleset); } - // # Responses - - // Return 422 Unprocessable Content error if request validation failed - private function resp_rules_invalid(): Response { - return new Response($this->ruleset->get_errors(), 422); - } - - // Return a 503 Service Unavailable error if something went wrong with the database call - private function resp_database_error(): Response { - return new Response("Failed to get work data, please try again later", 503); + private static function get_entity(): Response { + return (new Call(Endpoints::WORK->value))->params([ + WorkModel::ID->value => $_POST[WorkTagsModel::REF_WORK_ID->value] + ])->get(); } public function main(): Response { - // Bail out if request validation failed - if (!$this->ruleset->is_valid()) { - return $this->resp_rules_invalid(); + // Bail out if work entity could not be fetched + $entity = self::get_entity(); + if (!$entity->ok) { + return $entity; } - // Ensure an entity with the provided id exists - $entity = Call("work?id={$_GET["id"]}", Method::GET); - if ($entity->code !== 200) { - // Response from endpoint is not 404, something went wrong - if ($entity->code !== 404) { - return $this->resp_database_error(); - } - - return new Response("No entity with id '{$_GET["id"]}' was found", 404); - } - - // Ensure the tag does not already exist for entity - $existing_tag = $this->db->for(WorkTagsModel::TABLE) - ->where([ - WorkTagsModel::ANCHOR->value => $_GET["id"], - WorkTagsModel::NAME->value => $_POST["name"] - ]) - ->select(null); - - // Bail out if this tag already exists - if ($existing_tag->num_rows !== 0) { - return new Response("Tag '{$_POST["name"]}' is already set on entity id '{$_GET["id"]}'", 402); - } - - // Attempt to create tag for entity - $insert = $this->db->for(WorkTagsModel::TABLE) - ->insert([ - WorkTagsModel::ANCHOR->value => $_GET["id"], - WorkTagsModel::NAME->value => $_POST["name"] - ]); - - // Return 201 Created and entity id as body if insert was successful - return $insert === true ? new Response($_GET["id"], 201) : $this->resp_database_error(); + return $this->db->for(WorkTagsModel::TABLE)->insert($_POST) === true + ? new Response($_POST[WorkTagsModel::REF_WORK_ID->value], 201) + : new Response("Failed to add tag to work entity", 500); } } \ No newline at end of file diff --git a/api/src/Endpoints.php b/api/src/Endpoints.php new file mode 100644 index 0000000..b049671 --- /dev/null +++ b/api/src/Endpoints.php @@ -0,0 +1,11 @@ +db = new MySQL( $_ENV["vlwdb"]["mariadb_host"], @@ -46,7 +55,8 @@ ); } - public static function is_mysqli_result(\mysqli_result|bool $resp): bool { - return $resp instanceof \mysqli_result; + // Bail out if provided ReflectRules\Ruleset is invalid + private static function eval_ruleset_or_exit(Ruleset $ruleset): ?Response { + return !$ruleset->is_valid() ? new Response($ruleset->get_errors(), 422) : null; } } \ No newline at end of file diff --git a/api/src/databases/models/Coffee.php b/api/src/databases/models/Coffee.php deleted file mode 100755 index 0d4377c..0000000 --- a/api/src/databases/models/Coffee.php +++ /dev/null @@ -1,10 +0,0 @@ -