This is the back-end of the Roster Bot, my undergraduate thesis for IFRS that I created a Telegram chatbot with the purpose of helping users when they need manage a roster.
In this repository you will find a REST API, built with Node.js, Express, Sequelize and PostgreSQL, with the main objective of serving list and item management routes to the Roster-Bot, in addition to registering users through the Roster-Bot-Website.
To run this API in the development mode, you'll need to have a environment with NodeJS installed and you'll need to have a PostgreSQL database running on your machine at the port (15432).
Create a .env file to save yours environment variables, like this:
JWT_SECRET="my_hash",
DB_NAME="my_db_name",
DB_USER="my_db_username",
DB_PASSWORD="my_db_password",
DB_HOST="localhost",
DB_DIALECT="postgres",
DB_PORT="15432",
Cloning the Repository
$ git clone https://github.com/MateusZucco/Roster-Bot-Api.git
$ cd roster-bot-api
Installing dependencies
$ npm install
Running the database's migrations
$ npx sequelize db:create
$ npx sequelize db:migrate
With all dependencies installed, the Database running and the environment properly configured, you can now run the server:
$ npm run dev
The API has three main models: Rosters, RosterItems and Users. The Roster has a relation 1:N with RosterItems,the RosterItems has a realtion 1:1 to Rosters, the Users has a 0:N with Rosters and Rosters has a relation 1:1 with Users.
name: User's name. email: User's email. telegramId: User's Telegram Chat Id. phone: User's phone. password: User's password.
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
unique: true
},
telegramId: {
type: DataTypes.INTEGER,
allowNull: false,
unique: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.TEXT,
allowNull: false,
},
phone: {
type: DataTypes.STRING,
allowNull: false,
},
}
static associate(models) {
this.hasMany(models.rosters, { foreignKey: 'userId', as: 'rosters' })
}
}
userId: User id. title: Roster's title. description: Roster's description. itemsNumber: Roster length.
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
unique: true
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'users', key: 'id' },
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
title: {
type: DataTypes.STRING,
},
description: {
type: DataTypes.STRING,
},
itemsNumber: {
type: DataTypes.INTEGER,
default: 0
},
createdAt: {
type: DataTypes.DATE,
defaultValue: new Date(),
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
defaultValue: new Date(),
allowNull: false
}
static associate(models) {
this.belongsTo(models.users, { foreignKey: 'userId', as: 'rosters' })
this.hasMany(models.rostersItems, { foreignKey: 'rosterId', as: 'rosterItems' })
}
}
rosterId: Roster id. text: Item text. icon: Item icon. position: Item postiion.
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
unique: true
},
rosterId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'rosters', key: 'id' },
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
text: {
type: DataTypes.STRING,
allowNull: false
},
icon: {
type: DataTypes.STRING,
},
position: {
type: DataTypes.INTEGER,
default: 0
},
createdAt: {
type: DataTypes.DATE,
defaultValue: new Date(),
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
defaultValue: new Date(),
allowNull: false
}
static associate(models) {
this.belongsTo(models.rosters, { foreignKey: 'rosterId', as: 'rosterItems' })
}
}
ROUTES.post("/new-user", CONTROLLER.create); // create a new user
ROUTES.post("/:telegramId/login", CONTROLLER.login); // login in the Telegram Bot
ROUTES.get("/:telegramId/verify-register", CONTROLLER.verifyRegister); // verify if user is registered by Telegram Chat Id
ROUTES.get('/list-rosters', verifyToken, Controller.list) // get all rosters
ROUTES.post('/roster',verifyToken, Controller.create) // create a new roster
ROUTES.post('/roster/:ROSTER_ID/new-item', verifyToken, Controller.newItens) // add a new item to roster
ROUTES.put('/roster/:ROSTER_ID/item/:ITEM_ID', verifyToken, Controller.editItem) // edit item
ROUTES.put('/roster/:ROSTER_ID/title', verifyToken, Controller.editTitle) // edit roster title
ROUTES.put('/roster/:ROSTER_ID/description', verifyToken, Controller.editDescription) // edit roster description
ROUTES.put('/roster/:ROSTER_ID/change-positions-items/:ITEM_ONE_ID/:ITEM_TWO_ID', verifyToken, Controller.changePositions) // change position of roster items
ROUTES.delete('/roster/:ROSTER_ID', verifyToken, Controller.delete) // delete a roster
ROUTES.delete('/roster/:ROSTER_ID/item/:ITEM_ID', verifyToken, Controller.deleteItem) // delete a roster item
All rosters routers has the "verifyToken" autentication, so when a route is called the header is verified. If the header contains the authenticated token, the request receives a new key containing the User.
if ("headers" in req && "token" in req.headers) {
const TOKEN = req.headers["token"];
JWT.verify(TOKEN, process.env.JWT_SECRET, async function (err, decode) {
if (err) req.user = undefined;
const USER_FINDED = await USER.findOne({ where: { id: decode.id } });
req.user = USER_FINDED;
next();
});
} else {
req.user = undefined;
res
.status(401)
.send({
message:
"Usuário não autorizado, reinicie o bot e faça login novamente",
});
}