From f059ed17be8694b16a43e65f7587fcd0bc51ebfb Mon Sep 17 00:00:00 2001 From: rafa Date: Thu, 9 Jul 2026 21:52:41 -0400 Subject: [PATCH] feat(api): endpoint fea/v1/crear-autor para alta de autores (#166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /wp-json/fea/v1/crear-autor — permite a Mixbot/Inma dar de alta autores nuevos sin depender de intervención manual, sin necesitar el permiso sensible create_users de WP. Rol 'author' fijo en el código (no lo decide quien llama). Auth: is_user_logged_in() + edit_others_posts (nivel Editor+, ya lo cumple su cuenta actual). Idempotente por slug: si ya existe, devuelve el usuario existente en vez de duplicar/errorar. Ver gitea.feadulta.com#166 (protocolo entre Claudes, opción B elegida sobre A porque WP no permite nativamente restringir create_users a un solo rol, y sobre C porque evita el salto humano/agente en el bucle). Co-Authored-By: Claude Sonnet 5 --- .../mu-plugins/fea-crear-autor-api.php | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 wordpress/wp-content/mu-plugins/fea-crear-autor-api.php diff --git a/wordpress/wp-content/mu-plugins/fea-crear-autor-api.php b/wordpress/wp-content/mu-plugins/fea-crear-autor-api.php new file mode 100644 index 0000000..789acd5 --- /dev/null +++ b/wordpress/wp-content/mu-plugins/fea-crear-autor-api.php @@ -0,0 +1,109 @@ + WP_REST_Server::CREATABLE, + 'callback' => 'fea_crear_autor_handle', + 'permission_callback' => 'fea_crear_autor_can_call', + 'args' => [ + 'nombre' => ['required' => true], + 'slug' => ['required' => false], + ], + ]); +}); + +/** Editor o superior (mismo nivel que ya usan Inma/Mixbot para editar posts). */ +function fea_crear_autor_can_call(WP_REST_Request $request) { + if (!is_user_logged_in()) { + return new WP_Error( + 'fea_crear_autor_not_authenticated', + 'Debes autenticarte para crear un autor.', + ['status' => 401] + ); + } + if (!current_user_can('edit_others_posts')) { + return new WP_Error( + 'fea_crear_autor_forbidden', + 'No tienes permiso para crear autores.', + ['status' => 403] + ); + } + return true; +} + +function fea_crear_autor_handle(WP_REST_Request $request) { + $nombre = trim((string) $request->get_param('nombre')); + if ($nombre === '') { + return new WP_Error( + 'fea_crear_autor_invalid', + 'nombre es obligatorio.', + ['status' => 400] + ); + } + + $slug_input = trim((string) $request->get_param('slug')); + $login = sanitize_user(sanitize_title($slug_input !== '' ? $slug_input : $nombre), true); + if ($login === '') { + return new WP_Error( + 'fea_crear_autor_invalid', + 'No se pudo derivar un slug válido de nombre/slug.', + ['status' => 400] + ); + } + + // Idempotencia: si el login/slug ya existe, se devuelve tal cual (created=false). + $existing = get_user_by('login', $login); + if ($existing) { + return fea_crear_autor_response($existing, false); + } + + $email = $login . '@feadulta.com'; + $i = 2; + while (email_exists($email)) { + $email = $login . $i . '@feadulta.com'; + $i++; + } + + $user_id = wp_insert_user([ + 'user_login' => $login, + 'user_pass' => wp_generate_password(20), + 'user_email' => $email, + 'display_name' => $nombre, + 'nickname' => $nombre, + 'role' => 'author', // fijo: este endpoint nunca crea otro rol. + ]); + + if (is_wp_error($user_id)) { + return new WP_Error( + 'fea_crear_autor_failed', + 'No se pudo crear el usuario: ' . $user_id->get_error_message(), + ['status' => 500] + ); + } + + return fea_crear_autor_response(get_userdata($user_id), true); +} + +function fea_crear_autor_response(WP_User $user, bool $created) { + return rest_ensure_response([ + 'id' => $user->ID, + 'login' => $user->user_login, + 'slug' => $user->user_nicename, + 'display_name' => $user->display_name, + 'role' => 'author', + 'created' => $created, + ]); +}