Additional Tutorial: Announcements Plugin (creating a database model)

We will go over how we built the plugin announcements that allows kit admins to post announcement messages to the client. This tutorial will demonstrate how to create a new model in the DB, how to create endpoints, and how to modify the client through a plugin.

We will focus on the script section for this plugin. For a more basic tutorial, please refer to the previous tutorial.

Step 1: Populate the basic JSON object

This plugin will not have any outside libraries installed nor any public_meta or meta values.

{
name: 'announcements',
version: 1,
bio: 'Enables getting, posting, and deleting announcements',
description: 'Add an accouncement for displaying news and posts to users',
author: 'bitHolla'
}

Step 2: Code the script for announcements

This is the full script for announcements:

const announcementModel = toolsLib.database.createModel(
'announcement',
properties = {
created_by: {
type: 'integer',
onDelete: 'CASCADE',
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
title: {
type: 'string',
allowNull: false
},
message: {
type: 'text',
allowNull: false
},
type: {
type: 'string',
allowNull: false,
defaultValue: 'info'
}
}
);
const { checkSchema } = expressValidator;
app.get('/plugins/announcements', [
checkSchema({
limit: {
in: ['query'],
isInt: true,
optional: true,
errorMessage: 'must be an integer'
},
page: {
in: ['query'],
isInt: true,
optional: true,
errorMessage: 'must be an integer'
},
order_by: {
in: ['query'],
isString: true,
optional: true,
errorMessage: 'must be a string'
},
order: {
in: ['query'],
isString: true,
isIn: {
options: [['asc', 'desc']],
},
errorMessage: 'must be one of [asc, desc]',
optional: true
},
start_date: {
in: ['query'],
isISO8601: true,
errorMessage: 'must be an iso date',
optional: true
},
end_date: {
in: ['query'],
isISO8601: true,
errorMessage: 'must be an iso date',
optional: true
}
})
], (req, res) => {
const errors = expressValidator.validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { limit, page, order_by, order, start_date, end_date } = req.query;
loggerPlugin.info(
req.uuid,
'GET /plugins/announcements query',
limit,
page,
order_by,
order,
start_date,
end_date
);
const pagination = toolsLib.database.paginationQuery(limit, page);
const ordering = toolsLib.database.orderingQuery(order_by, order);
const timeframe = toolsLib.database.timeframeQuery(start_date, end_date);
const query = {
where: {},
order: [ordering],
attributes: {
exclude: ['created_by']
},
...pagination
};
if (timeframe) query.where.created_at = timeframe;
announcementModel.findAndCountAll(query)
.then(toolsLib.database.convertSequelizeCountAndRows)
.then((announcements) => {
return res.json(announcements);
})
.catch((err) => {
loggerPlugin.error(
req.uuid,
'GET /plugins/announcements err',
err.message
);
return res.status(err.status || 400).json({ message: err.message });
});
});
app.post('/plugins/announcement', [
toolsLib.security.verifyBearerTokenExpressMiddleware(['admin']),
checkSchema({
title: {
in: ['body'],
errorMessage: 'must be a string',
isString: true,
isLength: {
errorMessage: 'must be minimum length of 1',
options: { min: 1 }
},
optional: false
},
message: {
in: ['body'],
errorMessage: 'must be a string',
isString: true,
isLength: {
errorMessage: 'must be minimum length of 5',
options: { min: 1 }
},
optional: false
},
type: {
in: ['body'],
errorMessage: 'must be a string',
isLength: {
errorMessage: 'must be minimum length of 1',
options: { min: 1 }
},
isString: true,
optional: true
}
})
], (req, res) => {
const errors = expressValidator.validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
loggerPlugin.verbose(
req.uuid,
'POST /plugins/announcement auth',
req.auth.sub
);
let { title, message, type } = req.body;
if (!type) type = 'info';
loggerPlugin.info(
req.uuid,
'POST /plugins/announcement title',
title,
'type',
type
);
announcementModel.create({
created_by: req.auth.sub.id,
title,
message,
type
})
.then((announcement) => {
return res.json(announcement);
})
.catch((err) => {
loggerPlugin.error(
req.uuid,
'POST /plugins/announcement err',
err.message
);
return res.status(err.status || 400).json({ message: err.message });
});
});
app.delete('/plugins/announcement', [
toolsLib.security.verifyBearerTokenExpressMiddleware(['admin']),
checkSchema({
id: {
in: ['query'],
errorMessage: 'must include an id',
isInt: true,
optional: false
}
})
], (req, res) => {
const errors = expressValidator.validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
loggerPlugin.verbose(
req.uuid,
'DELETE /plugins/announcement auth',
req.auth.sub
);
const { id } = req.query;
loggerPlugin.info(
req.uuid,
'DELETE /plugins/announcement id',
id
);
announcementModel.findOne({
where: { id }
})
.then((announcement) => {
if (!announcement) {
throw new Error('Not found');
}
return announcement.destroy();
})
.then(() => {
return res.json({ message: 'Success' });
})
.catch((err) => {
loggerPlugin.error(
req.uuid,
'DELETE /plugins/announcement err',
err.message
);
return res.status(err.status || 400).json({ message: err.message });
});
});

Let's break this down.

const announcementModel = toolsLib.database.createModel(
'announcement',
properties = {
created_by: {
type: 'integer',
onDelete: 'CASCADE',
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
title: {
type: 'string',
allowNull: false
},
message: {
type: 'text',
allowNull: false
},
type: {
type: 'string',
allowNull: false,
defaultValue: 'info'
}
}
);

This part of the code creates a new Sequelize Database model for a new table Announcement. We do this by using the HollaEx Tools Library function createModel. The createModel function takes two parameters: the name of the new table and the properties for it. For our announcement table, we need the properties created_by, title, message, and type. The properties given should follow the sequelize standard. Once created, we can use this model throughout the plugin.

app.get('/plugins/announcements', [...], (req, res) => {
...
});
app.post('/plugins/announcement', [...], (req, res) => {
...
});
app.delete('/plugins/announcement', [...], (req, res) => {
...
});

These sections create endpoints GET /plugins/announcements, POST /plugins/announcement, and DELETE /plugins/annoucement. We use the default libraries toolsLib, expressValidator, app, and loggerPlugin. GET /plugins/announcements is a public endpoint and does not use the tools library function verifyBearerTokenExpressMiddleware() while the other two endpoints use this middleware function to verify that the user is an admin.