Quellcode durchsuchen

Started separate project for the automate server.

Tomislav Cvetic vor 8 Jahren
Commit
2df6248264
13 geänderte Dateien mit 631 neuen und 0 gelöschten Zeilen
  1. 4 0
      .babelrc
  2. 24 0
      .gitignore
  3. 39 0
      core/basicSchema.js
  4. 52 0
      index.js
  5. 27 0
      package.json
  6. 47 0
      project/__mocks__/model.js
  7. 19 0
      project/_sampleData.js
  8. 19 0
      project/model.js
  9. 100 0
      project/route.js
  10. 52 0
      project/route.test.js
  11. 176 0
      routeGen.js
  12. 5 0
      routeGen.test.js
  13. 67 0
      router.js

+ 4 - 0
.babelrc

@@ -0,0 +1,4 @@
+{
+  "plugins": [["transform-object-rest-spread", {"useBuiltIns": true}]],
+  "presets": ["latest"]
+}

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# testing
+/coverage
+
+# production
+/build
+
+# documentation
+/code_docu
+
+# database
+/db
+
+# misc
+.DS_Store
+.env
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+

+ 39 - 0
core/basicSchema.js

@@ -0,0 +1,39 @@
+/** History requirement
+- Document can have embedded sub-documents
+- Document can have referenced documents
+- Document can be referenced by documents
+
+- On save:
+  * copy current version
+  *
+*/
+import mongoose from 'mongoose'
+
+const historySchema = new mongoose.Schema({
+  author: [mongoose.Schema.Types.ObjectId],
+  created: Date,
+  tag: String,
+  reference: [mongoose.Schema.Types.ObjectId],
+  head: Boolean
+})
+
+export const History = mongoose.model('History', historySchema)
+
+const basicSchema = {
+  name: {
+    type: String,
+    maxlength: 20,
+    required: true },
+  tag: {
+    type: String,
+    maxlength: 10,
+    required: true },
+  description: {
+    type: String,
+    maxlength: 200 }
+}
+
+export const collection = {
+  ...basicSchema,
+  __history: [historySchema]
+}

+ 52 - 0
index.js

@@ -0,0 +1,52 @@
+const express = require('express')
+const bodyParser = require('body-parser')
+const http = require('http')
+// const oauthserver = require('oauth2-server')
+const mongoose = require('mongoose')
+
+/** Load the submodules */
+import project from './project/route'
+
+/** Create the express app */
+const app = express()
+
+/** MongoDB middleware */
+const dbName = 'AutoMateDB'
+const connectionString = `mongodb://localhost:27017/${dbName}`
+
+/** Bind the http server to express */
+const server = http.createServer(app)
+
+/** Connect the middleware to the server */
+mongoose.connect(connectionString)
+
+function welcomeRouter (req, res) {
+  res.status(200)
+  res.json({ message: 'Welcome to the AutoMate DB API!' })
+}
+function errorRouter (req, res) {
+  res.status(404)
+  res.send()
+}
+
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: true }))
+
+/*
+app.oauth = oauthserver({
+  model: {},
+  grants: ['password'],
+  debug: true
+})
+app.all('/oauth/token', app.oauth.grant())
+app.get('/', app.oauth.authorise(), (req, res) => {
+  res.send('Secret area')
+})
+app.use(app.oauth.errorHandler())
+*/
+
+app.get('/', welcomeRouter)
+app.use('/project', project)
+app.use(/.*/, errorRouter)
+
+server.listen(process.env.PORT || 4100)

+ 27 - 0
package.json

@@ -0,0 +1,27 @@
+{
+  "name": "db-server",
+  "version": "0.0.1",
+  "description": "API Server for Mongo DB",
+  "main": "index.js",
+  "scripts": {
+    "start": "babel-node index.js",
+    "test": "jest --watch --env=jsdom ."
+  },
+  "author": "Tomi Cvetic",
+  "license": "GPL-3.0",
+  "dependencies": {
+    "babel": "^6.23.0",
+    "babel-cli": "^6.24.0",
+    "babel-jest": "^19.0.0",
+    "babel-plugin-transform-object-rest-spread": "^6.23.0",
+    "babel-preset-latest": "^6.24.0",
+    "body-parser": "^1.17.1",
+    "dotenv": "^4.0.0",
+    "express": "^4.15.2",
+    "jest-cli": "^19.0.2",
+    "mongoose": "^4.9.1",
+    "node-babel": "^0.1.2",
+    "oauth2-server": "^2.4.1",
+    "sinon": "^2.1.0"
+  }
+}

+ 47 - 0
project/__mocks__/model.js

@@ -0,0 +1,47 @@
+import samples from '../_sampleData.js'
+
+class ProjectMock {
+  constructor (data) {
+    for (let prop in data) {
+      this[prop] = data[prop]
+    }
+    // use this switch to simulate save errors.
+    this._saveError = false
+    // use this switch to simulate remove errors.
+    this._removeError = false
+  }
+
+  static findOne (criteria, callback) {
+    const { id } = criteria
+    // Find only the entry with id 58d3d6883d34293254afae42
+    if (id === '58d3d6883d34293254afae42') {
+      callback(undefined, samples[0])
+      return
+    }
+    // else return 404
+    callback(Error('item not found'), undefined)
+  }
+
+  static find (criteria, options, callback) {
+    const { limit, offset } = options
+    // apply criteria
+    const sampleSlice = samples.slice(offset, offset + limit)
+    // return the array
+    if (sampleSlice) {
+      callback(undefined, sampleSlice)
+      return
+    }
+    // if nothing can be shown, return 404
+    callback(Error('no items found'), undefined)
+  }
+
+  save (callback) {
+    //
+  }
+
+  remove (callback) {
+    //
+  }
+}
+
+export default ProjectMock

+ 19 - 0
project/_sampleData.js

@@ -0,0 +1,19 @@
+const projects = [{
+  name: 'Jungfrau',
+  tag: 'JU_CSD',
+  description: '9th generation GNSS receiver'
+}, {
+  name: 'Dom',
+  tag: 'DO_CSD',
+  description: '8th generation GNSS receiver'
+}, {
+  name: 'Titlis',
+  tag: 'TI_CSD',
+  description: '7th generation GNSS receiver'
+}, {
+  name: 'G6',
+  tag: 'G6',
+  description: 'Carmines first choise'
+}]
+
+export default projects

+ 19 - 0
project/model.js

@@ -0,0 +1,19 @@
+import mongoose from 'mongoose'
+import { collection } from '../core/basicSchema'
+
+const metaSchema = new mongoose.Schema({
+  type: String,
+  name: String,
+  description: String,
+  value: String
+})
+
+/** Metas are embedded. */
+const projectSchema = new mongoose.Schema({
+  ...collection,
+  meta: [metaSchema]
+})
+
+const Project = mongoose.model('Project', projectSchema)
+
+export default Project

+ 100 - 0
project/route.js

@@ -0,0 +1,100 @@
+import Project from './model'
+import mongoose from 'mongoose'
+import express from 'express'
+
+export function getHandler (req, res) {
+  /** GET handler (request one or more projects) */
+  let [ id ] = req.params
+
+  /** check whether an id was specified. */
+  if (typeof id !== 'undefined') {
+    try {
+      id = mongoose.Types.ObjectId(id.substring(1))
+    } catch (err) {
+      res.status(422)
+      res.send(err)
+      return
+    }
+    /** if yes, return the one project */
+    Project.findOne({ _id: id }, function (err, project) {
+      if (err) {
+        res.status(404)
+        res.send(err)
+        return
+      }
+      res.json(project)
+    })
+  } else {
+    /** if not, return all projects */
+    const { limit, offset } = req.query
+    Project.find({}, {
+      limit: Math.min(Math.max(parseInt(limit) || 20, 1), 100),
+      skip: Math.max(parseInt(offset) || 0, 0)
+    }, function (err, projects) {
+      if (err) {
+        res.status(404)
+        res.send(err)
+        return
+      }
+      res.json(projects)
+    })
+  }
+}
+
+export function postHandler (req, res) {
+  /** POST handler (insert new projects into database) */
+  const project = new Project(req.body)
+
+  project.save(function (err) {
+    if (err) {
+      res.status(422)
+      res.send(err)
+      return
+    }
+    res.send()
+  })
+}
+
+export function putHandler (req, res) {
+  /** PUT handler (update existing project) */
+  const id = mongoose.Types.ObjectId(req.params['0'].substring(1))
+
+  Project.findOne({ _id: id }, function (err, project) {
+    if (err) {
+      res.status(404)
+      res.send(err)
+    }
+
+    for (let prop in req.body) {
+      project[prop] = req.body[prop]
+    }
+
+    project.save(function (err) {
+      if (err) {
+        res.status(422)
+        res.send(err)
+      }
+      res.send()
+    })
+  })
+}
+
+export function deleteHandler (req, res) {
+  /** DELETE handler (delete project) */
+  const id = mongoose.Types.ObjectId(req.params['0'].substring(1))
+
+  Project.remove({ _id: id }, function (err, project) {
+    if (err) {
+      res.send(err)
+    }
+    res.send()
+  })
+}
+
+const router = express.Router()
+router.get(/\/(\w+)?\/?$/, getHandler)
+router.post('/', postHandler)
+router.put(/\/(\w+)?\/?$/, putHandler)
+router.delete('/', deleteHandler)
+
+export default router

+ 52 - 0
project/route.test.js

@@ -0,0 +1,52 @@
+import router, { getHandler, postHandler, putHandler, deleteHandler } from './route'
+import samples from './_sampleData'
+import sinon from 'sinon'
+import mongoose from 'mongoose'
+jest.mock('./model')
+
+function ResClass () {
+  this._status = null
+  this._data = null
+  this.status = (status) => { this._status = status }
+  this.send = (data) => { this._data = data }
+  this.json = (data) => {
+    this._data = data
+    this._status = 200
+  }
+}
+
+describe('Route handlers', () => {
+  it('handles the GET request for lists.', () => {
+    // if no _id is passed, return a list of objects.
+    // apply the limits correctly
+    const reqList = { params: [], query: {limit: 2.4, offset: 1.2} }
+    const res = new ResClass()
+    const returnValue = getHandler(reqList, res)
+    expect(returnValue).toBeUndefined()
+    expect(res._data).toBe(samples)
+    expect(res._status).toBe(200)
+    expect(ProjectFind.called).toBe(true)
+    expect(ProjectFind.firstCall.args[0]).toEqual({})
+    expect(ProjectFind.firstCall.args[1]).toEqual({limit: 2, skip: 1})
+  })
+  it('handles the GET request for single documents.', () => {
+    const reqOne = { params: ['/58d3d6883d34293254afae42'], query: undefined }
+    const res = new ResClass()
+    const returnValue = getHandler(reqOne, res)
+    expect(returnValue).toBeUndefined()
+    expect(res._data).toBe(samples[0])
+    expect(res._status).toBe(200)
+    expect(ProjectFindOne.called).toBe(true)
+    expect(ProjectFindOne.firstCall.args[0]).toEqual({_id: mongoose.Types.ObjectId('58d3d6883d34293254afae42')})
+  })
+  it('handles the POST request.', () => {
+    const reqOne = { params: ['/58d3d6883d34293254afae42'], query: undefined }
+    const res = new ResClass()
+    const returnValue = postHandler(reqOne, res)
+    expect(returnValue).toBeUndefined()
+    expect(res._data).toBe(samples[0])
+    expect(res._status).toBe(200)
+    expect(ProjectFindOne.called).toBe(true)
+    expect(ProjectFindOne.firstCall.args[0]).toEqual({_id: mongoose.Types.ObjectId('58d3d6883d34293254afae42')})
+  })
+})

+ 176 - 0
routeGen.js

@@ -0,0 +1,176 @@
+import mongoose from 'mongoose'
+import { History } from './basicSchema'
+
+function routeGen (model) {
+  /** GET handler (request one item or a list of items) */
+  function get (req, res, next) {
+    const path = req.params['0']
+    /** If the path doesn't match, call the next router */
+    if (path !== model.modelName.toLowerCase()) {
+      next()
+    }
+
+    let id = req.params['1']
+    /** check whether an id was specified. */
+    if (typeof id === 'string' && id.length > 1) {
+      // remove leading slash
+      id = id.substring(1)
+      try {
+        // try to convert the id string to a valid ObjectId
+        id = mongoose.Types.ObjectId(req.params['1'].substring(1))
+      } catch (err) {
+        // If id couldn't be converted, return an error message.
+        res.status(422)
+        res.send({error: err.message})
+        return
+      }
+      // if yes, return the one item
+      model.findOne({ _id: id }, function (err, item) {
+        if (err) {
+          res.status(404)
+          res.send(err)
+          return
+        }
+        res.json(item)
+      })
+    } else {
+      // if not, return a list of items
+      // limit the number of returned items and use an offset
+      const limit = req.query.limit ? Math.min(Math.max(parseInt(req.query.limit), 1), 100) : 20
+      const offset = req.query.offset ? Math.min(Math.max(parseInt(req.query.offset), 1), 100) : 20
+      model.find({}, {limit: limit, skip: offset}, function (err, items) {
+        if (err) {
+          res.status(404)
+          res.send(err)
+          return
+        }
+        res.json(items)
+      })
+    }
+  }
+
+  /** POST handler (insert a new item into the database) */
+  function post (req, res, next) {
+    const path = req.params['0']
+    // If the path doesn't match, call the next router
+    if (path !== model.modelName.toLowerCase()) {
+      next()
+    }
+
+    // create the new item
+    const item = new model(req.body)
+
+    // Check, if the model supports revision control
+    if (typeof model.schema.obj.__history !== 'undefined') {
+      item.__history = new History({
+        author: '',
+        created: Date(),
+        tag: '',
+        reference: [],
+        head: true
+      })
+    }
+
+    // save the item
+    item.save(function (err) {
+      if (err) {
+        res.status(422)
+        res.send(err)
+        return
+      }
+      res.send({ success: `${model.modelName} added.` })
+    })
+  }
+
+  /** PUT handler (update existing project) */
+  function put (req, res, next) {
+    const path = req.params['0']
+    /** If the path doesn't match, call the next router */
+    if (path !== model.modelName.toLowerCase()) {
+      next()
+    }
+
+    let id = req.params['1']
+    /** check whether an id was specified. */
+    if (typeof id === 'string' && id.length > 1) {
+      // remove leading slash
+      id = id.substring(1)
+      try {
+        // try to convert the id string to a valid ObjectId
+        id = mongoose.Types.ObjectId(req.params['1'].substring(1))
+      } catch (err) {
+        // If id couldn't be converted, return an error message.
+        res.status(422)
+        res.send({error: err.message})
+        return
+      }
+    }
+
+    // Find the object in the database
+    model.findOne({ _id: id }, function (err, item) {
+      if (err) {
+        res.status(404)
+        res.send(err)
+      }
+
+      // Check, if the model supports revision control
+      if (typeof model.schema.obj.__history !== 'undefined') {
+        // If yes, don't update the item, but create a new one based on the original
+        const newItem = model(item)
+        // replace the requested elements
+        for (let prop in req.body) {
+          newItem[prop] = req.body[prop]
+        }
+        // give it a new history with updated reference array. Set the head-indicator to true
+        newItem.__history = new History({
+          author: '',
+          created: Date(),
+          tag: '',
+          reference: [ ...item.__history.reference, item._id ],
+          head: true
+        })
+        // set the original item's head indicator to false
+        item.__history.head = false
+        // save the documents
+        newItem.save(function (err) {
+          if (err) {
+            res.status(422)
+            res.send(err)
+          }
+          res.json({ message: `${model.modelName} updated.` })
+        })
+      } else {
+        for (let prop in req.body) {
+          item[prop] = req.body[prop]
+        }
+      }
+
+      item.save(function (err) {
+        if (err) {
+          res.status(422)
+          res.send(err)
+        }
+        res.json({ message: `${model.modelName} updated.` })
+      })
+    })
+  }
+
+  /** DELETE handler (delete project) */
+  function del (req, res, next) {
+    const path = req.params['0']
+    const id = mongoose.Types.ObjectId(req.params['1'].substring(1))
+    /** If the path doesn't match, call the next router */
+    if (path !== '/project') {
+      next()
+    }
+
+    Project.remove({ _id: id }, function (err, project) {
+      if (err) {
+        res.send(err)
+      }
+      res.json({ message: 'Movie deleted.' })
+    })
+  }
+}
+
+export default routeGen

+ 5 - 0
routeGen.test.js

@@ -0,0 +1,5 @@
+import routeGen from './routeGen'
+
+// mock mongodb
+describe('generates routes for GET, POST, PUT, DELETE', () => {
+})

+ 67 - 0
router.js

@@ -0,0 +1,67 @@
+const Project = require('./project')
+const express = require('express')
+const router = express.Router()
+
+// GET all movies
+router.route(/^\/(\w+)\/(\w+)$/).get(function (req, res, next) {
+  Project.find(function (err, movies) {
+    if (err) {
+      return res.send(err)
+    }
+    res.json(movies)
+  })
+})
+/*
+// POST new movie
+router.route('/movies').post(function (req, res) {
+  const movie = new Project(req.body)
+
+  movie.save(function (err) {
+    if (err) {
+      return res.send(err)
+    }
+    res.send({ message: 'Project added.' })
+  })
+})
+
+// PUT update movie
+router.route('/movies/:id').put(function (req, res) {
+  Project.findOne({ _id: req.params.id }, function (err, movie) {
+    if (err) {
+      return res.send(err)
+    }
+
+    for (prop in req.body) {
+      movie[prop] = req.body[prop]
+    }
+
+    movie.save(function (req, res) {
+      if (err) {
+        return res.send(err)
+      }
+      res.json({ message: 'Project updated.' })
+    })
+  })
+})
+
+// GET one movie
+router.route('/movies/:id').get(function (req, res) {
+  Project.findOne({ _id: req.params.id }, function (err, movie) {
+    if (err) {
+      return res.send(err)
+    }
+    res.json(movie)
+  })
+})
+
+// DELETE one
+router.route('/movies/:id').delete(function (req, res) {
+  Project.remove({ _id: req.params.id }, function (err, movie) {
+    if (err) {
+      return res.send(err)
+    }
+    res.json({ message: 'Project deleted.' })
+  })
+}) */
+
+module.exports = router