✨ Servicio de Actualización de Datos de Usuario por Medio de API
La API update-user fue diseñada para permitir a los usuarios autenticados modificar su información de perfil de forma segura. Está pensada para actualizar campos específicos como:
- Número de teléfono (
phone) - Número de identificación (
identificationNumber) - Nombre completo (
nameLastName) - Dirección de envío (
mailingAddress) - Dirección de facturación (
billingAddress)
Esta API está protegida por autenticación JWT-> Se obtiene con la nueva API de Login, lo que garantiza que solo los usuarios autenticados puedan modificar su información.
🔐 Autenticación
El acceso a esta API requiere un token JWT válido. Este token debe enviarse en el encabezado de la solicitud como:
Authorization: Bearer TU_TOKEN_JWTSi el token es inválido o no se proporciona, la API rechazará la solicitud.
🧠 Controlador
Este archivo contiene la lógica principal para el manejo de la actualización del perfil del usuario.
Ruta del archivo
src/api/user-endpoints/controllers/update-user.ts
Código del controlador
function validateRequestBody(body: UserProfileUpdateData) { validateKeys(body, User.VALID_USER_UPDATE_KEYS, User.FIELD_ROOT);
if (body.phone !== undefined) { if (typeof body.phone !== User.FIELD_OBJECT) throw new Error(ERROR_INVALID_FORMAT + User.COMPONENT_NAME_PHONE);
validateKeys(body.phone, User.VALID_PHONE_KEYS, User.COMPONENT_NAME_PHONE); }
const addressFields = [ { value: body.billingAddress, validKeys: User.VALID_BILLING_ADDRESS_KEYS, context: User.COMPONENT_NAME_BILLING, }, { value: body.mailingAddress, validKeys: User.VALID_MAILING_ADDRESS_KEYS, context: User.COMPONENT_NAME_MAILING, }, ];
for (const { value, validKeys, context } of addressFields) { if (value !== undefined) { if (typeof value !== User.FIELD_OBJECT) throw new Error(ERROR_INVALID_FORMAT + context);
validateKeys(value, validKeys, context);
if (value.phone !== undefined) { if (typeof value.phone !== User.FIELD_OBJECT) throw new Error(ERROR_INVALID_FORMAT + `${context}.phone`);
validateKeys(value.phone, User.VALID_PHONE_KEYS, `${context}.phone`); } } }}
const validatePhoneFields = (phone?: { codePhone?: string; number?: string;}) => { if (!phone) return; validateFieldPatterns( { codePhone: phone.codePhone }, { codePhone: FIELD_VALIDATIONS.codePhone } ); validateFieldPatterns( { number: phone.number }, { number: FIELD_VALIDATIONS.number } );};
export default { async updateProfile(ctx) { const recivedUser = ctx.state.user; try { validateRequestBody(ctx.request.body);
if (!recivedUser) return ctx.unauthorized(ERROR_UNAUTHORIZED);
const { email } = recivedUser; const body = ctx.request.body;
validatePhoneFields(body.phone); validatePhoneFields(body.billingAddress?.phone); validatePhoneFields(body.mailingAddress?.phone);
let userStrapi = await findOne( User.USER, User.FIELDS_USER, { email }, User.COMPONENTS_USER );
const userData = { ...userStrapi, ...body, mailingAddress: { ...userStrapi.mailingAddress, ...body.mailingAddress, }, billingAddress: { ...userStrapi.billingAddress, ...body.billingAddress, }, };
await updateDocument(User.USER, userStrapi.documentId, { ...userData });
const updatedUser = await findOne( User.USER, User.FIELDS_USER, { email }, User.COMPONENTS_USER );
return ctx.send(updatedUser); } catch (error) { return ctx.badRequest(error, ERROR_UPDATE); } },};🗞 Explicación del código
- Autenticación del usuario
const recivedUser = ctx.state.user;if (!recivedUser) { return ctx.unauthorized(ERROR_UNAUTHORIZED);}Esta línea verifica que el usuario esté autenticado. ctx.state.user solo estará presente si el JWT fue enviado correctamente y es válido. Si el usuario no está autenticado, se devuelve un error 401.
- Validación de Campos
validateRequestBody(ctx.request.body);Esta línea valida que el usuario no esté enviando campos indeseados a la API, cualquier campo no permitido regresará un error. vease en ✅ Validación de Claves Permitidas
const validatePhoneFields = (phone?: { codePhone?: string; number?: string;}) => { if (!phone) return; validateFieldPatterns( { codePhone: phone.codePhone }, { codePhone: FIELD_VALIDATIONS.codePhone } ); validateFieldPatterns( { number: phone.number }, { number: FIELD_VALIDATIONS.number } );};
validatePhoneFields(cleanBody.phone);validatePhoneFields(cleanBody.billingAddress?.phone);validatePhoneFields(cleanBody.mailingAddress?.phone);Asi como tambien valida los campos mediante sus regex correspondientes para cada uno de los campos de telefono y contraseña. esta se encuentra en el mismo URL que el de arriba->validateFieldPatterns.
- Extracción de datos de la solicitud
const body = ctx.request.body;Aquí se obtienen los datos enviados por el usuario que desea actualizar.
Todos estos campos son opcionales:
phone, identificationNumber, nameLastName, mailingAddress, billingAddress,- Consulta del usuario en Strapi
let userStrapi = await findOne( User.USER, User.FIELDS_USER, { email }, User.COMPONENTS_USER);Esta función busca el usuario actual en la base de datos de Strapi usando su correo electrónico. FIELDS_USER define los campos a recuperar y COMPONENTS_USER incluye los componentes (direcciones).
- Construcción del objeto a actualizar
const userData = { ...userStrapi, ...body, mailingAddress: { ...userStrapi.mailingAddress, ...body.mailingAddress, }, billingAddress: { ...userStrapi.billingAddress, ...body.billingAddress, },};Se asegura de reemplazar los datos existentes con los nuevos. Si el usuario no envía un campo, se mantiene el valor anterior.
Esto se hace para los campos principales y para las direcciones (mailingAddress y billingAddress).
- Actualización de datos
await updateDocument(User.USER, userStrapi.documentId, { ...userData });Se actualizan los campos principales (como phone, nameLastName, etc.) usando la función updateDocument.
Las direcciones (billingAddress, mailingAddress) se tratan como componentes separados:
- Si el componente ya existe, se actualiza.
- Si no existe, se crea dentro del documento del usuario.
- Obtención del usuario actualizado
const updatedUser = await findOne(...);Una vez finalizada la actualización, se consulta nuevamente el usuario para enviarlo como respuesta al frontend.
✅ Validación de Claves Permitidas
Para mantener la integridad y seguridad de los datos, la API implementa una validación explícita de las claves enviadas en el cuerpo (body) de la solicitud. Esto evita que se intenten modificar campos no autorizados.
🔒 ¿Cómo funciona?
Antes de que se realice cualquier operación de actualización en la base de datos, el controlador ejecuta una función llamada validateRequestBody, la cual:
👌 Utiliza el util de API Validation Queries
-
Valida que los campos en el objeto raíz (
body) pertenezcan al conjunto permitido. -
Valida de forma independiente las claves dentro de
billingAddressymailingAddress. -
Si alguna clave no está permitida, lanza un error con un mensaje claro indicando el contexto (por ejemplo, “billingAddress”) y las claves inválidas.
🔑 Claves permitidas
Nivel raíz (UserProfileUpdateData)
const validUserKeys = [ "phone", "identificationNumber", "nameLastName", "mailingAddress", "billingAddress",];billingAddress
const validBillingAddressKeys = [ "name", "company", "identificationNumber", "phone", "street", "city", "country", "state",];mailingAddress
const validMailingAddressKeys = [ "name", "country", "phone", "street", "city", "zipCode", "identificationNumber", "state",];🔎 Validaciones de campos
Antes de actualizar cualquier dato, se realiza una validación estricta para asegurar que únicamente se procesen campos permitidos. Esto se logra mediante funciones auxiliares que comparan las claves recibidas con las definidas como válidas:
-
Campos válidos del usuario:
phone,identificationNumber,nameLastName,mailingAddress,billingAddress -
Campos válidos en billingAddress:
name,company,identificationNumber,phone,street,city,country,state -
Campos válidos en mailingAddress:
name,country,phone,street,city,zipCode,identificationNumber,state
Si se encuentra una clave no permitida, se lanza un error indicando exactamente qué campo es inválido y en qué parte del objeto ocurrió (por ejemplo, en billingAddress).
🧹 Estructura de Datos
📌 Importante: La validación se realiza antes de ejecutar cualquier actualización. Si se detectan campos no válidos en el nivel raíz o dentro de billingAddress y mailingAddress, se lanza un error 500 Bad Request con el detalle de los campos no permitidos.
Nota: Solo se podrán modificar los datos que estén explícitamente declarados en esta sección. Cualquier campo que no esté definido en los TYPES será ignorado por la API, incluso si se incluye en la solicitud, es decir, no envia el error por que es permitido, pero si no se define en los tipos no lo actualiza, son 2 validaciones.
Estructura del cuerpo (body) de la solicitud
export type UserProfileUpdateData = { phone?: string; identificationNumber?: string; nameLastName?: string; mailingAddress?: MailingAddress; billingAddress?: BillingAddress;};mailingAddress
export type MailingAddress = { name?: string; country?: string; phone?: string; street?: string; city?: string; zipCode?: string; identificationNumber?: string; state?: string;};billingAddress
export type BillingAddress = { name?: string; company?: string; identificationNumber?: string; phone?: string; street?: string; city?: string; country?: string; state?: string;};⚠️ Para actualizar contraseñas de verticales no se utiliza esta API, eso vease Aquí.
⚙️ Configuración de la Ruta
Archivo: user-endpoints/routes/update-user.ts
export default { routes: [ { method: "PUT", path: "/user-endpoints/profile", handler: "update-user.updateProfile", }, ],};Explicación
- Método:
PUT, porque se están actualizando datos existentes. - Ruta:
/user-endpoints/profileindica que se trata de una actualización de perfil. - Handler:
'update-user.updateProfile'llama a la función definida en el controlador.
🧪 Pruebas y Uso de la API
1. Autenticación
Si no tienes un token JWT, realiza una solicitud POST al endpoint login con tus credenciales (para esto deberás haber registrado un usuario previamente):
- URL:
/api/user-endpoints/login - Método:
POST
Body:
{ "email": "prueba2@prueba2.com", "password": "MiContraseñaSegura123", "vertical": "Pharma"}Recibirás un token JWT válido en la respuesta.
2. Solicitud de actualización
- URL:
/api/user-endpoints/profile - Método:
PUT - Headers:
Authorization:Bearer TU_TOKEN_JWTContent-Type:application/json
Ejemplo de body (JSON)
{ "phone": { "codePhone": "+52", "number": "5551234567" }, "nameLastName": "Fernando Rodríguez", "mailingAddress": { "name": "Fernando", "street": "Calle del Lago 45", "city": "Morelia", "country": "México", "zipCode": "58000", "phone": { "codePhone": "+52", "number": "5551234567" } }, "billingAddress": { "name": "Fernando", "company": "DevTech S.A.", "identificationNumber": "RFC123456", "street": "Calle del Sol 99", "city": "Morelia", "country": "México", "phone": { "codePhone": "+52", "number": "5551234567" } }}📤 Respuestas de la API
✅ 1. Éxito (200 OK)
{ "id": 1, "email": "fernando@email.com", "nameLastName": "Fernando Rodríguez", "phone": { "codePhone": "+52", "number": "5551234567" }, "billingAddress": { ... }, "mailingAddress": { ... }}La respuesta incluirá todos los datos actualizados del usuario.
❌ 2. Error de Autenticación (401 Unauthorized)
{ "data": null, "error": { "status": 401, "name": "UnauthorizedError", "message": "Not authorized to perform this action", "details": {} }}Este error se produce si no se proporciona un token JWT o si el token es inválido o ha expirado.
❌ 3. Error de Validación o Interno (400 Bad Request)
{ "data": null, "error": { "status": 400, "name": "BadRequestError", "message": "Error updating profile", "details": {} }}Este error se devuelve si ocurre algún problema durante la actualización de los datos, por ejemplo, si los datos enviados no cumplen con el formato esperado.
📛 4. Error de Datos Invalidos enviados
Si un usuario intenta enviar una clave no permitida, como username en el nivel raíz, o postalCode dentro de billingAddress, se retornará una respuesta de error como esta:
{ "data": null, "error": { "status": 500, "name": "InternalServerError", "message": "Internal Server Error" }}En la consola del Servidor se mostrará que campos son los no permitidos.