We need to start by implementing a data model for storing messages. The basic fields required are a unique ID, the username of the person sending the message, the namespace the message is sent to, their message, and finally a timestamp for when the message was sent. As messages are received or deleted, events must be emitted from the model so we can do the right thing on the web page.
This model implementation will be written for Sequelize. If you prefer a different storage solution, you can , by all means, re-implement the same API on other data storage systems.
Create a new file, models/messages-sequelize.mjs, containing the following:
import Sequelize from 'sequelize';
import jsyaml from 'js-yaml';
import fs from 'fs-extra';
import util from 'util';
import EventEmitter from 'events';
class MessagesEmitter extends EventEmitter {}
import DBG from 'debug';
const debug = DBG('notes:model-messages');
const error = DBG('notes:error-messages');
var SQMessage;
var sequlz;
export const emitter = new MessagesEmitter();
This sets up the modules being used and also initializes the EventEmitter interface. We're also exporting the EventEmitter as emitter so other modules can use it:
async function connectDB() {
if (typeof sequlz === 'undefined') {
const yamltext = await
fs.readFile(process.env.SEQUELIZE_CONNECT, 'utf8');
const params = jsyaml.safeLoad(yamltext, 'utf8');
sequlz = new Sequelize(params.dbname,
params.username, params.password, params.params);
}
if (SQMessage) return SQMessage.sync();
SQMessage = sequlz.define('Message', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey:
true },
from: Sequelize.STRING,
namespace: Sequelize.STRING,
message: Sequelize.STRING(1024),
timestamp: Sequelize.DATE
});
return SQMessage.sync();
}
This defines our message schema in the database. We'll use the same database that we used for Notes, but the messages will be stored in their own table.
The id field won't be supplied by the caller; instead, it will be autogenerated. Because it is an autoIncrement field, each message that's added will be assigned a new id number by the database:
export async function postMessage(from, namespace, message) {
const SQMessage = await connectDB();
const newmsg = await SQMessage.create({
from, namespace, message, timestamp: new Date()
});
var toEmit = {
id: newmsg.id, from: newmsg.from,
namespace: newmsg.namespace, message: newmsg.message,
timestamp: newmsg.timestamp
};
emitter.emit('newmessage', toEmit);
}
This is to be called when a user posts a new comment/message. We first store it in the database, and then we emit an event saying the message was created:
export async function destroyMessage(id, namespace) {
const SQMessage = await connectDB();
const msg = await SQMessage.find({ where: { id } });
if (msg) {
msg.destroy();
emitter.emit('destroymessage', { id, namespace });
}
}
This is to be called when a user requests that a message should be deleted. With Sequelize, we must first find the message and then delete it by calling its destroy method. Once that's done, we emit a message saying the message was destroyed:
export async function recentMessages(namespace) {
const SQMessage = await connectDB();
const messages = SQMessage.findAll({
where: { namespace }, order: [ 'timestamp' ], limit: 20
});
return messages.map(message => {
return {
id: message.id, from: message.from,
namespace: message.namespace, message: message.message,
timestamp: message.timestamp
};
});
}
While this is meant to be called when viewing a note, it is generalized to work for any Socket.IO namespace. It finds the most recent 20 messages associated with the given namespace and returns a cleaned-up list to the caller.