Para alguien que tiene el problema populatey también quiere hacer esto:
- chatear con texto simple y respuestas rápidas (burbujas)
- 4 colecciones de bases de datos para el chat:
clients, users, rooms, messasges.
- misma estructura de base de datos de mensajes para 3 tipos de remitentes: bot, usuarios y clientes
refPatho referencia dinámica
populatecon pathy modelopciones
- usar
findOneAndReplace/ replaceOnecon$exists
- crear un nuevo documento si el documento obtenido no existe
CONTEXTO
Objetivo
- Guarde un nuevo mensaje de texto simple en la base de datos y complételo con los datos del usuario o del cliente (2 modelos diferentes).
- Guarde un nuevo mensaje de QuickReplies en la base de datos y complételo con los datos del usuario o cliente.
- Guardar cada mensaje de su tipo de remitente:
clients, usersy bot.
- Complete solo los mensajes que tienen el remitente
clientso userscon sus modelos Mongoose. _sender type client models is clients, for user is users.
Esquema de mensaje :
const messageSchema = new Schema({
room: {
type: Schema.Types.ObjectId,
ref: 'rooms',
required: [true, `Room's id`]
},
sender: {
_id: { type: Schema.Types.Mixed },
type: {
type: String,
enum: ['clients', 'users', 'bot'],
required: [true, 'Only 3 options: clients, users or bot.']
}
},
timetoken: {
type: String,
required: [true, 'It has to be a Nanosecond-precision UTC string']
},
data: {
lang: String,
// Format samples on https://docs.chatfuel.com/api/json-api/json-api
type: {
text: String,
quickReplies: [
{
text: String,
// Blocks' ids.
goToBlocks: [String]
}
]
}
}
mongoose.model('messages', messageSchema);
SOLUCIÓN
Mi solicitud de API del lado del servidor
Mi código
Función de utilidad (en chatUtils.jsarchivo) para obtener el tipo de mensaje que desea guardar:
/**
* We filter what type of message is.
*
* @param {Object} message
* @returns {string} The type of message.
*/
const getMessageType = message => {
const { type } = message.data;
const text = 'text',
quickReplies = 'quickReplies';
if (type.hasOwnProperty(text)) return text;
else if (type.hasOwnProperty(quickReplies)) return quickReplies;
};
/**
* Get the Mongoose's Model of the message's sender. We use
* the sender type to find the Model.
*
* @param {Object} message - The message contains the sender type.
*/
const getSenderModel = message => {
switch (message.sender.type) {
case 'clients':
return 'clients';
case 'users':
return 'users';
default:
return null;
}
};
module.exports = {
getMessageType,
getSenderModel
};
El lado de mi servidor (usando Nodejs) para obtener la solicitud de guardar el mensaje:
app.post('/api/rooms/:roomId/messages/new', async (req, res) => {
const { roomId } = req.params;
const { sender, timetoken, data } = req.body;
const { uuid, state } = sender;
const { type } = state;
const { lang } = data;
// For more info about message structure, look up Message Schema.
let message = {
room: new ObjectId(roomId),
sender: {
_id: type === 'bot' ? null : new ObjectId(uuid),
type
},
timetoken,
data: {
lang,
type: {}
}
};
// ==========================================
// CONVERT THE MESSAGE
// ==========================================
// Convert the request to be able to save on the database.
switch (getMessageType(req.body)) {
case 'text':
message.data.type.text = data.type.text;
break;
case 'quickReplies':
// Save every quick reply from quickReplies[].
message.data.type.quickReplies = _.map(
data.type.quickReplies,
quickReply => {
const { text, goToBlocks } = quickReply;
return {
text,
goToBlocks
};
}
);
break;
default:
break;
}
// ==========================================
// SAVE THE MESSAGE
// ==========================================
/**
* We save the message on 2 ways:
* - we replace the message type `quickReplies` (if it already exists on database) with the new one.
* - else, we save the new message.
*/
try {
const options = {
// If the quickRepy message is found, we replace the whole document.
overwrite: true,
// If the quickRepy message isn't found, we create it.
upsert: true,
// Update validators validate the update operation against the model's schema.
runValidators: true,
// Return the document already updated.
new: true
};
Message.findOneAndUpdate(
{ room: roomId, 'data.type.quickReplies': { $exists: true } },
message,
options,
async (err, newMessage) => {
if (err) {
throw Error(err);
}
// Populate the new message already saved on the database.
Message.populate(
newMessage,
{
path: 'sender._id',
model: getSenderModel(newMessage)
},
(err, populatedMessage) => {
if (err) {
throw Error(err);
}
res.send(populatedMessage);
}
);
}
);
} catch (err) {
logger.error(
`#API Error on saving a new message on the database of roomId=${roomId}. ${err}`,
{ message: req.body }
);
// Bad Request
res.status(400).send(false);
}
});
CONSEJOS :
Para la base de datos:
- Cada mensaje es un documento en sí mismo.
- En lugar de usar
refPath, usamos la utilidad getSenderModelque se usa en populate(). Esto se debe al bot. El sender.typepuede ser: userscon su base de datos, clientscon su base de datos y botsin una base de datos. El refPathverdaderas necesidades de referencia de modelo, si no, Mongooose emite un error.
sender._idpuede ser de tipo ObjectIdpara usuarios y clientes, o nullpara el bot.
Para la lógica de solicitud de API:
- Reemplazamos el
quickReplymensaje (Message DB debe tener solo una QuickReply, pero tantos mensajes de texto simples como desee). Usamos el en findOneAndUpdatelugar de replaceOneo findOneAndReplace.
- Ejecutamos la operación de consulta (la
findOneAndUpdate) y la populateoperación con la callbackde cada uno. Esto es importante si usted no sabe si el uso async/await, then(), exec()o callback(err, document). Para obtener más información, consulte Populate Doc .
- Reemplazamos el mensaje de respuesta rápida con la
overwriteopción y sin $setoperador de consulta.
- Si no encontramos la respuesta rápida, creamos una nueva. Tienes que decirle a Mongoose esto con
upsertopción.
- Completamos solo una vez, para el mensaje reemplazado o el nuevo mensaje guardado.
- Volvemos a las devoluciones de llamada, sea cual sea el mensaje que hayamos guardado con
findOneAndUpdatey para populate().
- En
populate, creamos una referencia de modelo dinámica personalizada con getSenderModel. Podemos utilizar la referencia dinámica Mangosta porque el sender.typede botno tiene ningún modelo de mangosta. Usamos una base de datos que se completa con modely pathoptins.
He pasado muchas horas resolviendo pequeños problemas aquí y allá y espero que esto ayude a alguien. 😃