Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
extends: airbnb-base
env:
node: true
mocha: true
jest: true
es6: true
parserOptions:
sourceType: strict
Expand Down Expand Up @@ -44,4 +44,4 @@
- global
arrow-parens:
- 2
- always
- always
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
FROM risingstack/alpine:3.4-v6.9.1-4.1.0
FROM risingstack/alpine:3.4-v7.9.0-4.4.0

ENV PORT 3001
EXPOSE 3001

COPY package.json package.json
RUN npm install
RUN npm install --global yarn
RUN yarn

# Add source files
COPY . .
Expand Down
2 changes: 1 addition & 1 deletion config/components/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const joi = require('joi')
const envVarsSchema = joi.object({
NODE_ENV: joi.string()
.allow(['development', 'production', 'test', 'provision'])
.required()
.default('production')
}).unknown()
.required()

Expand Down
2 changes: 1 addition & 1 deletion config/components/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const winston = require('winston')

const envVarsSchema = joi.object({
LOGGER_LEVEL: joi.string()
.allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
.allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly', 'test'])
.default('info'),
LOGGER_ENABLED: joi.boolean()
.truthy('TRUE')
Expand Down
21 changes: 17 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
'use strict'

const logger = require('winston')
const semver = require('semver')
const pkg = require('./package.json')

// validate Node version requirement
const runtime = {
expected: semver.validRange(pkg.engines.node),
actual: semver.valid(process.version)
}
const valid = semver.satisfies(runtime.actual, runtime.expected)
if (!valid) {
throw new Error(
`Expected Node.js version ${runtime.expected}, but found v${runtime.actual}. Please update or change your runtime!`
)
}

const type = process.env.PROCESS_TYPE

Expand All @@ -15,8 +29,7 @@ if (type === 'web') {
} else if (type === 'social-preprocessor-worker') {
require('./worker/social-preprocessor')
} else {
throw new Error(`
${type} is an unsupported process type.
Use one of: 'web', 'twitter-stream-worker', 'social-preprocessor-worker'!
`)
throw new Error(
`${type} is an unsupported process type. Use one of: 'web', 'twitter-stream-worker', 'social-preprocessor-worker'!`
)
}
26 changes: 25 additions & 1 deletion models/redis/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
'use strict'

module.exports = require('./redis')
const redis = require('./redis')

async function getTweets ({ offset, limit } = {}) {
const tweets = await redis.zrevrangebyscore(redis.SET.tweets, Date.now(), 0, 'LIMIT', offset, limit)
return tweets
.map((string) => {
try {
return JSON.parse(string)
} catch (ex) {
/* ignore */
}

return undefined
})
.filter(Boolean)
}

async function addTweet (tweet) {
return redis.zadd(redis.SET.tweets, tweet.createdAt.getTime(), JSON.stringify(tweet))
}

module.exports = Object.assign(redis, {
getTweets,
addTweet
})
50 changes: 28 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
{
"name": "multi-process-nodejs-example",
"version": "1.0.0",
"description": "",
"version": "2.0.0",
"description": "Example for https://blog.risingstack.com/node-js-project-structure-tutorial-node-js-at-scale/",
"main": "index.js",
"scripts": {
"start": "node .",
"start:prod": "NODE_ENV=production npm start",
"start:dev": "NODE_ENV=development nodemon .",
"test-web": "NODE_ENV=test PROCESS_TYPE=web mocha --require co-mocha test/setup.js 'web/**/*.spec.js'",
"test-web": "NODE_ENV=test PROCESS_TYPE=web jest --forceExit",
"test": "npm run test-web",
"lint": "eslint ."
},
"author": "Andras Toth <andras.toth@risingstack.com>",
"license": "MIT",
"engines": {
"node": ">=7.9.0"
},
"devDependencies": {
"chai": "3.5.0",
"co-mocha": "1.1.3",
"eslint": "3.12.0",
"eslint-config-airbnb-base": "11.0.0",
"eslint": "3.19.0",
"eslint-config-airbnb-base": "11.1.3",
"eslint-plugin-import": "2.2.0",
"eslint-plugin-promise": "3.4.0",
"eslint-plugin-promise": "3.5.0",
"jest": "19.0.2",
"mocha": "3.2.0",
"nock": "9.0.2",
"nock": "9.0.13",
"nodemon": "1.11.0",
"pre-commit": "1.2.1",
"sinon": "1.17.6",
"sinon-chai": "2.8.0",
"super-request": "1.1.0"
"pre-commit": "1.2.2",
"super-request": "1.2.0"
},
"dependencies": {
"@risingstack/trace": "2.36.0",
"dotenv": "2.0.0",
"@risingstack/trace": "3.6.2",
"boom": "4.3.1",
"dotenv": "4.0.0",
"es6-promisify": "5.0.0",
"ioredis": "2.4.2",
"joi": "10.0.5",
"koa": "1.2.4",
"koa-router": "5.4.0",
"qs": "6.3.0",
"ioredis": "2.5.0",
"joi": "10.4.1",
"koa": "2.2.0",
"koa-compose": "4.0.0",
"koa-router": "7.1.1",
"qs": "6.4.0",
"semver": "5.3.0",
"tortoise": "1.0.1",
"twitter": "1.7.0",
"winston": "2.3.0"
"winston": "2.3.1"
},
"jest": {
"testEnvironment": "node",
"setupTestFrameworkScriptFile": "./test/setup.js"
},
"pre-commit": [
"lint",
Expand Down
20 changes: 4 additions & 16 deletions test/setup.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
'use strict'

const sinon = require('sinon')
const chai = require('chai')
const sinonChai = require('sinon-chai')
const winston = require('winston')

process.env.PORT = 3000
process.env.REDIS_URI = 'redis://localhost'

before(() => {
chai.use(sinonChai)
const winston = require('winston')
const config = require('../config')

beforeAll(() => {
// we want to have logger.test() without flooding the console with other levels' messages
winston.setLevels({
debug: 5,
Expand All @@ -29,13 +25,5 @@ before(() => {
test: 'blue'
})
winston.remove(winston.transports.Console)
winston.add(winston.transports.Console, { level: process.env.LOGGER_LEVEL || 'test', colorize: true })
})

beforeEach(function beforeEach () {
this.sandbox = sinon.sandbox.create()
})

afterEach(function afterEach () {
this.sandbox.restore()
winston.add(winston.transports.Console, { level: config.logger.level, colorize: true })
})
4 changes: 3 additions & 1 deletion web/middleware/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict'

const parseQuery = require('./parseQuery')
const validator = require('./validator')

module.exports = {
parseQuery
parseQuery,
validator
}
6 changes: 3 additions & 3 deletions web/middleware/parseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
const qs = require('qs')

function parseQueryFactory (options) {
return function * parseQuery (next) {
this.query = qs.parse(this.querystring, options)
yield next
return async function parseQuery (ctx, next) {
ctx.query = qs.parse(ctx.querystring, options)
await next()
}
}

Expand Down
33 changes: 33 additions & 0 deletions web/middleware/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict'

const joi = require('joi')

function validatorFactory (schemas) {
return async function validatorMiddleware (ctx, next) {
['params', 'query']
.forEach((partToValidate) => {
if (schemas[partToValidate]) {
const validatedObject = joiValidate(ctx[partToValidate], schemas[partToValidate])

Object.assign(ctx[partToValidate], validatedObject)
}
})

if (schemas.body) {
ctx.request.body = joiValidate(ctx.request.body, schemas.body)
}

await next()
}
}

function joiValidate (props, schema, options) {
const validationResult = joi.validate(props, schema, options)
if (validationResult.error) {
throw validationResult.error
}

return validationResult.value
}

module.exports = validatorFactory
4 changes: 2 additions & 2 deletions web/router/api/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const tweets = require('./tweets')
const v1 = require('./v1')

module.exports = {
tweets
v1
}
36 changes: 0 additions & 36 deletions web/router/api/tweets/get.js

This file was deleted.

38 changes: 0 additions & 38 deletions web/router/api/tweets/get.spec.js

This file was deleted.

7 changes: 7 additions & 0 deletions web/router/api/v1/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

const tweets = require('./tweets')

module.exports = {
tweets
}
26 changes: 26 additions & 0 deletions web/router/api/v1/tweets/get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'

const joi = require('joi')
const compose = require('koa-compose')
const { validator } = require('../../../../middleware')
const redis = require('../../../../../models/redis')

const querySchema = joi.object({
limit: joi.number()
.default(10),
offset: joi.number()
.default(0)
})
.unknown()
.required()

async function getTweets (ctx) {
ctx.body = await redis.getTweets(ctx.query)
}

module.exports = compose([
validator({
query: querySchema
}),
getTweets
])
Loading