From 959f3b9170acc7f9b4b4fc1988691b6f5f63f9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulin?= Date: Mon, 23 Mar 2026 21:28:14 -0300 Subject: [PATCH] =?UTF-8?q?Implementado=20plugin=20com=20api=20rest=20para?= =?UTF-8?q?=20usu=C3=A1rio=20favoritar=20posts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/class-api.php | 178 +++++++++++++++++++++++++++++ includes/class-database.php | 188 +++++++++++++++++++++++++++++++ includes/class-plugin.php | 54 +++++++++ uninstall.php | 20 ++++ wordpress-back-end-challenge.php | 38 +++++++ 5 files changed, 478 insertions(+) create mode 100644 includes/class-api.php create mode 100644 includes/class-database.php create mode 100644 includes/class-plugin.php create mode 100644 uninstall.php create mode 100644 wordpress-back-end-challenge.php diff --git a/includes/class-api.php b/includes/class-api.php new file mode 100644 index 00000000..57a3ef9a --- /dev/null +++ b/includes/class-api.php @@ -0,0 +1,178 @@ +database = $database; + } + + /** + * Registra as rotas da REST API. + * + * @return void + */ + public function register_routes() { + // Endpoint para alternar favorito (favorite/unfavorite). + register_rest_route( + $this->namespace, + '/toggle', + array( + 'methods' => 'POST', + 'callback' => array( $this, 'toggle_favorite' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'args' => array( + 'post_id' => array( + 'required' => true, + 'validate_callback' => array( $this, 'validate_post_id' ), + 'sanitize_callback' => 'absint', + ), + ), + ) + ); + + // Endpoint para listar os posts favoritados. + register_rest_route( + $this->namespace, + '/list', + array( + 'methods' => 'GET', + 'callback' => array( $this, 'list_favorites' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + } + + /** + * Verifica se o usuário está autenticado. + * + * @return bool True se o usuário estiver logado; false caso contrário. + */ + public function check_permission() { + return is_user_logged_in(); + } + + /** + * Valida o parâmetro post_id. + * + * Verifica se é um número inteiro positivo e se o post existe. + * + * @param mixed $value Valor do parâmetro. + * @param WP_REST_Request $request Requisição REST. + * @param string $param Nome do parâmetro. + * @return bool|WP_Error True se válido; WP_Error caso contrário. + */ + public function validate_post_id( $value, $request, $param ) { + $post_id = absint( $value ); + + if ( $post_id <= 0 ) { + return new WP_Error( + 'rest_invalid_param', + 'O parâmetro post_id deve ser um número inteiro positivo.', + array( 'status' => 400 ) + ); + } + + // Verifica se o post existe. + $post = get_post( $post_id ); + if ( null === $post ) { + return new WP_Error( + 'rest_post_not_found', + 'O post informado não foi encontrado.', + array( 'status' => 404 ) + ); + } + + return true; + } + + /** + * Alterna o status de favorito de um post (toggle). + * + * Se o post já estiver favoritado, remove. Caso contrário, adiciona. + * + * @param WP_REST_Request $request Requisição REST. + * @return WP_REST_Response Resposta com o status atualizado. + */ + public function toggle_favorite( WP_REST_Request $request ) { + $user_id = get_current_user_id(); + $post_id = $request->get_param( 'post_id' ); + + $is_favorited = $this->database->is_favorited( $user_id, $post_id ); + + if ( $is_favorited ) { + // Remove dos favoritos. + $this->database->remove_favorite( $user_id, $post_id ); + $favorited = false; + } else { + // Adiciona aos favoritos. + $this->database->add_favorite( $user_id, $post_id ); + $favorited = true; + } + + // Invalida o cache após a alteração. + $this->database->clear_cache( $user_id ); + + return new WP_REST_Response( + array( + 'favorited' => $favorited, + ), + 200 + ); + } + + /** + * Retorna a lista de posts favoritados pelo usuário logado. + * + * @param WP_REST_Request $request Requisição REST. + * @return WP_REST_Response Resposta com a lista de IDs favoritados. + */ + public function list_favorites( WP_REST_Request $request ) { + $user_id = get_current_user_id(); + $favorites = $this->database->get_favorites( $user_id ); + + return new WP_REST_Response( + array( + 'favorites' => $favorites, + ), + 200 + ); + } +} diff --git a/includes/class-database.php b/includes/class-database.php new file mode 100644 index 00000000..da9eedc4 --- /dev/null +++ b/includes/class-database.php @@ -0,0 +1,188 @@ +table_name = $wpdb->prefix . 'favorites'; + } + + /** + * Cria a tabela de favoritos utilizando dbDelta. + * + * Deve ser chamada na ativação do plugin. + * + * @return void + */ + public function create_table() { + global $wpdb; + + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE {$this->table_name} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + post_id BIGINT UNSIGNED NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY user_post (user_id, post_id) + ) {$charset_collate};"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); + } + + /** + * Remove a tabela de favoritos do banco de dados. + * + * Utilizada na desinstalação do plugin. + * + * @return void + */ + public function drop_table() { + global $wpdb; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( "DROP TABLE IF EXISTS {$this->table_name}" ); + } + + /** + * Verifica se um post já está favoritado pelo usuário. + * + * @param int $user_id ID do usuário. + * @param int $post_id ID do post. + * @return bool True se o registro existir; false caso contrário. + */ + public function is_favorited( $user_id, $post_id ) { + global $wpdb; + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $result = $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(*) FROM {$this->table_name} WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + ) + ); + + return (int) $result > 0; + } + + /** + * Adiciona um post aos favoritos do usuário. + * + * @param int $user_id ID do usuário. + * @param int $post_id ID do post. + * @return bool True em caso de sucesso; false em caso de falha. + */ + public function add_favorite( $user_id, $post_id ) { + global $wpdb; + + $result = $wpdb->insert( + $this->table_name, + array( + 'user_id' => $user_id, + 'post_id' => $post_id, + ), + array( '%d', '%d' ) + ); + + return false !== $result; + } + + /** + * Remove um post dos favoritos do usuário. + * + * @param int $user_id ID do usuário. + * @param int $post_id ID do post. + * @return bool True em caso de sucesso; false em caso de falha. + */ + public function remove_favorite( $user_id, $post_id ) { + global $wpdb; + + $result = $wpdb->delete( + $this->table_name, + array( + 'user_id' => $user_id, + 'post_id' => $post_id, + ), + array( '%d', '%d' ) + ); + + return false !== $result; + } + + /** + * Retorna a lista de IDs de posts favoritados pelo usuário. + * + * Utiliza a Transient API para cache, melhorando a performance. + * + * @param int $user_id ID do usuário. + * @return array Lista de IDs de posts favoritados. + */ + public function get_favorites( $user_id ) { + $transient_key = 'wpbec_favorites_user_' . $user_id; + $cached = get_transient( $transient_key ); + + if ( false !== $cached ) { + return $cached; + } + + global $wpdb; + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $results = $wpdb->get_col( + $wpdb->prepare( + "SELECT post_id FROM {$this->table_name} WHERE user_id = %d ORDER BY created_at DESC", + $user_id + ) + ); + + $favorites = array_map( 'intval', $results ); + + // Armazena no cache por 1 hora. + set_transient( $transient_key, $favorites, HOUR_IN_SECONDS ); + + return $favorites; + } + + /** + * Invalida o cache de favoritos de um usuário. + * + * Deve ser chamada sempre que os favoritos forem alterados. + * + * @param int $user_id ID do usuário. + * @return void + */ + public function clear_cache( $user_id ) { + delete_transient( 'wpbec_favorites_user_' . $user_id ); + } +} diff --git a/includes/class-plugin.php b/includes/class-plugin.php new file mode 100644 index 00000000..2294c8f9 --- /dev/null +++ b/includes/class-plugin.php @@ -0,0 +1,54 @@ +database = new WPBEC_Database(); + $this->api = new WPBEC_Api( $this->database ); + } + + /** + * Registra os hooks necessários para o funcionamento do plugin. + * + * @return void + */ + public function run() { + add_action( 'rest_api_init', array( $this->api, 'register_routes' ) ); + } +} diff --git a/uninstall.php b/uninstall.php new file mode 100644 index 00000000..1a2984ae --- /dev/null +++ b/uninstall.php @@ -0,0 +1,20 @@ +drop_table(); diff --git a/wordpress-back-end-challenge.php b/wordpress-back-end-challenge.php new file mode 100644 index 00000000..63265953 --- /dev/null +++ b/wordpress-back-end-challenge.php @@ -0,0 +1,38 @@ +create_table(); +} +register_activation_hook( __FILE__, 'wpbec_activate' ); + +/** + * Inicializa o plugin. + */ +function wpbec_init() { + $plugin = new WPBEC_Plugin(); + $plugin->run(); +} +add_action( 'plugins_loaded', 'wpbec_init' ); \ No newline at end of file