├── .eslintignore ├── .codeclimate.yml ├── libs ├── config.js ├── log.js ├── db.js ├── utils.js └── auth.js ├── test ├── mocha.opts ├── events │ ├── fixture.js │ ├── handle.spec.js │ ├── scheduler.spec.js │ ├── routes.spec.js │ └── collection.spec.js ├── helpers.js ├── utils.spec.js ├── history │ ├── fixture.js │ ├── routes.spec.js │ └── collection.spec.js ├── home │ └── routes.spec.js ├── users │ ├── collection.spec.js │ └── routes.spec.js └── token │ └── routes.spec.js ├── .gitignore ├── bin └── boot.js ├── .travis.yml ├── api ├── history │ ├── routes.js │ └── collection.js ├── home │ └── routes.js ├── events │ ├── handle.js │ ├── collection.js │ ├── routes.js │ └── scheduler.js ├── users │ ├── collection.js │ └── routes.js └── token │ └── routes.js ├── clusters.js ├── LICENSE ├── index.js ├── .eslintrc ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | # node_modules ignored by default 2 | coverage/ 3 | public/ 4 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | JavaScript: true 3 | engines: 4 | eslint: 5 | enabled: true 6 | 7 | -------------------------------------------------------------------------------- /libs/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jwt: { 3 | secret: 'Easy$K-API', 4 | session: {session: false} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /libs/log.js: -------------------------------------------------------------------------------- 1 | import bunyan from 'bunyan' 2 | 3 | module.exports = bunyan.createLogger({ 4 | name: 'easy-schedule' 5 | }) 6 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require test/helpers 2 | --reporter spec 3 | --ui bdd 4 | --recursive 5 | --compilers js:babel/register 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/ 3 | npm-debug.log 4 | newrelic_agent.log 5 | coverage/ 6 | .idea 7 | .vagrant/ 8 | tags 9 | **/*.swp 10 | newrelic_agent.log 11 | -------------------------------------------------------------------------------- /libs/db.js: -------------------------------------------------------------------------------- 1 | import monk from 'monk' 2 | import wrap from 'co-monk' 3 | 4 | let db; 5 | 6 | export default name => { 7 | if (!db) { 8 | db = monk('localhost/schedule') 9 | } 10 | return wrap(db.get(name)) 11 | } 12 | -------------------------------------------------------------------------------- /bin/boot.js: -------------------------------------------------------------------------------- 1 | import http from 'http' 2 | import app from '../index' 3 | import log from '../libs/log' 4 | 5 | const port = process.env.PORT || 3000 6 | 7 | http.createServer(app.callback()) 8 | app.listen(port) 9 | log.info(`Easy Schedule API - port ${port}`) 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.1' 4 | services: 5 | - mongodb 6 | before_script: 7 | - sleep 15 8 | - mongo mydb_test --eval 'db.addUser("travis", "test");' 9 | - export PATH="$PATH:$(npm bin)" 10 | - npm update 11 | script: 12 | - npm test 13 | -------------------------------------------------------------------------------- /api/history/routes.js: -------------------------------------------------------------------------------- 1 | import krouter from 'koa-router' 2 | import {findByUser} from './collection' 3 | 4 | const router = krouter() 5 | 6 | router. 7 | get('/', function* () { 8 | try { 9 | this.body = yield findByUser(this.user) 10 | } catch(err) { 11 | this.throw(500, err) 12 | } 13 | }) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /test/events/fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = () => { 4 | return { 5 | event1: { 6 | url: 'https://google.com', 7 | type: 'Recurring', 8 | cron: '* * * * *' 9 | }, 10 | event2: { 11 | url: 'https://google.com', 12 | type: 'Recurring', 13 | cron: '* * * * *' 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | import supertest from 'supertest' 2 | import mocha from 'mocha' 3 | import sinon from 'sinon' 4 | import sinonChai from 'sinon-chai' 5 | import coMocha from 'co-mocha' 6 | import chai from 'chai' 7 | import app from '../index' 8 | 9 | global.app = app 10 | global.sinon = sinon 11 | global.request = supertest(app.listen()) 12 | global.expect = chai.expect 13 | 14 | chai.use(sinonChai) 15 | coMocha(mocha) 16 | -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | import utils from '../libs/utils' 2 | 3 | const isPlainObject = utils.isPlainObject 4 | 5 | describe('UtilsSpec', () => { 6 | 7 | describe('.isPlainObject', () => { 8 | it('should return truthy', () => { 9 | expect(isPlainObject({})).to.be.true 10 | }) 11 | 12 | it('should return falsy', () => { 13 | expect(isPlainObject([{foo: 'bar'}])).to.be.false 14 | }) 15 | }) 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /api/history/collection.js: -------------------------------------------------------------------------------- 1 | import db from '../../libs/db' 2 | 3 | const history = db('history') 4 | 5 | const findByUser = function* (user) { 6 | return yield history.find({ 7 | 'user.name': user.name 8 | }) 9 | } 10 | 11 | const create = function* (data) { 12 | return yield history.insert(data) 13 | } 14 | 15 | const cleardb = function* () { 16 | return yield history.remove() 17 | } 18 | 19 | export default {findByUser, create, cleardb} 20 | -------------------------------------------------------------------------------- /libs/utils.js: -------------------------------------------------------------------------------- 1 | const fnToString = Function.prototype.toString 2 | const objCtorString = fnToString.call(Object) 3 | const getPrototypeOf = Object.getPrototypeOf 4 | let proto = Object.prototype 5 | 6 | exports.isPlainObject = value => { 7 | if (typeof value.constructor === 'function') { 8 | proto = getPrototypeOf(value) 9 | } 10 | if (!proto) return true 11 | const Ctor = proto.constructor 12 | 13 | return typeof Ctor === 'function' && 14 | Ctor instanceof Ctor && 15 | fnToString.call(Ctor) === objCtorString 16 | } 17 | -------------------------------------------------------------------------------- /api/home/routes.js: -------------------------------------------------------------------------------- 1 | import krouter from 'koa-router' 2 | 3 | const router = krouter() 4 | 5 | /** 6 | * @api {get} / API Status 7 | * @apiGroup Status 8 | * @apiSuccess {String} status API status message 9 | * @apiSuccessExample {json} Success 10 | * HTTP/1.1 200 OK 11 | * { 12 | * "status": "Easy Schedule API" 13 | * } 14 | */ 15 | router. 16 | get('/', function* () { 17 | this.type = 'json' 18 | this.status = 200 19 | this.body = {status: 'Easy Schedule API'} 20 | }) 21 | 22 | export default router 23 | -------------------------------------------------------------------------------- /test/history/fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = () => { 4 | return { 5 | history1: { 6 | event: 'foo', 7 | status: 200, 8 | url: 'https://github.com/rafaeljesus/easy-schedule', 9 | elapsed: 243, 10 | scheduled: '2013-12-13T20:12:43Z', 11 | actual: '2013-12-13T20:12:45Z' 12 | }, 13 | history2: { 14 | event: 'bar', 15 | status: 503, 16 | url: 'https://github.com/rafaeljesus/easy-schedule', 17 | elapsed: 200, 18 | scheduled: '2013-12-13T20:12:43Z', 19 | actual: '2013-12-13T20:12:45Z' 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/events/handle.js: -------------------------------------------------------------------------------- 1 | import co from 'co' 2 | import request from 'co-request' 3 | import History from '../history/collection' 4 | import log from '../../libs/log' 5 | 6 | const fn = event => { 7 | return co(function* () { 8 | let res = yield request(event.url) 9 | let logObj = { 10 | statusCode: res.statusCode, 11 | body: res.body, 12 | headers: res.headers 13 | } 14 | log.info('succesfully sent cron job request', logObj) 15 | logObj.event = event._id 16 | logObj.user = event.user 17 | logObj.url = event.url 18 | return yield History.create(logObj) 19 | }). 20 | catch(err => log.error('failed to send cron job request', err)) 21 | } 22 | 23 | export default fn 24 | -------------------------------------------------------------------------------- /test/home/routes.spec.js: -------------------------------------------------------------------------------- 1 | import User from '../../api/users/collection' 2 | 3 | describe('Home:RoutesSpec', () => { 4 | 5 | let name = 'user-login' 6 | , pass = 'user-password' 7 | 8 | before(function* () { 9 | try { 10 | yield User.create(name, pass) 11 | } catch(err) { 12 | expect(err).to.not.be.ok 13 | } 14 | }) 15 | 16 | afterEach(function* () { 17 | try { 18 | yield User.cleardb() 19 | } catch(err) { 20 | expect(err).to.not.be.ok 21 | } 22 | }) 23 | 24 | it('should respond 200', done => { 25 | request. 26 | get('/v1'). 27 | auth(name, pass). 28 | expect('Content-Type', /json/). 29 | expect(200, done) 30 | }) 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /api/users/collection.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt' 2 | import db from '../../libs/db' 3 | 4 | const users = db('users') 5 | 6 | const findByEmail = function* (email) { 7 | return yield users.find({ 8 | email: email 9 | }) 10 | } 11 | 12 | const isPassword = (encodedPassword, password) => { 13 | return bcrypt.compareSync(encodedPassword, password) 14 | } 15 | 16 | const create = function* (user) { 17 | const salt = bcrypt.genSaltSync() 18 | user.password = bcrypt.hashSync(user.password, salt) 19 | user.createdAt = new Date() 20 | return yield users.insert(user) 21 | } 22 | 23 | const cleardb = function* () { 24 | return yield users.remove() 25 | } 26 | 27 | export default { 28 | create, 29 | findByEmail, 30 | isPassword, 31 | cleardb 32 | } 33 | -------------------------------------------------------------------------------- /clusters.js: -------------------------------------------------------------------------------- 1 | import cluster from 'cluster' 2 | import os from 'os' 3 | import co from 'co' 4 | import Scheduler from './api/events/scheduler' 5 | import log from './libs/log' 6 | 7 | const CPUS = os.cpus() 8 | 9 | if (cluster.isMaster) { 10 | co(function* () { 11 | yield Scheduler.start() 12 | }) 13 | 14 | CPUS.map(() => cluster.fork()) 15 | 16 | cluster.on('listening', worker => { 17 | log.info('cluster %d conected', worker.process.pid) 18 | }) 19 | 20 | cluster.on('disconnect', worker => { 21 | log.info('cluster %d disconected', worker.process.pid) 22 | }) 23 | 24 | cluster.on('exit', worker => { 25 | log.info('cluster %d exited', worker.process.pid) 26 | cluster.fork() 27 | }) 28 | } 29 | 30 | if (cluster.isWorker) { 31 | require('./bin/boot') 32 | } 33 | -------------------------------------------------------------------------------- /libs/auth.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport' 2 | import {Strategy} from 'passport-jwt' 3 | import Users from '../api/users/collection' 4 | import cfg from './config' 5 | 6 | const options = {secretOrKey: cfg.jwt.secret} 7 | 8 | module.exports = (() => { 9 | 10 | const strategy = Strategy(options, (payload, done) => { 11 | Users.findById(payload.id). 12 | then(user => { 13 | if (user) { 14 | return done(null, { 15 | id: user.id, 16 | email: user.email 17 | }) 18 | } 19 | done(null, false) 20 | }). 21 | catch(error => done(error, null)) 22 | }) 23 | 24 | passport.use(strategy) 25 | 26 | return { 27 | initialize: () => { 28 | return passport.initialize() 29 | }, 30 | authenticate: () => { 31 | return passport.authenticate('jwt', cfg.jwt.session) 32 | } 33 | } 34 | })() 35 | -------------------------------------------------------------------------------- /test/events/handle.spec.js: -------------------------------------------------------------------------------- 1 | import nock from 'nock' 2 | import History from '../../api/history/collection' 3 | import handle from '../../api/events/handle' 4 | 5 | describe('Events:HandleSpec', () => { 6 | 7 | let httpMock 8 | let event = { 9 | _id: 'foo', 10 | user: {_id: 'bar', name: 'foo'}, 11 | url: 'https://google.com' 12 | } 13 | 14 | before(() => { 15 | httpMock = nock(event.url). 16 | get('/'). 17 | reply(200, { 18 | statusCode: 200, 19 | body: {}, 20 | headers: {} 21 | }) 22 | }) 23 | 24 | after(nock.cleanAll) 25 | 26 | afterEach(function* () { 27 | httpMock.done() 28 | yield History.cleardb() 29 | }) 30 | 31 | beforeEach(() => handle(event)) 32 | 33 | it('should send http request and store response', function* () { 34 | let res = yield History.findByUser(event.user) 35 | expect(res[0].event).to.exist 36 | expect(res[0].user).to.exist 37 | expect(res[0].url).to.exist 38 | }) 39 | 40 | }) 41 | 42 | -------------------------------------------------------------------------------- /api/events/collection.js: -------------------------------------------------------------------------------- 1 | import db from '../../libs/db' 2 | 3 | const events = db('events') 4 | 5 | const findAll = function* () { 6 | return yield events.find({}) 7 | } 8 | 9 | const findByUser = function* (user) { 10 | return yield events.find({ 11 | 'user.name': user.name 12 | }) 13 | } 14 | 15 | const findById = function* (id) { 16 | return yield events.findById(id) 17 | } 18 | 19 | const create = function* (data) { 20 | data.status = 'ACTIVE' 21 | return yield events.insert(data) 22 | } 23 | 24 | const update = function* (id, data) { 25 | const options = {new: true} 26 | , query = {_id: id} 27 | , mod = {$set: data} 28 | return yield events.findAndModify(query, mod, options) 29 | } 30 | 31 | const remove = function* (id) { 32 | return yield events.remove({_id: id}) 33 | } 34 | 35 | const cleardb = function* () { 36 | return yield events.remove() 37 | } 38 | 39 | export default { 40 | findAll, 41 | findByUser, 42 | findById, 43 | create, 44 | update, 45 | remove, 46 | cleardb 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rafael Jesus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/history/routes.spec.js: -------------------------------------------------------------------------------- 1 | import User from '../../api/users/collection' 2 | import History from '../../api/history/collection' 3 | 4 | describe('History:RoutesSpec', () => { 5 | 6 | let fixture = require('./fixture')() 7 | , name = 'foo' 8 | , password = 'bar' 9 | 10 | beforeEach(function* () { 11 | try { 12 | fixture.history1.user = {name: name} 13 | fixture.history2.user = {name: name} 14 | yield [ 15 | History.create(fixture.history1), 16 | History.create(fixture.history2), 17 | User.create(name, password) 18 | ] 19 | } catch(err) { 20 | expect(err).to.not.exist 21 | } 22 | }) 23 | 24 | afterEach(function* () { 25 | try { 26 | yield History.cleardb() 27 | } catch(err) { 28 | expect(err).to.not.exist 29 | } 30 | }) 31 | 32 | describe('GET /v1/history', () => { 33 | it('should find event history', done => { 34 | request. 35 | get('/v1/history'). 36 | auth(name, password). 37 | set('Accept', 'application/json'). 38 | set('Accept-Encoding', 'gzip'). 39 | expect('Content-Type', /json/). 40 | expect(200, done) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /api/users/routes.js: -------------------------------------------------------------------------------- 1 | import krouter from 'koa-router' 2 | import {create, remove} from './collection' 3 | 4 | const router = krouter() 5 | 6 | router. 7 | post('/', function* () { 8 | let login = this.request.body.login 9 | , password = this.request.body.password 10 | try { 11 | yield create(login, password) 12 | this.status = 201 13 | this.type = 'json' 14 | this.body = {message: 'User was successfully created'} 15 | } catch(err) { 16 | this.throw(500, err) 17 | } 18 | }). 19 | /** 20 | * @api {delete} /v1/users Exclude a user 21 | * @apiGroup Users 22 | * @apiHeader {String} Basic Authorization 23 | * @apiSuccessExample {json} Success 24 | * HTTP/1.1 200 User was successfully deleted 25 | * @apiErrorExample {json} Error 26 | * HTTP/1.1 422 Precondition Failed 27 | */ 28 | delete('/', function* () { 29 | let name = this.user.name 30 | , password = this.user.password 31 | try { 32 | yield remove(name, password) 33 | this.status = 200 34 | this.type = 'json' 35 | this.body = {message: 'User was successfully deleted'} 36 | } catch(err) { 37 | this.throw(422, err) 38 | } 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /test/users/collection.spec.js: -------------------------------------------------------------------------------- 1 | import User from '../../api/users/collection' 2 | 3 | describe('User:CollectionSpec', () => { 4 | 5 | let fixture = { 6 | email: 'foo@gmail.com', 7 | password: '123456' 8 | } 9 | 10 | afterEach(function* () { 11 | try { 12 | yield User.cleardb() 13 | } catch(err) { 14 | expect(err).to.not.exist 15 | } 16 | }) 17 | 18 | describe('.create', () => { 19 | 20 | it('should create a new user', function* () { 21 | try { 22 | let res = yield User.create(fixture) 23 | expect(res._id).to.exist 24 | } catch(err) { 25 | expect(err).to.not.exist 26 | } 27 | }) 28 | }) 29 | 30 | describe('.findByEmail', () => { 31 | 32 | before(function* () { 33 | try { 34 | yield User.create(fixture) 35 | } catch(err) { 36 | expect(err).to.not.exist 37 | } 38 | }) 39 | 40 | it('should find user by email', function* () { 41 | try { 42 | let user = yield User.findByEmail(fixture.email) 43 | expect(user).to.exist 44 | } catch(err) { 45 | expect(err).to.not.exist 46 | } 47 | }) 48 | }) 49 | 50 | describe.skip('.isPassword', () => { 51 | 52 | }) 53 | 54 | }) 55 | -------------------------------------------------------------------------------- /test/users/routes.spec.js: -------------------------------------------------------------------------------- 1 | import User from '../../api/users/collection' 2 | 3 | describe('User:RoutesSpec', () => { 4 | 5 | let name = 'user-login' 6 | , password = 'user-password' 7 | 8 | afterEach(function* () { 9 | try { 10 | yield User.cleardb() 11 | } catch(err) { 12 | expect(err).to.not.be.ok 13 | } 14 | }) 15 | 16 | describe('POST /v1/users', () => { 17 | it('should create a user', done => { 18 | request. 19 | post('/v1/users'). 20 | set('Accept', 'application/json'). 21 | set('Accept-Encoding', 'gzip'). 22 | send({name: name, password: password}). 23 | expect('Content-Type', /json/). 24 | expect(201, done) 25 | }) 26 | }) 27 | 28 | describe('DELETE /v1/users', () => { 29 | 30 | before(function* () { 31 | try { 32 | yield User.create(name, password) 33 | } catch(err) { 34 | expect(err).to.not.exist 35 | } 36 | }) 37 | 38 | it('should delete a user', done => { 39 | request. 40 | delete('/v1/users'). 41 | auth(name, password). 42 | set('Accept', 'application/json'). 43 | set('Accept-Encoding', 'gzip'). 44 | expect('Content-Type', /json/). 45 | expect(200, done) 46 | }) 47 | }) 48 | 49 | }) 50 | -------------------------------------------------------------------------------- /test/history/collection.spec.js: -------------------------------------------------------------------------------- 1 | import History from '../../api/history/collection' 2 | 3 | describe('History:CollectionSpec', () => { 4 | 5 | let user = {name: 'foo'} 6 | let fixture = require('./fixture')() 7 | 8 | afterEach(function* () { 9 | try { 10 | yield History.cleardb() 11 | } catch(err) { 12 | expect(err).to.not.exist 13 | } 14 | }) 15 | 16 | describe('.findByUser', () => { 17 | 18 | before(function* () { 19 | try { 20 | fixture.history1.user = user 21 | fixture.history2.user = user 22 | yield [ 23 | History.create(fixture.history1), 24 | History.create(fixture.history2) 25 | ] 26 | } catch(err) { 27 | expect(err).to.not.exist 28 | } 29 | }) 30 | 31 | it('should find all history', function* () { 32 | try { 33 | let res = yield History.findByUser(user) 34 | expect(res.length).to.be.equal(2) 35 | } catch(err) { 36 | expect(err).to.not.exist 37 | } 38 | }) 39 | }) 40 | 41 | describe('.create', () => { 42 | 43 | it('should create a history event', function* () { 44 | try { 45 | let res = yield History.create(fixture.history1) 46 | expect(res._id).to.exist 47 | } catch(err) { 48 | expect(err).to.not.exist 49 | } 50 | }) 51 | }) 52 | 53 | }) 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import koa from 'koa' 2 | import mount from 'koa-mount' 3 | import serve from 'koa-static' 4 | import koaBody from 'koa-bodyparser' 5 | import logger from 'koa-logger' 6 | import compress from 'koa-compress' 7 | import limit from 'koa-better-ratelimit' 8 | import helmet from 'koa-helmet' 9 | import cors from 'kcors' 10 | import zlib from 'zlib' 11 | // import auth from './libs/auth' 12 | import APIhome from './api/home/routes' 13 | import APItoken from './api/token/routes' 14 | import APIusers from './api/users/routes' 15 | import APIevents from './api/events/routes' 16 | import APIhistory from './api/history/routes' 17 | 18 | const app = koa() 19 | 20 | app.use(compress({ 21 | filter: contentType => /text/i.test(contentType) 22 | , threshold: 2048 23 | , flush: zlib.Z_SYNC_FLUSH 24 | })) 25 | app.use(limit({ 26 | duration: 1000 * 60 * 3 27 | , max: 10 28 | , blacklist: [] 29 | })) 30 | app.use(koaBody()) 31 | app.use(logger()) 32 | app.use(helmet()) 33 | app.use(cors({ 34 | methods: ['GET', 'POST', 'PUT', 'DELETE'], 35 | allowedHeaders: ['Content-Type', 'Authorization'] 36 | })) 37 | // app.use(auth.initialize()) 38 | app.use(mount('/v1', APIhome.middleware())) 39 | app.use(mount('/v1/token', APItoken.middleware())) 40 | app.use(mount('/v1/users', APIusers.middleware())) 41 | app.use(mount('/v1/events', APIevents.middleware())) 42 | app.use(mount('/v1/history', APIhistory.middleware())) 43 | app.use(serve('public')) 44 | 45 | module.exports = app 46 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "mocha": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "globals": { 9 | "expect": true, 10 | "request": true, 11 | "sinon": true 12 | }, 13 | "ecmaFeatures": { 14 | "arrowFunctions": true, 15 | "generators": true, 16 | "templateStrings": true, 17 | "globalReturn": true, 18 | }, 19 | "rules": { 20 | "strict": 0, 21 | "semi": 0, 22 | "no-undef": 2, 23 | "quotes": 0, 24 | "eqeqeq": 2, 25 | "indent": [2, 2], 26 | "camelcase": [1, {"properties": "always"}], 27 | "brace-style": 2, 28 | "max-depth": [2, 2], 29 | "max-params": [2, 4], 30 | "consistent-return": 0, 31 | "space-before-blocks": 2, 32 | "space-return-throw-case": 2, 33 | "no-delete-var": 2, 34 | "no-unneeded-ternary": 2, 35 | "no-shadow": 0, 36 | "no-new-object": 2, 37 | "no-dupe-keys": 2, 38 | "no-undef-init": 2, 39 | "no-undefined": 2, 40 | "no-new-require": 2, 41 | "no-func-assign": 2, 42 | "no-unreachable": 2, 43 | "no-unused-vars": 2, 44 | "no-use-before-define": 2, 45 | "no-extra-parens": 1, 46 | "no-use-before-define": 0, 47 | "no-unused-expressions": 0, 48 | "no-array-constructor": 0, 49 | "no-multiple-empty-lines": [1, {"max": 1}], 50 | "callback-return": [2, ["callback", "cb", "next"]], 51 | "comma-spacing": [2, {"before": false, "after": true}], 52 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/token/routes.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jwt-simple' 2 | import krouter from 'koa-router' 3 | import {findByEmail, isPassword} from '../users/collection' 4 | import cfg from '../../libs/config' 5 | 6 | const router = krouter() 7 | /** 8 | * @api {post} /token Auth token 9 | * @apiGroup Credentials 10 | * @apiParam {String} email User email 11 | * @apiParam {String} password User password 12 | * @apiParamExample {json} Input 13 | * { 14 | * "email": "john@connor.net", 15 | * "password": "123456" 16 | * } 17 | * @apiSuccess {String} token Token of authenticated user 18 | * @apiSuccessExample {json} Success 19 | * HTTP/1.1 200 OK 20 | * { 21 | * "token": "xyz.abc.123.hgf" 22 | * } 23 | * @apiErrorExample {json} Auth error 24 | * HTTP/1.1 401 Unauthorized 25 | */ 26 | router. 27 | post('/', function* () { 28 | const email = this.request.body.email 29 | , password = this.request.body.password 30 | 31 | if (!email || !password) { 32 | return this.status = 401 33 | } 34 | 35 | try { 36 | let res = yield findByEmail(email) 37 | let user = res && res[0] 38 | 39 | if (!isPassword(user.password, password)) { 40 | return this.status = 401 41 | } 42 | 43 | const payload = {_id: user._id} 44 | this.type = 'json' 45 | this.status = 200 46 | this.body = { 47 | token: jwt.encode(payload, cfg.jwt.secret) 48 | } 49 | } catch(err) { 50 | this.status = 401 51 | } 52 | }) 53 | 54 | export default router 55 | -------------------------------------------------------------------------------- /api/events/routes.js: -------------------------------------------------------------------------------- 1 | import krouter from 'koa-router' 2 | import Event from './collection' 3 | import Scheduler from './scheduler' 4 | 5 | const router = krouter() 6 | 7 | router. 8 | get('/', function* () { 9 | try { 10 | this.body = yield Event.findByUser(this.user) 11 | } catch(err) { 12 | this.throw(500, err) 13 | } 14 | }). 15 | 16 | post('/', function* () { 17 | let event = this.request.body 18 | try { 19 | let res = yield Event.create(event) 20 | event._id = res._id 21 | yield Scheduler.create(event) 22 | this.body = res._id 23 | } catch(err) { 24 | this.throw(500, err) 25 | } 26 | }). 27 | 28 | put('/:id', function* () { 29 | let event = this.request.body 30 | , id = this.params.id 31 | try { 32 | let res = yield [ 33 | Event.update(id, event), 34 | Scheduler.update(id, event) 35 | ] 36 | this.body = res[0] 37 | } catch(err) { 38 | this.throw(500, err) 39 | } 40 | }). 41 | 42 | get('/:id', function* () { 43 | const id = this.params.id 44 | try { 45 | this.body = yield Event.findById(id) 46 | } catch(err) { 47 | this.throw(500, err) 48 | } 49 | }). 50 | 51 | delete('/:id', function* () { 52 | const id = this.params.id 53 | try { 54 | yield [ 55 | Event.remove(id), 56 | Scheduler.cancel(id) 57 | ] 58 | this.body = {message: 'succesfully updated schedule job'} 59 | } catch(err) { 60 | this.throw(500, err) 61 | } 62 | }) 63 | 64 | export default router 65 | -------------------------------------------------------------------------------- /api/events/scheduler.js: -------------------------------------------------------------------------------- 1 | import scheduler from 'node-schedule' 2 | import handle from './handle' 3 | import Event from '../events/collection' 4 | import log from '../../libs/log' 5 | import utils from '../../libs/utils' 6 | 7 | const isPlainObject = utils.isPlainObject 8 | let scheduledEvents = {} 9 | 10 | const start = function* () { 11 | try { 12 | let res = yield Event.findAll() 13 | if (!res || res.length === 0) return 14 | if (isPlainObject(res)) { 15 | return yield create(res) 16 | } 17 | return yield res.map(create) 18 | } catch(err) { 19 | log.error('scheduler failed to start', err) 20 | } 21 | } 22 | 23 | const cancel = function* (_id) { 24 | try { 25 | let job = scheduledEvents[_id] 26 | if (!job) return 27 | job.cancel() 28 | delete scheduledEvents[_id] 29 | return yield {ok: 1} 30 | } catch(err) { 31 | throw err 32 | } 33 | } 34 | 35 | const create = function* (event) { 36 | try { 37 | const cron = event.cron ? event.cron : new Date(event.when) 38 | , fn = handle.bind(null, event) 39 | , job = scheduler.scheduleJob(cron, fn) 40 | 41 | scheduledEvents[event._id] = job 42 | log.info('succesfully scheduled job', event) 43 | return yield {ok: 1} 44 | } catch(err) { 45 | throw err 46 | } 47 | } 48 | 49 | const update = function* (_id, event) { 50 | event._id = _id 51 | yield cancel(_id) 52 | return yield create(event) 53 | } 54 | 55 | const getScheduledEvents = () => scheduledEvents 56 | 57 | const resetScheduledEvents = () => { 58 | scheduledEvents = {} 59 | } 60 | 61 | export default { 62 | start, 63 | create, 64 | update, 65 | cancel, 66 | getScheduledEvents, 67 | resetScheduledEvents 68 | } 69 | -------------------------------------------------------------------------------- /test/token/routes.spec.js: -------------------------------------------------------------------------------- 1 | import Users from '../../api/users/collection' 2 | 3 | describe('Routes:Token', () => { 4 | 5 | describe('POST /v1/token', () => { 6 | 7 | const fixture = { 8 | name: 'foo', 9 | email: 'foo@gmail.com', 10 | password: '123456' 11 | } 12 | 13 | beforeEach(function* () { 14 | try { 15 | yield Users.cleardb() 16 | yield Users.create(fixture) 17 | } catch(err) { 18 | expect(err).to.not.exist 19 | } 20 | }); 21 | 22 | describe('status 200', () => { 23 | it('returns authenticated user token', done => { 24 | request. 25 | post('/v1/token'). 26 | send({ 27 | email: fixture.email, 28 | password: fixture.password 29 | }). 30 | expect(200). 31 | end((err, res) => { 32 | expect(res.body).to.include.keys('token') 33 | done(err) 34 | }) 35 | }) 36 | }) 37 | 38 | describe('status 401', () => { 39 | it('throws error when password is incorrect', done => { 40 | request. 41 | post('/v1/token'). 42 | send({ 43 | email: fixture.email, 44 | password: 'INVALID' 45 | }). 46 | expect(401, done) 47 | }) 48 | 49 | it('throws error when email not exist', done => { 50 | request. 51 | post('/v1/token'). 52 | send({ 53 | email: 'INVALID', 54 | password: fixture.password 55 | }). 56 | expect(401, done) 57 | }); 58 | 59 | it('throws error when email and password are blank', done => { 60 | request. 61 | post('/v1/token'). 62 | expect(401, done) 63 | }) 64 | }) 65 | 66 | }) 67 | 68 | }) 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-schedule", 3 | "version": "1.0.1", 4 | "description": "Simple and flexible job scheduling for your application", 5 | "scripts": { 6 | "start": "npm run apidoc && npm run clusters", 7 | "clusters": "babel-node clusters.js", 8 | "test": "npm run lint && NODE=test ./node_modules/mocha/bin/_mocha --recursive -- test", 9 | "watch:server": "nodemon --exec npm run babel-node -- bin/boot", 10 | "lint": "eslint .", 11 | "apidoc": "apidoc -i api/ -o public/apidoc/" 12 | }, 13 | "keywords": [ 14 | "Schedule", 15 | "Scheduler", 16 | "Job", 17 | "Jobs" 18 | ], 19 | "author": "Rafael Jesus ", 20 | "license": "MIT", 21 | "apidoc": { 22 | "name": "REST Api Docs - Easy Scheduler API" 23 | }, 24 | "dependencies": { 25 | "apidoc": "^0.13.1", 26 | "babel": "^5.8.23", 27 | "bcrypt": "^0.8.5", 28 | "bunyan": "^1.4.0", 29 | "co": "^4.6.0", 30 | "co-monk": "^1.0.0", 31 | "co-request": "^1.0.0", 32 | "debug": "^2.2.0", 33 | "jwt-simple": "^0.3.1", 34 | "kcors": "^1.0.1", 35 | "koa": "^1.1.0", 36 | "koa-better-ratelimit": "^2.1.2", 37 | "koa-bodyparser": "^2.0.1", 38 | "koa-compress": "^1.0.8", 39 | "koa-helmet": "^0.3.0", 40 | "koa-logger": "^1.3.0", 41 | "koa-mount": "^1.3.0", 42 | "koa-router": "^5.1.2", 43 | "koa-static": "^1.5.1", 44 | "mongodb": "^1.4.4", 45 | "monk": "^1.0.1", 46 | "node-schedule": "^0.2.9", 47 | "passport": "^0.3.0", 48 | "passport-jwt": "^1.2.1", 49 | "request": "^2.65.0" 50 | }, 51 | "devDependencies": { 52 | "babel-eslint": "^4.1.3", 53 | "babel-runtime": "^5.8.25", 54 | "chai": "^3.2.0", 55 | "co-mocha": "^1.1.2", 56 | "eslint": "^1.3.1", 57 | "mocha": "^2.2.5", 58 | "nock": "^2.15.0", 59 | "sinon": "^1.15.4", 60 | "sinon-chai": "^2.8.0", 61 | "supertest": "*" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## easy-schedule 2 | 3 | [![Node version](https://img.shields.io/node/v/latest-version.svg?style=flat-square)](https://npmjs.org/package/easy-schedule) 4 | [![Build Status](https://img.shields.io/travis/rafaeljesus/easy-schedule/master.svg?style=flat-square 5 | )](https://travis-ci.org/rafaeljesus/easy-schedule) 6 | [![bitHound Score](https://www.bithound.io/github/rafaeljesus/easy-schedule/badges/score.svg)](https://www.bithound.io/github/rafaeljesus/easy-schedule) 7 | [![bitHound Dependencies](https://www.bithound.io/github/rafaeljesus/easy-schedule/badges/dependencies.svg)](https://www.bithound.io/github/rafaeljesus/easy-schedule/master/dependencies/npm) 8 | [![license](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/rafaeljesus/easy-schedule/blob/master/LICENSE) 9 | 10 | Easy Schedule handles job scheduling for your application by storing and managing events that correspond to actions that your application intends to execute in the future. 11 | At the appropriate time, Easy Schedule calls back to your application at the provided URL to signal that some work must be completed. 12 | 13 | ## Built with 14 | - [nodejs](https://https://nodejs.org) — Back end is a nodejs. 15 | - [koa](http://koajs.com) — API is a KOA app. It respond to requests RESTfully in JSON. 16 | - [Mongodb](https://www.mongodb.com/) — Mongodb as a data store. 17 | 18 | ## REST API 19 | 20 | #### Users 21 | (POST /v1/users) create a user account 22 | 23 | (DELETE /v1/users) delete a user account 24 | 25 | #### Events 26 | (GET /v1/events) list all events from user 27 | 28 | (GET /v1/events/:id) find a event by id 29 | 30 | (POST /v1/events) create a event 31 | 32 | (PUT /v1/events/:id) update a event by id 33 | 34 | (DELETE /v1/events/:id) delete a event by id 35 | 36 | #### History 37 | (GET /v1/history) list all history events from user 38 | 39 | ## Contributing 40 | - Fork it 41 | - Create your feature branch (`git checkout -b my-new-feature`) 42 | - Commit your changes (`git commit -am 'Add some feature'`) 43 | - Push to the branch (`git push origin my-new-feature`) 44 | - Create new Pull Request 45 | 46 | ## Maintaners 47 | 48 | * [Rafael Jesus](https://github.com/rafaeljesus) 49 | 50 | ## License 51 | Easy Schedule is released under the [MIT License](http://www.opensource.org/licenses/MIT). 52 | -------------------------------------------------------------------------------- /test/events/scheduler.spec.js: -------------------------------------------------------------------------------- 1 | import scheduler from 'node-schedule' 2 | import Scheduler from '../../api/events/scheduler' 3 | import Event from '../../api/events/collection' 4 | 5 | describe('Events:SchedulerSpec', () => { 6 | 7 | let scheduleJobSpy 8 | , event 9 | 10 | beforeEach(() => { 11 | event = {_id: 'foo', cron: '* * * * *'} 12 | scheduleJobSpy = sinon.spy(scheduler, 'scheduleJob') 13 | }) 14 | 15 | afterEach(() => { 16 | scheduleJobSpy.restore() 17 | Scheduler.resetScheduledEvents() 18 | }) 19 | 20 | describe('.start', () => { 21 | 22 | let findAllStub 23 | 24 | beforeEach(function* () { 25 | findAllStub = sinon.stub(Event, 'findAll', () => [ 26 | {_id: 'foo'}, 27 | {_id: 'bar'} 28 | ]) 29 | yield Scheduler.start() 30 | }) 31 | 32 | afterEach(() => findAllStub.restore()) 33 | 34 | it('should find all events stored on db', () => { 35 | expect(findAllStub).to.have.been.called 36 | }) 37 | 38 | it('should schedule events', () => { 39 | expect(scheduleJobSpy).to.have.been.calledTwice 40 | }) 41 | 42 | it('should have scheduled two running jobs', () => { 43 | expect(Scheduler.getScheduledEvents()).to.contain.any.keys('foo', 'bar') 44 | }) 45 | }) 46 | 47 | describe('.create', () => { 48 | 49 | beforeEach(function* () { 50 | yield Scheduler.create(event) 51 | }) 52 | 53 | it('should have one scheduled running jobs', () => { 54 | const running = Scheduler.getScheduledEvents() 55 | const len = Object.keys(running).length 56 | expect(len).to.equal(1) 57 | expect(running).to.contain.any.keys('foo') 58 | }) 59 | 60 | it('should schedule new event', () => { 61 | expect(scheduleJobSpy).to.have.been.calledWith(event.cron) 62 | }) 63 | }) 64 | 65 | describe('.update', () => { 66 | 67 | beforeEach(function* () { 68 | yield Scheduler.create(event) 69 | }) 70 | 71 | it('should update scheduled event', function* () { 72 | event.cron = '1 * * * *' 73 | yield Scheduler.update(event._id, event) 74 | expect(scheduleJobSpy).to.have.been.calledWith(event.cron) 75 | }) 76 | 77 | it('should have one scheduled running jobs', () => { 78 | const running = Scheduler.getScheduledEvents() 79 | const len = Object.keys(running).length 80 | expect(len).to.equal(1) 81 | expect(running).to.contain.any.keys('foo') 82 | }) 83 | }) 84 | 85 | describe('.cancel', () => { 86 | 87 | beforeEach(function* () { 88 | yield Scheduler.create(event) 89 | }) 90 | 91 | it('should cancel a scheduled job', function* () { 92 | let res = yield Scheduler.cancel(event._id) 93 | const running = Scheduler.getScheduledEvents() 94 | const len = Object.keys(running).length 95 | expect(res.ok).to.eql(1) 96 | expect(running[event._id]).to.not.exist 97 | expect(len).to.equal(0) 98 | }) 99 | }) 100 | 101 | }) 102 | -------------------------------------------------------------------------------- /test/events/routes.spec.js: -------------------------------------------------------------------------------- 1 | import User from '../../api/users/collection' 2 | import Event from '../../api/events/collection' 3 | 4 | describe('Events:RoutesSpec', () => { 5 | 6 | let fixture = require('./fixture')() 7 | , evt1 = fixture.event1 8 | , evt2 = fixture.event2 9 | , name = 'user-name' 10 | , password = 'user-password' 11 | 12 | beforeEach(function* () { 13 | try { 14 | let user = yield User.create(name, password) 15 | evt1.user = user 16 | evt2.user = user 17 | let res = yield [ 18 | Event.create(evt1), 19 | Event.create(evt2), 20 | ] 21 | evt1 = res[0] 22 | evt2 = res[1] 23 | } catch(err) { 24 | expect(err).to.not.exist 25 | } 26 | }) 27 | 28 | afterEach(function* () { 29 | try { 30 | yield [ 31 | User.cleardb(), 32 | Event.cleardb() 33 | ] 34 | } catch(err) { 35 | expect(err).to.not.exist 36 | } 37 | }) 38 | 39 | it('should respond 401', done => { 40 | request. 41 | get('/events'). 42 | set('Accept', 'application/json'). 43 | set('Accept-Encoding', 'gzip'). 44 | expect('Content-Type', /json/). 45 | expect(401, done) 46 | }) 47 | 48 | describe('GET /v1/events', () => { 49 | it('should find all events', done => { 50 | request. 51 | get('/v1/events'). 52 | auth(name, password). 53 | set('Accept', 'application/json'). 54 | set('Accept-Encoding', 'gzip'). 55 | expect('Content-Type', /json/). 56 | expect(200, done) 57 | }) 58 | }) 59 | 60 | describe('GET /v1/events/:id', () => { 61 | it('should find a event by id', done => { 62 | request. 63 | get('/v1/events/' + evt1._id). 64 | auth(name, password). 65 | set('Accept', 'application/json'). 66 | set('Accept-Encoding', 'gzip'). 67 | expect('Content-Type', /json/). 68 | expect(200, done) 69 | }) 70 | }) 71 | 72 | describe('POST /v1/events', () => { 73 | it('should create a event', done => { 74 | delete evt1._id 75 | const newEvent = evt1 76 | request. 77 | post('/v1/events'). 78 | auth(name, password). 79 | set('Accept', 'application/json'). 80 | set('Accept-Encoding', 'gzip'). 81 | send(newEvent). 82 | expect('Content-Type', /json/). 83 | expect(200, done) 84 | }) 85 | }) 86 | 87 | describe('PUT /v1/events/:id', () => { 88 | it('should update a event', done => { 89 | let _id = evt1._id 90 | delete evt1._id 91 | evt1.url = 'https://github.com/rafaeljesus' 92 | 93 | request. 94 | put('/v1/events/' + _id). 95 | auth(name, password). 96 | send(evt1). 97 | set('Accept', 'application/json'). 98 | set('Accept-Encoding', 'gzip'). 99 | expect('Content-Type', /json/). 100 | expect(200, done) 101 | }) 102 | }) 103 | 104 | describe('DELETE /v1/events/:id', () => { 105 | it('should delete a event', done => { 106 | request. 107 | delete('/v1/events/' + evt1._id). 108 | auth(name, password). 109 | set('Accept', 'application/json'). 110 | set('Accept-Encoding', 'gzip'). 111 | expect('Content-Type', /json/). 112 | expect(200, done) 113 | }) 114 | }) 115 | }) 116 | -------------------------------------------------------------------------------- /test/events/collection.spec.js: -------------------------------------------------------------------------------- 1 | import Event from '../../api/events/collection' 2 | 3 | describe('Events:CollectionSpec', () => { 4 | 5 | afterEach(function* () { 6 | try { 7 | yield Event.cleardb() 8 | } catch(err) { 9 | expect(err).to.exist 10 | } 11 | }) 12 | 13 | describe('.findAll', () => { 14 | 15 | let fixture = require('./fixture')() 16 | , evt1 = fixture.event1 17 | 18 | beforeEach(function* () { 19 | try { 20 | yield Event.create(evt1) 21 | } catch(err) { 22 | expect(err).to.not.be.ok 23 | } 24 | }) 25 | 26 | it('should find all events', function* () { 27 | try { 28 | let res = yield Event.findAll() 29 | expect(res).to.not.be.empty 30 | } catch(err) { 31 | expect(err).to.not.be.ok 32 | } 33 | }) 34 | }) 35 | 36 | describe('.findByUser', () => { 37 | 38 | let fixture = require('./fixture')() 39 | , userFixture = {name: 'foo', _id: 'bar'} 40 | , evt1 = fixture.event1 41 | , evt2 = fixture.event2 42 | 43 | beforeEach(function* () { 44 | evt1.user = userFixture 45 | evt2.user = userFixture 46 | try { 47 | yield [ 48 | Event.create(evt1), 49 | Event.create(evt2) 50 | ] 51 | } catch(err) { 52 | expect(err).to.exist 53 | } 54 | }) 55 | 56 | it('should find events by user', function* () { 57 | try { 58 | let res = yield Event.findByUser(userFixture) 59 | expect(res.length).to.be.equal(2) 60 | expect(res[0]._id).to.not.be.empty 61 | expect(res[1]._id).to.not.be.empty 62 | } catch(err) { 63 | expect(err).to.not.exist 64 | } 65 | }) 66 | }) 67 | 68 | describe('.get', () => { 69 | 70 | let fixture = require('./fixture')() 71 | , evt1 = fixture.event1 72 | 73 | beforeEach(function* () { 74 | try { 75 | evt1 = yield Event.create(evt1) 76 | } catch(err) { 77 | expect(err).to.not.exist 78 | } 79 | }) 80 | 81 | it('should get a event by id', function* () { 82 | try { 83 | let evt = yield Event.findById(evt1._id) 84 | expect(evt.url).to.be.equal(evt1.url) 85 | } catch(err) { 86 | expect(err).to.not.exist 87 | } 88 | }) 89 | }) 90 | 91 | describe('.create', () => { 92 | 93 | let fixture = require('./fixture')() 94 | , evt1 = fixture.event1 95 | 96 | it('should create a event and publish schedule:created', function* () { 97 | try { 98 | evt1 = yield Event.create(evt1) 99 | expect(evt1._id).to.exist 100 | } catch(err) { 101 | expect(err).to.not.exist 102 | } 103 | }) 104 | }) 105 | 106 | describe('.update', function() { 107 | 108 | let fixture = require('./fixture')() 109 | , evt1 = fixture.event1 110 | 111 | beforeEach(function* () { 112 | evt1 = yield Event.create(evt1) 113 | }) 114 | 115 | it('should update a event and publish schedule:updated', function* () { 116 | evt1.url = 'https://example2.com' 117 | try { 118 | let _id = evt1._id 119 | delete evt1._id 120 | let evt = yield Event.update(_id, evt1) 121 | expect(evt.url).to.be.equal(evt1.url) 122 | } catch(err) { 123 | expect(err).to.not.exist 124 | } 125 | }) 126 | }) 127 | 128 | describe('.delete', () => { 129 | 130 | let fixture = require('./fixture')() 131 | , evt1 = fixture.event1 132 | 133 | beforeEach(function* () { 134 | try { 135 | evt1 = yield Event.create(evt1) 136 | } catch(err) { 137 | expect(err).to.not.exist 138 | } 139 | }) 140 | 141 | it('should delete a event and publish schedule:deleted', function* () { 142 | try { 143 | let res = yield Event.remove(evt1._id) 144 | expect(res).to.eql(1) 145 | } catch(err) { 146 | expect(err).to.not.exist 147 | } 148 | }) 149 | }) 150 | 151 | }) 152 | --------------------------------------------------------------------------------