Skip to content

Commit

Permalink
🚀 First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fatmatto committed Apr 25, 2019
0 parents commit b13dd21
Show file tree
Hide file tree
Showing 12 changed files with 7,231 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "standard",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.nyc_output/
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
Controller: require('./src/controller'),
buildRouter: require('./src/router'),
utils: require('./src/utils')
}
6,714 changes: 6,714 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "express-toolkit",
"version": "1.0.0",
"description": "Handy tools for building expressjs based http microservices",
"main": "index.js",
"scripts": {
"lint": "npx eslint index.js test/* --fix",
"test": "nyc --reporter=text ava test/*.spec.js --verbose",
"commit": "npx git-cz",
"changelog": "rm CHANGELOG.md; npx conventional-changelog -t -i CHANGELOG.md --same-file",
"release": "sh scripts/release.sh"
},
"author": "Mattia Alfieri",
"license": "MIT",
"husky": {
"hooks": {
"pre-commit": "npm test",
"pre-push": "npm test"
}
},
"keywords": [
"http",
"microservices",
"express"
],
"dependencies": {
"express": "^4.16.4",
"throwable-http-errors": "^1.0.1"
},
"devDependencies": {
"ava": "^1.4.1",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
"husky": "^1.3.1",
"mongodb-memory-server": "^5.1.0"
}
}
47 changes: 47 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/sh
# Based on https://gist.github.com/fatmatto/9e989d582c391446dcf4bf0c8116cb6a
# Increments the project version (e.g. from 2.3.0 to 2.4.0)
# It handles stuff like
# * CHANGELOG
# * NPM package version
# * Git tags


# Calculating the new version requires to know which kind of update this is
# The default version increment is patch
# Used values: major|minor|patch where in x.y.z :
# major=x
# minor=y
# patch=z

if [ -z "$1" ]
then
versionType="patch"
else
versionType=$1
fi

# Increment version without creating a tag and a commit (we will create them later)
npm --no-git-tag-version version $versionType || exit 1

# Using the package.json version
version="$(grep '"version"' package.json | cut -d'"' -f4)"

# Generate changelog from commits
rm CHANGELOG.md;
npx conventional-changelog -t -i CHANGELOG.md --same-file;

# Build the commit
git add package.json;
git add CHANGELOG.md;

git commit -m "📦 Release $version"

# Create an annotated tag
git tag -a $version -m "📦 Release $version"

# Gotta push them all
git push origin master --follow-tags;

# Release it!
npm publish
140 changes: 140 additions & 0 deletions src/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@

const DEFAULT_LIMIT_VALUE = 100
const DEFAULT_SKIP_VALUE = 0
const { getSorting } = require('./utils')
const Errors = require('throwable-http-errors')

class Controller {
/**
*
* @param {Object} config The configuration object
* @param {String} config.name The resource name
* @param {Number} config.defaultSkipValue The default skip value to be used in find() queries
* @param {Number} config.defaultLimitValue The default skip value to be used in find() queries
* @param {Object} config.model A mongoose model
*
*/
constructor (config) {
this.Model = config.model
this.name = config.name
this.defaultSkipValue = config.defaultSkipValue || DEFAULT_SKIP_VALUE
this.defaultLimitValue = config.defaultLimitValue || DEFAULT_LIMIT_VALUE
}
list (query) {
let skip = this.defaultSkipValue
let limit = this.defaultLimitValue
if (query.hasOwnProperty('limit')) {
limit = Number(query.limit)
}
if (query.hasOwnProperty('skip')) {
skip = Number(query.skip)
}

let sort = getSorting(query)

// Deleting modifiers from the query
delete query.skip
delete query.limit
delete query.sortby
delete query.sortorder

return this.Model
.find(query)
.sort(sort)
.skip(skip)
.limit(limit)
}

async findOne (query) {
const instance = await this.Model.findOne(query)
if (instance === null) {
throw new Errors.NotFound()
} else {
return instance
}
}
async getById (id) {
const instance = await this.Model.findOne({
_id: id
})

if (instance === null) {
throw new Errors.NotFound()
} else {
return instance
}
}

async create (data) {
const instance = new this.Model(data)

// In testing we don't have the

let validationError = instance.validateSync()
if (validationError) {
throw new Errors.BadRequest(validationError.message)
}

let savedInstance = await instance.save()

return savedInstance.toJSON()
}

async updateOne (query, update) {
let instance = await this.Model.findOne(query)

if (instance === null) {
throw new Errors.NotFound()
}

for (var k in update) {
instance.set(k, update[k])
}

let validationError = instance.validateSync()
if (validationError) {
throw new Errors.BadRequest(validationError.message)
}

return instance.save()
}

async updateById (id, update) {
let instance = await this.Model.findOne({
_id: id
})

if (instance === null) {
throw new Errors.NotFound()
}

for (var k in update) {
instance.set(k, update[k])
}

let validationError = instance.validateSync()
if (validationError) {
throw new Errors.BadRequest(validationError.message)
}

return instance.save()
}

/**
* Removes resources by query
* @param {Object} query Match resources to remove.
*/
delete (query) {
return this.Model.remove(query)
}

/**
* Removes a resource by id
* @param {String} id The resource's id.
*/
deleteById (id) {
return this.Model.remove({ _id: id })
}
}

module.exports = Controller
50 changes: 50 additions & 0 deletions src/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

const express = require('express')
const { asyncMiddleware } = require('./utils')
/**
* Builds an expressjs router instance
* @param {Object} config
* @param {Object} config.controller
*/
function buildRouter (config) {
const router = express.Router()
router.get('/count', asyncMiddleware(async (req, res, next) => {
let query = req.query
let count = await config.controller.count(query)
res.send({ status: true, data: { count: count } })
}))

router.get('/', asyncMiddleware(async (req, res, next) => {
let query = req.query
let resources = await config.controller.list(query)
res.send({ status: true, data: resources })
}))

router.get('/:id', asyncMiddleware(async (req, res, next) => {
let resource = await config.controller.getById(req.params.id)

if (resource === null) { throw new Errors.NotFound('Cannot find resource with id ' + req.params.id) }

res.send({ status: true, data: resource })
}))

router.post('/', asyncMiddleware(async (req, res, next) => {
let resource = await config.controller.create(req.body)
res.send({ status: true, data: resource })
}))

router.put('/:id', asyncMiddleware(async (req, res, next) => {
let resource = await config.controller.update(req.params.id, req.body)

res.send({ status: true, data: resource })
}))

router.delete('/:id', asyncMiddleware(async (req, res, next) => {
await config.controller.delete(req.params.id)
res.send({ status: true })
}))

return router
}

module.exports = buildRouter
54 changes: 54 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict'

const Errors = require('throwable-http-errors')
/**
*
* @param {Function} fn The function to wrap
*/
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}

/**
*
* @param {String} str The string to test
* @return true if the string is valid json
*/
let isJSON = (str) => {
try {
return (JSON.parse(str) && !!str)
} catch (e) {
return false
}
}

/**
* Returns the sort object to pass to MongoDB
* @param {Object} query The parsed query string object
*/
function getSorting (query) {
let sortBy = query.sortby || '_id'
let sortOrder = query.sortorder || 'DESC'

if (sortOrder === 'DESC') {
sortOrder = -1
} else if (sortOrder === 'ASC') {
sortOrder = 1
} else {
throw new Errors.BadRequest(`sortorder parameter can be "ASC" or "DESC". Got "${sortOrder}."`)
}

let sorting = {}

sorting[sortBy] = sortOrder

return sorting
}

module.exports = {
getSorting,
asyncMiddleware,
isJSON
}
Loading

0 comments on commit b13dd21

Please sign in to comment.