From b2a0f6d898f90761c6001624d8e93c5e4ff6ada9 Mon Sep 17 00:00:00 2001 From: kaike9612 Date: Sun, 8 Mar 2026 23:03:11 -0300 Subject: [PATCH] feat: implement favorite posts plugin using WordPress REST API --- README.md | 171 ++++++++++++++-- apiki-favorites.php | 42 ++++ includes/class-activator.php | 49 +++++ includes/class-favorites-repository.php | 123 +++++++++++ includes/class-favorites-rest-controller.php | 204 +++++++++++++++++++ includes/class-plugin.php | 47 +++++ 6 files changed, 620 insertions(+), 16 deletions(-) create mode 100644 apiki-favorites.php create mode 100644 includes/class-activator.php create mode 100644 includes/class-favorites-repository.php create mode 100644 includes/class-favorites-rest-controller.php create mode 100644 includes/class-plugin.php diff --git a/README.md b/README.md index a4f6c256..87b8424f 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,166 @@ -# WordPress Back-end Challenge +# Apiki Favorites Plugin -Desafio para os futuros programadores back-end em WordPress da Apiki. +Plugin WordPress para permitir que usuários logados favoritem posts usando a WP REST API. -## Introdução +## Instalação -Desenvolva um Plugin em WordPress que implemente a funcionalidade de favoritar posts para usuários logados usando a [WP REST API](https://developer.wordpress.org/rest-api/). +1. Faça o download ou clone este repositório. +2. Copie a pasta do plugin para o diretório `wp-content/plugins/` do seu WordPress. +3. Ative o plugin através do menu "Plugins" no WordPress. -**Especifícações**: +## Estrutura de Arquivos -* Possibilidade de favoritar e desfavoritar um post; -* Persistir os dados em uma [tabela a parte](https://codex.wordpress.org/Creating_Tables_with_Plugins); +``` +apiki-favorites/ +├── apiki-favorites.php # Arquivo principal do plugin +├── README.md # Este arquivo +└── includes/ + ├── class-activator.php # Classe de ativação (cria a tabela) + ├── class-favorites-repository.php # Repositório (persistência no banco) + ├── class-favorites-rest-controller.php # Controlador REST API + └── class-plugin.php # Classe principal do plugin +``` -## Instruções +## Endpoints da API -1. Efetue o fork deste repositório e crie um branch com o seu nome e sobrenome. (exemplo: fulano-dasilva) -2. Após finalizar o desafio, crie um Pull Request. -3. Aguarde algum contribuidor realizar o code review. +### Favoritar um Post -## Pré-requisitos +- **Endpoint:** `POST /wp-json/apiki/v1/favorites/{post_id}` +- **Autenticação:** Necessária (usuário logado) +- **Exemplo de requisição:** + +``` +bash + curl -X POST https://seusite.com.br/wp-json/apiki/v1/favorites/123 \ + -H "Authorization: Basic base64_encode(username:application_password)" + +``` -* PHP >= 5.6 -* Orientado a objetos +- **Resposta de sucesso (201):** + +``` +json + { + "message": "Post favoritado com sucesso.", + "data": { + "post_id": 123, + "user_id": 1 + } + } + +``` -## Dúvidas +- **Resposta de erro - Post não encontrado (404):** + +``` +json + { + "code": "apiki_favorites_post_not_found", + "message": "Post não encontrado.", + "data": { + "status": 404 + } + } + +``` -Em caso de dúvidas, crie uma issue. +- **Resposta de erro - Já favoritado (422):** + +``` +json + { + "code": "apiki_favorites_already_exists", + "message": "Este post já está favoritado.", + "data": { + "status": 422 + } + } + +``` + +### Desfavoritar um Post + +- **Endpoint:** `DELETE /wp-json/apiki/v1/favorites/{post_id}` +- **Autenticação:** Necessária (usuário logado) +- **Exemplo de requisição:** + +``` +bash + curl -X DELETE https://seusite.com.br/wp-json/apiki/v1/favorites/123 \ + -H "Authorization: Basic base64_encode(username:application_password)" + +``` + +- **Resposta de sucesso (200):** + +``` +json + { + "message": "Post desfavoritado com sucesso.", + "data": { + "post_id": 123, + "user_id": 1 + } + } + +``` + +- **Resposta de erro - Post não encontrado (404):** + +``` +json + { + "code": "apiki_favorites_post_not_found", + "message": "Post não encontrado.", + "data": { + "status": 404 + } + } + +``` + +- **Resposta de erro - Não favoritado (404):** + +```json + { + "code": "apiki_favorites_not_found", + "message": "Este post não está favoritado.", + "data": { + "status": 404 + } + } + +``` + +## Tabela do Banco de Dados + +O plugin cria uma tabela chamada `{prefix}apiki_favorites` com a seguinte estrutura: + +| Coluna | Tipo | Descrição | +|------------|-------------------|---------------------| +| id | bigint(20) | ID único | +| user_id | bigint(20) | ID do usuário | +| post_id | bigint(20) | ID do post | +| created_at | datetime | Data de criação | + +A tabela possui uma chave única na combinação (user_id, post_id) para evitar duplicatas. + +## Requisitos + +- PHP >= 5.6 +- WordPress 4.7 ou superior (para REST API) +- Usuário logado para favoritar/desfavoritar + +## Autenticação + +A API requer autenticação. Você pode usar: + +- **Application Passwords** (recomendado para WordPress 5.6+) +- **Cookie Authentication** (para testes locais) +- **JWT Authentication** (se instalado) + +Exemplo com Application Password: +``` +bash +curl -X POST https://seusite.com.br/wp-json/apiki/v1/favorites/123 \ + -u "username:application_password" diff --git a/apiki-favorites.php b/apiki-favorites.php new file mode 100644 index 00000000..3a7340f9 --- /dev/null +++ b/apiki-favorites.php @@ -0,0 +1,42 @@ +run(); +} + +add_action( 'plugins_loaded', 'apiki_favorites_load' ); diff --git a/includes/class-activator.php b/includes/class-activator.php new file mode 100644 index 00000000..d3b79e13 --- /dev/null +++ b/includes/class-activator.php @@ -0,0 +1,49 @@ +prefix . 'apiki_favorites'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE {$table_name} ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + user_id bigint(20) unsigned NOT NULL, + post_id bigint(20) unsigned NOT NULL, + created_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (id), + UNIQUE KEY unique_user_post (user_id, post_id) + ) {$charset_collate};"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); + } +} diff --git a/includes/class-favorites-repository.php b/includes/class-favorites-repository.php new file mode 100644 index 00000000..31fa8fd2 --- /dev/null +++ b/includes/class-favorites-repository.php @@ -0,0 +1,123 @@ +table_name = $wpdb->prefix . 'apiki_favorites'; + } + + /** + * Add a favorite for a user and post. + * + * @param int $user_id User ID. + * @param int $post_id Post ID. + * @return bool True on success, false on failure. + */ + 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, + 'created_at' => current_time( 'mysql' ), + ), + array( + '%d', + '%d', + '%s', + ) + ); + + return $result !== false; + } + + /** + * Remove a favorite for a user and post. + * + * @param int $user_id User ID. + * @param int $post_id Post ID. + * @return bool True on success, false on failure. + */ + 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 $result !== false; + } + + /** + * Check if a post is already favorited by a user. + * + * @param int $user_id User ID. + * @param int $post_id Post ID. + * @return bool True if already favorited, false otherwise. + */ + public function is_favorited( $user_id, $post_id ) { + global $wpdb; + + $query = $wpdb->prepare( + "SELECT COUNT(*) FROM {$this->table_name} WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + ); + + $count = $wpdb->get_var( $query ); + + return (int) $count > 0; + } + + /** + * Get favorite ID by user and post. + * + * @param int $user_id User ID. + * @param int $post_id Post ID. + * @return int|null Favorite ID or null if not found. + */ + public function get_favorite_id( $user_id, $post_id ) { + global $wpdb; + + $query = $wpdb->prepare( + "SELECT id FROM {$this->table_name} WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + ); + + return $wpdb->get_var( $query ); + } +} diff --git a/includes/class-favorites-rest-controller.php b/includes/class-favorites-rest-controller.php new file mode 100644 index 00000000..2c3ac353 --- /dev/null +++ b/includes/class-favorites-rest-controller.php @@ -0,0 +1,204 @@ +repository = $repository; + } + + /** + * Register REST API routes. + * + * @return void + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'add_favorite' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'args' => array( + 'post_id' => array( + 'required' => true, + 'validate_callback' => array( $this, 'validate_post_id' ), + ), + ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'remove_favorite' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'args' => array( + 'post_id' => array( + 'required' => true, + 'validate_callback' => array( $this, 'validate_post_id' ), + ), + ), + ), + ) + ); + } + + /** + * Check if user is authenticated. + * + * @return bool True if user is logged in, false otherwise. + */ + public function check_permission() { + return is_user_logged_in(); + } + + /** + * Validate post ID exists. + * + * @param mixed $param Parameter value. + * @return bool True if valid, false otherwise. + */ + public function validate_post_id( $param ) { + $post_id = absint( $param ); + return $post_id > 0 && get_post( $post_id ) !== null; + } + + /** + * Add a favorite to a post. + * + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object. + */ + public function add_favorite( $request ) { + $post_id = absint( $request->get_param( 'post_id' ) ); + $user_id = get_current_user_id(); + + // Validate post exists. + $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'apiki_favorites_post_not_found', + __( 'Post not found.', 'apiki-favorites' ), + array( 'status' => 404 ) + ); + } + + // Check if already favorited. + if ( $this->repository->is_favorited( $user_id, $post_id ) ) { + return new WP_Error( + 'apiki_favorites_already_exists', + __( 'This post is already favorited.', 'apiki-favorites' ), + array( 'status' => 422 ) + ); + } + + // Add favorite. + $result = $this->repository->add_favorite( $user_id, $post_id ); + + if ( ! $result ) { + return new WP_Error( + 'apiki_favorites_add_failed', + __( 'Failed to add favorite.', 'apiki-favorites' ), + array( 'status' => 500 ) + ); + } + + $response = array( + 'message' => __( 'Post favorited successfully.', 'apiki-favorites' ), + 'data' => array( + 'post_id' => $post_id, + 'user_id' => $user_id, + ), + ); + + return new WP_REST_Response( $response, 201 ); + } + + /** + * Remove a favorite from a post. + * + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object. + */ + public function remove_favorite( $request ) { + $post_id = absint( $request->get_param( 'post_id' ) ); + $user_id = get_current_user_id(); + + // Validate post exists. + $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'apiki_favorites_post_not_found', + __( 'Post not found.', 'apiki-favorites' ), + array( 'status' => 404 ) + ); + } + + // Check if favorite exists. + if ( ! $this->repository->is_favorited( $user_id, $post_id ) ) { + return new WP_Error( + 'apiki_favorites_not_found', + __( 'This post is not favorited.', 'apiki-favorites' ), + array( 'status' => 404 ) + ); + } + + // Remove favorite. + $result = $this->repository->remove_favorite( $user_id, $post_id ); + + if ( ! $result ) { + return new WP_Error( + 'apiki_favorites_remove_failed', + __( 'Failed to remove favorite.', 'apiki-favorites' ), + array( 'status' => 500 ) + ); + } + + $response = array( + 'message' => __( 'Post unfavorited successfully.', 'apiki-favorites' ), + 'data' => array( + 'post_id' => $post_id, + 'user_id' => $user_id, + ), + ); + + return new WP_REST_Response( $response, 200 ); + } +} diff --git a/includes/class-plugin.php b/includes/class-plugin.php new file mode 100644 index 00000000..899d0513 --- /dev/null +++ b/includes/class-plugin.php @@ -0,0 +1,47 @@ +repository = new Apiki_Favorites_Repository(); + $this->rest_controller = new Apiki_Favorites_REST_Controller( $this->repository ); + } + + /** + * Run the plugin. + * + * @return void + */ + public function run() { + add_action( 'rest_api_init', array( $this->rest_controller, 'register_routes' ) ); + } +}