Pour quelqu'un qui a un problème avec populate
et qui souhaite également le faire:
- discuter avec un texte simple et des réponses rapides (bulles)
- 4 collections de base de données pour le chat:
clients
, users
, rooms
,messasges
.
- même structure de base de données de messages pour 3 types d'expéditeurs: bot, utilisateurs et clients
refPath
ou référence dynamique
populate
avec path
etmodel
options
- utiliser
findOneAndReplace
/ replaceOne
avec$exists
- créer un nouveau document si le document récupéré n'existe pas
LE CONTEXTE
Objectif
- Enregistrez un nouveau message texte simple dans la base de données et remplissez-le avec les données de l'utilisateur ou du client (2 modèles différents).
- Enregistrez un nouveau message quickReplies dans la base de données et remplissez-le avec les données de l'utilisateur ou du client.
- Enregistrer chaque message son type d'expéditeur:
clients
, users
&bot
.
- Remplissez uniquement les messages qui ont l'expéditeur
clients
ou users
avec ses modèles Mongoose. Les modèles de client de type _sender sont clients
, pour l'utilisateur users
.
Schéma de message :
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);
SOLUTION
Ma demande d'API côté serveur
Mon code
Fonction utilitaire (sur chatUtils.js
fichier) pour obtenir le type de message que vous souhaitez enregistrer:
/**
* 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
};
Mon côté serveur (en utilisant Nodejs) pour obtenir la demande d'enregistrement du message:
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);
}
});
CONSEILS :
Pour la base de données:
- Chaque message est un document lui-même.
- Au lieu d'utiliser
refPath
, nous utilisons l'utilitaire getSenderModel
utilisé sur populate()
. C'est à cause du bot. Le sender.type
peut être: users
avec sa base de données, clients
avec sa base de données et bot
sans base de données. Les refPath
besoins vraie référence de modèle, sinon, Mongooose jette une erreur.
sender._id
peut être de type ObjectId
pour les utilisateurs et les clients, ou null
pour le bot.
Pour la logique de demande d'API:
- Nous remplaçons le
quickReply
message (Message DB doit avoir une seule réponse rapide, mais autant de messages texte simples que vous le souhaitez). Nous utilisons le findOneAndUpdate
au lieu de replaceOne
ou findOneAndReplace
.
- Nous exécutons l'opération de requête (le
findOneAndUpdate
) et l' populate
opération avec le callback
de chacun. Ceci est important si vous ne savez pas si l' utilisation async/await
, then()
, exec()
ou callback(err, document)
. Pour plus d'informations, consultez le document Populate Doc .
- Nous remplaçons le message de réponse rapide par l'
overwrite
option et sans $set
opérateur de requête.
- Si nous ne trouvons pas la réponse rapide, nous en créons une nouvelle. Vous devez le dire à Mongoose avec
upsert
option.
- Nous ne remplissons qu'une seule fois, pour le message remplacé ou le nouveau message enregistré.
- Nous revenons aux rappels, quel que soit le message que nous avons enregistré avec
findOneAndUpdate
et pour le populate()
.
- Dans
populate
, nous créons une référence de modèle dynamique personnalisée avec le getSenderModel
. Nous pouvons utiliser la référence dynamique Mongoose car le sender.type
for bot
n'a pas de modèle Mongoose. Nous utilisons une base de données peuplée avec model
et path
optins.
J'ai passé beaucoup d'heures à résoudre de petits problèmes ici et là et j'espère que cela aidera quelqu'un! 😃