Este es un artículo para futuros desarrolladores que puedan necesitar esto para crear complementos personalizados específicamente para la validación de campos personalizados de usuario.
Escrito mientras se tiene la versión 3.6.0.beta3-latest de Discourse (commit actual a7326abf15), por lo que si algo no funciona, podría ser que algo haya cambiado en el código principal.
La razón principal para escribir esto es la ausencia absoluta de información sobre cómo agregar validación personalizada a los campos personalizados de usuario.
La historia corta es que tuve que agregar una validación personalizada para uno de los campos personalizados de usuario, que es básicamente una entrada de texto libre. El requisito principal era que este campo tuviera un valor único. Siendo desarrollador de Python/PHP, es bastante fácil de implementar en los frameworks/CMS con los que trabajo, mientras que Discourse no fue el caso y no se trata de las especificidades del lenguaje, sino del hecho de que no hay documentación para hacer esto y las publicaciones que encontré con preguntas similares que podrían ayudar, no fueron respondidas. Después de media semana de investigación del código principal, finalmente logré el resultado esperado y quiero compartirlo con otros, ya que podría ayudar a alguien.
Entonces, campo personalizado de usuario que debe tener un valor único.
Para resolver esto, necesitamos crear un complemento personalizado para Discourse, la razón principal es que necesitamos verificar el valor en la base de datos, lo que se hace en el backend.
Aquí está la estructura:
- my_custom_plugin
-- assets
--- javascripts
---- discourse
----- initializers
------ your-initializer-js-file.js
-- config
--- locales
---- server.en.yml
---- client.en.yml
--- settings.yml
-- plugin.rb
Ahora, el problema principal que enfrenté fue cómo inyectar en los campos personalizados. Después de buscar en el código principal, encontré el método API addCustomUserFieldValidationCallback, se puede encontrar en el archivo plugin-api.gjs en el código principal. Este es un método que se activa después del primer intento de enviar el formulario de registro y, posteriormente, se activa con cada escritura en la entrada o en cualquier campo personalizado de usuario, que es lo que necesitaba.
En el ejemplo para este método, muestra que acepta una función de devolución de llamada y el argumento que le devuelve es el userField en sí mismo con el que interactúa en ese momento.
Aquí está el código para your-initializer-js-file.js (no completo pero suficiente):
import { apiInitializer } from "discourse/lib/api";
import { i18n } from "discourse-i18n";
export default apiInitializer("1.8.0", (api) => {
// Lista de IDs de campo que requieren validación única
const UNIQUE_FIELD_IDS = ["5"];
// Devolución de llamada de validación del lado del cliente
api.addCustomUserFieldValidationCallback((userField) => {
const fieldId = userField.field.id.toString();
// Solo valida campos en nuestra lista de campos únicos
if (UNIQUE_FIELD_IDS.includes(fieldId)) {
// Comprueba si el valor está vacío
if (!userField.value || userField.value.trim() === "") {
return null; // Deja que la validación predeterminada maneje los valores vacíos
}
const value = userField.value.trim();
// Comprueba contra la lista de valores
const unique = isValueUnique(fieldId, value);
if (!unique) {
return {
failed: true,
ok: false,
reason: i18n("js.my_custom_plugin_text_validator.value_taken"),
element: userField.field.element,
};
}
}
return null;
});
});
Puntos principales a comentar aquí:
- UNIQUE_FIELD_IDS se utiliza para filtrar qué campo quiero que se vea afectado por mi lógica personalizada. El número 5 es el ID del campo, se puede verificar al abrir la página Editar del campo personalizado dentro de la URL.
- isValueUnique es una función personalizada donde toda su lógica personalizada realiza la validación. Debe especificarse fuera de api.addCustomUserFieldValidationCallback.
Debido a que necesitamos verificar el valor en la base de datos, eso significa que necesitamos hacer una llamada API a algún punto final que realice la validación y nos proporcione una respuesta si es válida o no, O podría haber otra forma de verificar esto sin un punto final personalizado que aún no he encontrado. Hay un módulo para alguna validación, pero su validación está restringida a la verificación REGEX directamente en JS, que no es lo que necesitaba.
El archivo plugin.rb es donde especificamos información sobre nuestro complemento personalizado, sus configuraciones y todo lo que se puede agregar. En mi caso, registro mi punto final personalizado para la validación. Aquí hay un ejemplo de validación posible, puede que lo tenga de manera diferente.
# frozen_string_literal: true
# name: my_custom_plugin
# about: Valida la unicidad de los campos personalizados de usuario
# version: 0.1
# authors: Yan R
enabled_site_setting :my_custom_plugin_enabled
after_initialize do
# Lista codificada de IDs de campo únicos
UNIQUE_FIELD_IDS = ["5"].freeze
# Agregar puntos finales de API para validación
Discourse::Application.routes.append do
get "/my_custom_plugin_endpoint_url/:field_id" => "my_custom_plugin_validation#validate"
end
class ::MyCustomPluginValidationController < ::ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :check_xhr
skip_before_action :redirect_to_login_if_required
def validate
field_name = params[:field_name]
field_value = params[:field_value]
return render json: { valid: true } unless field_name.present? && field_value.present?
return render json: { valid: true } unless field_name.start_with?("user_field_")
# Normalizar el valor: eliminar espacios en blanco y comparar sin distinción de mayúsculas y minúsculas
normalized_value = field_value.to_s.strip
return render json: { valid: true } if normalized_value.empty?
existing = UserCustomField
.where(name: field_name)
.where("LOWER(TRIM(value)) = LOWER(?)", normalized_value)
.exists?
render json: { valid: !existing }
end
end
end
Ahora, aquí está la parte complicada, puede usar este punto final y hacer una llamada fetch/ajax desde su inicializador, pero no funcionará, la razón principal es que addCustomUserFieldValidationCallback no funciona con devoluciones de llamada asíncronas, por lo que necesita hacer una llamada que no sea asíncrona. Usé XMLHttpRequest para esto, como ejemplo xhr.open('GET', '/my_custom_plugin_endpoint_url/${fieldId}', true); donde true deshabilita lo asíncrono.
Esto comenzará a funcionar, pero se enfrentará a un problema, donde no podrá escribir rápidamente en la entrada, porque addCustomUserFieldValidationCallback se activa con cada letra proporcionada y esperará hasta que tenga una respuesta adecuada, por lo que congelará su entrada.
No lo mostré en los ejemplos, pero mi solución fue recuperar la lista de valores únicos del campo después de cargar la página y usarla para filtrar el valor directamente en JS sin hacer llamadas API adicionales, solo necesita asegurarse de tener una cantidad razonable de valores y asegurar el punto final. En mi caso, en lugar de la lista de valores, devolví una lista de hashes, por lo que en JS convierto el valor escrito en un hash y comparo los hashes. Además del hecho de que es un poco más seguro, también reduce significativamente el peso de la respuesta, por lo que puede obtener muchos más valores para comparar.
Aquí la información sobre la carpeta de configuración.
settings.yml:
plugins:
my_custom_plugin_enabled:
default: true
client: true
server.en.yml:
en:
site_settings:
my_custom_plugin_enabled: "Habilitar validación de campos de usuario únicos"
my_custom_plugin_text_validator:
value_taken: "Este valor ya está en uso"
client.en.yml:
en:
js:
my_custom_plugin_text_validator:
value_taken: "Este valor ya está en uso"
Adición: No encontré información sobre cómo agregar mi complemento personalizado además de agregarlo en app.yml, ya que tengo una configuración de Docker. Lo que hice fue agregar mi complemento personalizado en la carpeta/repositorio de Docker, que contiene la configuración de Discourse y lo monté durante la operación de Reconstrucción en la carpeta de complementos dentro del Contenedor.
Espero que esto ayude a alguien a implementar validaciones personalizadas para los campos de usuario.