Nodejs RESTful API Production ============================================================= :Date: 5 Oct 2019 Progress ------------ - Set Email and Password: - If Email exists, this is an **reset password** action, verification email will be sent - Otherwise this is and **registration** action, verification email will be sent - Handle request - For every request, Email and Password will be verified - If failed, the identity is treated as "public" - User can create data - User can find data by its ID and **only modify the part of content that belongs to them** - Database Model .. code-block:: jsx data = { _id: assigned automatically by database, creater: creater email (unique), contents: [ { owner: owner 1 email (unique), content: owner 1 content, }, { owner: owner 2 email (unique), content: owner 2 content, }, ... ], } 数据 = { _id: 由 database 自动分配, 创建者: 创建者邮箱(唯一), 所有内容: [ { 所有者: 所有者 1 邮箱(唯一), 内容: 所有者 1 内容, }, { 所有者: 所有者 2 邮箱(唯一), 内容: 所有者 2 内容, }, ... ], } Database Model: possible improvements ----------------------------------------------- .. code-block:: jsx createData(account,req,res){ var self = this var query = req.body.createData var data = new self.DataModel() data.creater = account data.content = query.content data.save() console.log("\nNew Data:\n", data) query.parents.forEach((parentId)=>{ self.DataModel.findById(parentId,(err,parent)=>{ if(parent==null){ console.log("\nparentId not found\n") } else{ parent.content.children.push(data._id) } }) }) } updateData(account,req,res){ var self = this var query = req.body.updateData self.DataModel.findById(query.id,(err,data)=>{ if(data == null){ console.log("\nupdateDataId not found\n") } else{ if(data.creater==account) data.content = query.content } }) } //============ Model ============ var DataSchema = new self.mongoose.Schema({ creater: String, content: self.mongoose.Schema.Types.Mixed, }) DataSchema.index({ content: 'text', }) self.DataModel = self.mongoose.model('DataModel', DataSchema) To Do --------- - Compare the above **Access Control** model with `Role based access control `_ - Figure out what are **Hash and Salt** and whether I need it if every request requires **Email and Password** - Split the **my_modules/SimpleAPI.js** into more modules, it has got too long ! Backend Main ------------------ - To use: .. code-block:: console sudo service mongod start node index.js - **index.js** .. code-block:: jsx const app = require('express')() app.get('/',(req, res)=>{ res.sendFile(__dirname+'/index.html') }) const SimpleAPI = require("./my_modules/SimpleAPI.js") const simpleAPI = new SimpleAPI( hostIP = 'localhost', port = 3000, app, appName = "SimpleAPI", myAddress = "dingruiqi97m@gmail.com", myPassword = "" ) Backend Module ------------------ - **my_modules/SimpleAPI.js** - **Dependencies** - **express** // Of course - **./SimpleGmail.js** // To send verification email - **crypto** // To generate secretCode for verification - **ejs** // To use html templates - **mongoose (MongoDB)** // Database .. code-block:: jsx module.exports = class SimpleAPI{ constructor(hostIP, port, app, appName, myAddress, myPassword){ var self = this self.appName = appName // express server const jsonParser = require('body-parser').json() app.get('/SimpleAPI/setAccountVerify',(req, res)=>{ self.verifyEmail(req, res) }) app.post('/SimpleAPI',jsonParser,(req,res)=>{ if(req.body.type == "setAccount"){ self.setAccount(req,res) } else if(req.body.type == "listThenDeleteAll"){ self.listThenDeleteAll() } else self.verifyAccount(req,res,(account)=>{ if(req.body.type=="saveData"){ self.saveData(account,req,res) } else if(req.body.type=="updateData"){ self.updateData(account,req,res) } }) }) self.host = "http://"+hostIP+":"+port app.listen(port, hostIP, () => console.log(self.host)) // mongodb mongoose self.mongoose = require('mongoose') self.mongoose.connect('mongodb://localhost/SimpleAPI',{ useNewUrlParser: true, useFindAndModify: false, useCreateIndex: true, useUnifiedTopology: true }) //Fix Deprecation Warnings: https://mongoosejs.com/docs/deprecations.html self.db = self.mongoose.connection self.db.on('error',console.error.bind(console,'db err:')) self.defineSchemasAndModels() // SimpleGmail const SimpleGmail = require("./SimpleGmail.js") self.simpleGmail = new SimpleGmail(myAddress, myPassword) } setAccount(req,res){ var self = this var secretCode = require('crypto').randomBytes(16).toString('hex') var query = req.body.setAccount self.AccountModel.findOneAndUpdate( {type: query.type, email: query.email}, {state: "ToBeVerified"+secretCode}, {new: true}, (err, modifiedAccount)=>{ if(modifiedAccount==null){ var account = new self.AccountModel({ type: query.type, email: query.email, state: "ToBeVerified"+secretCode }) account.save() console.log("\nNew Account:\n", account) } else console.log("\nModified Account:\n", modifiedAccount) } ) // send user email for verification var email = query.email var subject = self.appName+": Set Account Verification" var htmlPromise = require('ejs').renderFile(__dirname+'/SetAccountVerification.ejs',{ subject: subject, host: self.host, setAccount: query, state: "ToBeVerified"+secretCode, }) htmlPromise.then( (html)=>{ self.simpleGmail.send(email, subject, html) res.send(html) // For Testing }) } verifyEmail(req, res){ var self = this var query = req.query self.AccountModel.findOneAndUpdate({ type: query.type, email: query.email, state: query.state }, {password: query.password, state:"Verified"}, (err,doc)=>{ if(doc==null) res.send("Error: This is not the latest link!") else res.json(doc) } ) } verifyAccount(req,res, callback){ var self = this var query = req.body.verifyAccount self.AccountModel.findOne( { type: query.type, email: query.email, password : query.password}, (err, account)=>{ if(account==null){ console.log("public") callback("public") } else{ console.log(query.email) callback(query.email) } } ) } saveData(account, req, res){ var self = this var query = req.body.saveData var data = new self.DataModel() data.creater = account data.contents.push({owner:account, content:query.content}) data.save() console.log("\nNew Data:\n", data) } updateData(account, req, res){ var self = this var query = req.body.updateData self.DataModel.findById(query.id,(err, data)=>{ if(data == null){ console.log("\nId not found\n") } else{ var index = data.contents.findIndex((content)=>{ return content.owner==account }) if(index>=0){ data.contents[index].content = query.content console.log("\nUpdate Data:\n", data) } else{ data.contents.push({owner:account, content:query.content}) console.log("\nUpdate New Data:\n", data) } } }) } defineSchemasAndModels(){ // https://stackoverflow.com/questions/28775051/best-way-to-perform-a-full-text-search-in-mongodb-and-mongoose var self = this var AccountSchema = new self.mongoose.Schema({ // for security type: String, email: String, password: String, state: String, content: String, }) AccountSchema.index({ type: 'text', content: 'text', }) self.AccountModel = self.mongoose.model('AccountModel', AccountSchema) var DataSchema = new self.mongoose.Schema({ creater: String, contents: [self.mongoose.Schema.Types.Mixed], }) DataSchema.index({ contents: 'text', }) self.DataModel = self.mongoose.model('DataModel', DataSchema) } listThenDeleteAll(){ var self = this self.AccountModel.find((err,accounts)=>{ console.log("\nCheck Accounts:\n", accounts) self.AccountModel.deleteMany((err,report)=>{ console.log("\nDelete Accounts:\n", report) }) }) self.DataModel.find((err,data)=>{ console.log("\nCheck Data:\n", data) self.DataModel.deleteMany((err,report)=>{ console.log("\nDelete Data:\n", report) }) }) } } Backend Email Verification Template -------------------------------------- - **my_modules/SetAccountVerification.ejs** .. code-block:: jsx <%= subject %>

setAccount: <%= JSON.stringify(setAccount) %>

Frontend For Testing -------------------------------------- - **index.html** - **The following code is ugly since it is only for testing!** .. code-block:: jsx

setAccount

click again to reset password

email will look like this

To verify your account

Create a new data

To update a data, type its id here

To list Then clear database for testing