diff --git a/apiki-favorites.php b/apiki-favorites.php new file mode 100644 index 00000000..e8b24e21 --- /dev/null +++ b/apiki-favorites.php @@ -0,0 +1,30 @@ +run(); +} + +apiki_favorites_run_plugin(); diff --git a/includes/class-activator.php b/includes/class-activator.php new file mode 100644 index 00000000..ca79ba55 --- /dev/null +++ b/includes/class-activator.php @@ -0,0 +1,26 @@ +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, + PRIMARY KEY (id), + UNIQUE KEY 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..93561933 --- /dev/null +++ b/includes/class-favorites-repository.php @@ -0,0 +1,59 @@ +wpdb = $database ? $database : $wpdb; + $this->table_name = $this->wpdb->prefix . 'apiki_favorites'; + } + + public function exists( $user_id, $post_id ) { + $favorite_id = $this->wpdb->get_var( + $this->wpdb->prepare( + "SELECT id FROM {$this->table_name} WHERE user_id = %d AND post_id = %d LIMIT 1", + $user_id, + $post_id + ) + ); + + return ! empty( $favorite_id ); + } + + public function insert( $user_id, $post_id ) { + return $this->wpdb->insert( + $this->table_name, + array( + 'user_id' => $user_id, + 'post_id' => $post_id, + ), + array( + '%d', + '%d', + ) + ); + } + + public function delete( $user_id, $post_id ) { + return $this->wpdb->delete( + $this->table_name, + array( + 'user_id' => $user_id, + 'post_id' => $post_id, + ), + array( + '%d', + '%d', + ) + ); + } +} diff --git a/includes/class-favorites-rest-controller.php b/includes/class-favorites-rest-controller.php new file mode 100644 index 00000000..bc7260ce --- /dev/null +++ b/includes/class-favorites-rest-controller.php @@ -0,0 +1,149 @@ +repository = $repository; + } + + public function register_routes() { + register_rest_route( + $this->namespace, + '/posts/(?P\d+)/favorite', + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'favorite' ), + 'permission_callback' => array( $this, 'permission_check' ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'unfavorite' ), + 'permission_callback' => array( $this, 'permission_check' ), + ), + ) + ); + } + + public function permission_check( $request ) { + if ( ! is_user_logged_in() ) { + return new WP_Error( + 'apiki_favorites_forbidden', + 'Authentication required.', + array( 'status' => 401 ) + ); + } + + return true; + } + + public function favorite( WP_REST_Request $request ) { + $post_id = (int) $request['post_id']; + $post = $this->get_valid_post( $post_id ); + $user_id = get_current_user_id(); + + if ( is_wp_error( $post ) ) { + return $post; + } + + if ( $this->repository->exists( $user_id, $post_id ) ) { + return new WP_Error( + 'apiki_favorites_conflict', + 'Post already favorited.', + array( 'status' => 409 ) + ); + } + + $insert_result = $this->repository->insert( $user_id, $post_id ); + + if ( false === $insert_result ) { + if ( $this->repository->exists( $user_id, $post_id ) ) { + return new WP_Error( + 'apiki_favorites_conflict', + 'Post already favorited.', + array( 'status' => 409 ) + ); + } + + return new WP_Error( + 'apiki_favorites_insert_error', + 'Could not favorite post.', + array( 'status' => 500 ) + ); + } + + return new WP_REST_Response( + array( + 'post_id' => $post_id, + 'favorited' => true, + ), + 201 + ); + } + + public function unfavorite( WP_REST_Request $request ) { + $post_id = (int) $request['post_id']; + $post = $this->get_valid_post( $post_id ); + $user_id = get_current_user_id(); + + if ( is_wp_error( $post ) ) { + return $post; + } + + if ( ! $this->repository->exists( $user_id, $post_id ) ) { + return new WP_Error( + 'apiki_favorites_not_found', + 'Favorite not found.', + array( 'status' => 404 ) + ); + } + + $delete_result = $this->repository->delete( $user_id, $post_id ); + + if ( 0 === $delete_result ) { + return new WP_Error( + 'apiki_favorites_not_found', + 'Favorite not found.', + array( 'status' => 404 ) + ); + } + + if ( false === $delete_result ) { + return new WP_Error( + 'apiki_favorites_delete_error', + 'Could not unfavorite post.', + array( 'status' => 500 ) + ); + } + + return new WP_REST_Response( + array( + 'post_id' => $post_id, + 'favorited' => false, + ), + 200 + ); + } + + protected function get_valid_post( $post_id ) { + $post = get_post( $post_id ); + + if ( ! $post || 'post' !== $post->post_type ) { + return new WP_Error( + 'apiki_favorites_post_not_found', + 'Post not found.', + array( 'status' => 404 ) + ); + } + + return $post; + } +} diff --git a/includes/class-plugin.php b/includes/class-plugin.php new file mode 100644 index 00000000..3a5148ec --- /dev/null +++ b/includes/class-plugin.php @@ -0,0 +1,21 @@ +repository = new Apiki_Favorites_Repository(); + $this->rest_controller = new Apiki_Favorites_REST_Controller( $this->repository ); + } + + public function run() { + add_action( 'rest_api_init', array( $this->rest_controller, 'register_routes' ) ); + } +}