Per qualcuno che ha il problema populatee vuole anche farlo:
- chattare con testo semplice e risposte rapide (bolle)
- 4 collezioni di database per la chat:
clients, users, rooms, messasges.
- stessa struttura del DB dei messaggi per 3 tipi di mittenti: bot, utenti e client
refPatho riferimento dinamico
populatecon pathe modelopzioni
- utilizzare
findOneAndReplace/ replaceOnecon$exists
- creare un nuovo documento se il documento recuperato non esiste
CONTESTO
Obbiettivo
- Salvare un nuovo messaggio di testo semplice nel database e popolarlo con i dati dell'utente o del cliente (2 diversi modelli).
- Salvare un nuovo messaggio quickReplies nel database e popolarlo con i dati dell'utente o del cliente.
- Salvare ogni messaggio il tipo del mittente:
clients, userse bot.
- Popolare solo i messaggi che hanno il mittente
clientso userscon i relativi modelli Mongoose. _sender type client models is clients, for user is users.
Schema del messaggio :
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);
SOLUZIONE
La mia richiesta API lato server
Il mio codice
Funzione di utilità (su chatUtils.jsfile) per ottenere il tipo di messaggio che si desidera salvare:
/**
* 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
};
Il mio lato server (utilizzando Nodejs) per ottenere la richiesta di salvataggio del messaggio:
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);
}
});
SUGGERIMENTI :
Per il database:
- Ogni messaggio è un documento stesso.
- Invece di usare
refPath, usiamo l'utility getSenderModelche viene utilizzata su populate(). Ciò è dovuto al bot. La sender.typepuò essere: userscon la sua base di dati, clientscon il suo database e botsenza un database. Il refPathvero riferimento al modello richiede, in caso contrario, Mongooose genera un errore.
sender._idpuò essere di tipo ObjectIdper utenti e client o nullper il bot.
Per la logica della richiesta API:
- Sostituiamo il
quickReplymessaggio (il DB dei messaggi deve avere solo una risposta rapida, ma tutti i messaggi di testo semplici che desideri). Usiamo findOneAndUpdateinvece di replaceOneo findOneAndReplace.
- Eseguiamo l'operazione di query (il
findOneAndUpdate) e l' populateoperazione con il callbackdi ciascuno. Questo è importante se non si sa se l'uso async/await, then(), exec()o callback(err, document). Per maggiori informazioni guarda il Populate Doc .
- Sostituiamo il messaggio di risposta rapida con l'
overwriteopzione e senza$set operatore di query.
- Se non troviamo la risposta rapida, ne creiamo una nuova. Devi dirlo a Mongoose con questo
upsert opzione.
- Compiliamo solo una volta, per il messaggio sostituito o per il nuovo messaggio salvato.
- Torniamo ai callback, qualunque sia il messaggio che abbiamo salvato con
findOneAndUpdatee per il populate().
- In
populate, creiamo un riferimento al modello dinamico personalizzato con l'estensione getSenderModel. Possiamo usare il riferimento dinamico Mongoose perché sender.typefor botnon ha alcun modello Mongoose. Usiamo un database di popolamento attraverso con modele pathoptins.
Ho passato molte ore a risolvere piccoli problemi qua e là e spero che questo possa aiutare qualcuno! 😃