Для тих, хто має проблеми з цим populateі хоче це зробити:
- чат з простим текстом та швидкими відповідями (бульбашки)
- 4 колекції бази даних для чату:
clients, users, rooms,messasges .
- однакова структура БД повідомлень для 3 типів відправників: бот, користувачі та клієнти
refPathабо динамічне посилання
populateз pathіmodel варіанти
- використання
findOneAndReplace/ replaceOneз$exists
- створити новий документ, якщо вилучений документ не існує
КОНТЕКСТ
Мета
- Збережіть нове просте текстове повідомлення в базі даних і заповніть його даними користувача або клієнта (2 різних моделі).
- Збережіть нове повідомлення quickReplies у базі даних та заповніть його даними користувача чи клієнта.
- Зберегти кожне повідомлення його тип відправника:
clients, users&bot .
- Населяйте лише ті повідомлення, які має відправник
clientsабо usersз його мангустовими моделями. _sender тип клієнтських моделей є clients, для користувача є users.
Схема повідомлення :
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);
РІШЕННЯ
Мій запит на API сервера
Мій код
Функція утиліти (у chatUtils.jsфайлі), щоб отримати тип повідомлення, яке потрібно зберегти:
/**
* 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
};
Мій сервер (використовуючи Nodejs), щоб отримати запит на збереження повідомлення:
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);
}
});
ПОРАДИ :
Для бази даних:
- Кожне повідомлення - це сам документ.
- Замість використання
refPathми використовуємо утиліту, на getSenderModelяку використовується populate(). Це через бота. sender.typeМоже бути: usersз його базою даних, clientsз його базою даних і botбез бази даних. TherefPathПотребує в істинної еталонної моделі, якщо немає, то Mongooose видасть помилку.
sender._idможе бути тип ObjectIdдля користувачів і клієнтів, або nullдля бота.
Для логіки запиту API:
- Ми замінюємо
quickReplyповідомлення (у БД повідомлень повинно бути лише одне швидке, але стільки ж простих текстових повідомлень, скільки ви хочете). Ми використовуємо findOneAndUpdateзамість replaceOneабо findOneAndReplace.
- Ми виконуємо операцію запиту (the
findOneAndUpdate) та populateоперацію з callbackкожного з них. Це важливо , якщо ви не знаєте , якщо використовувати async/await, then(), exec()або callback(err, document). Для отримання додаткової інформації дивіться Документ про населення .
- Ми замінюємо швидке відповідь на
overwriteопцію та без $setзапиту оператора.
- Якщо ми не знайдемо швидкої відповіді, ми створюємо нову. Ви повинні сказати це мангусту з
upsertможливістю.
- Ми заповнюємо лише один раз для заміненого повідомлення або нового збереженого повідомлення.
- Ми повертаємось до зворотних викликів, незалежно від того, яке повідомлення ми зберегли
findOneAndUpdateі для якого populate().
- У
populateнас ми створюємо власну динамічну посилання на модель із getSenderModel. Ми можемо використовувати динамічну довідку Mongoose, тому що sender.typeдля bot"Монуз" немає жодної моделі. Ми використовуємо базу даних по населенню за допомогою modelта pathoptins.
Я витрачаю багато годин на вирішення маленьких проблем тут і там і сподіваюся, що це комусь допоможе! 😃