diff --git a/LICENSE.txt b/LICENSE.txt index ef1b55c..a159cd4 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2013 Richard Rodger +Copyright (c) 2013 - 2016 Richard Rodger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 689039c..0f1bdf6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +![Seneca](http://senecajs.org/files/assets/seneca-logo.png) +> [Seneca.js](https://github.com/senecajs/) + + # seneca-examples - Node.js plugin examples for the Seneca toolkit This repository is a collection of simple examples to get you started @@ -13,9 +17,20 @@ example for a fully commented walk through of the example. * [simple-plugin](//github.com/rjrodger/seneca-examples/tree/master/simple-plugin): Create a simple Seneca plugin, including unit tests. * [api-server](//github.com/rjrodger/seneca-examples/tree/master/api-server): building a REST server with Seneca * [plugin-web](//github.com/rjrodger/seneca-examples/tree/master/plugin-web): creating plugins that expose web user interfaces - * [micro-services](github.com/rjrodger/seneca-examples/tree/master/micro-services): create a small micro-services system + * [micro-services](//github.com/rjrodger/seneca-examples/tree/master/micro-services): create a small micro-services system * [user-accounts](//github.com/rjrodger/seneca-examples/tree/master/user-accounts): A user account system, showing login/logout logic. * [shopping-cart](//github.com/rjrodger/seneca-examples/tree/master/shopping-cart): A shopping cart example, showing how plugins expose additional HTTP APIs. +## Contributing + +The [Senecajs org](https://github.com/senecajs/) encourage open and safe participation. +If you feel you can help in any way, be it with documentation, examples, +extra testing, or new features please get in touch. - Before contributing please review [here](http://senecajs.org/contribute/code-of-conduct.html) + + +## License +Copyright Richard Rodger and other contributors 2013 - 2016, Licensed under [MIT](./LICENSE.txt). + + diff --git a/api-server/.eslintrc b/api-server/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/api-server/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/api-server/README.md b/api-server/README.md index a15a283..bcfd464 100644 --- a/api-server/README.md +++ b/api-server/README.md @@ -1,6 +1,28 @@ +## Setup: +``` +npm install +``` + +## Run: +the server with: +``` +npm run start +``` + +the client, in a separate terminal, with: +``` +npm run client +``` -Notes: +Try running the client more than once. Some operations only work the +first time! For example, you can't create a new entity with the same +id as an existing one. Nor can you access a deleted entity. + +You can get a view of the current state of the data by opening: +http://localhost:3000/mem-store/dump + +## Notes: This example provides a simple structure for an API server. The server exposes a product catalog API. The API provides a REST interface, and @@ -18,7 +40,7 @@ some custom endpoints: This is a basic API, and does not handle permissions. However it does require a (hardcoded) token to be accessed. This is enforced using the -_startware_ feature of [seneca-web](github.com/rjrodger/seneca-web) +_startware_ feature of [seneca-web](https://github.com/senecajs/seneca-web). The API is defined by the _api.js_ plugin. This defines a set of action patterns that exist merely to translate HTTP requests into @@ -28,7 +50,7 @@ The translation is done manually for the _/hello_ and _/product/:id/star_ endpoints, using _seneca-web's_ mapping facility. The HTTP REST API for product data is provided by the -[seneca-jsonrest-api](github.com/rjrodger/seneca-jsonrest-api) plugin. +[seneca-jsonrest-api](https://github.com/rjrodger/seneca-jsonrest-api) plugin. The business logic is provided by the plugin _product_catalog.js_. This is a seneca plugin that exposes a single @@ -41,19 +63,5 @@ _product_catalog.js_ in it's own process, and also the data access patterns _role:entity,..._. -Setup: -$ npm install - -Run the server with: -$ node app.js --seneca.log.all -Run the client, in a separate terminal, with: -$ node client.js - -Try running the client more than once. Some operations only work the -first time! For example, you can't create a new entity with the same -id as an existing one. Nor can you access a deleted entity. - -You can get a view of the current state of the data by opening: -http://localhost:3000/mem-store/dump diff --git a/api-server/api.js b/api-server/api.js index 53e9d8e..7118662 100644 --- a/api-server/api.js +++ b/api-server/api.js @@ -1,49 +1,41 @@ // PUBLIC DOMAIN -"use strict"; +'use strict' -module.exports = function api( options ) { +module.exports = function api (options) { var seneca = this - seneca.add('role:api,info:hello', hello) - seneca.add('role:api,product:star', get_star) seneca.add('role:api,product:handle_star', handle_star) - - - seneca.add('init:api',function(args,done){ - + seneca.add('init:api', function (args, done) { // Order is significant here! - - seneca.act('role:web',{use:{ - prefix:'/', - pin:'role:api,info:*', - map:{ - hello:true + seneca.act('role:web', {use: { + prefix: '/', + pin: 'role:api,info:*', + map: { + hello: true } }}) - seneca.use( - {name:'jsonrest-api',tag:'product'}, + {name: 'jsonrest-api', tag: 'product'}, { prefix: '/', - list: {embed:'list'}, - pin: { name:'product' }, + list: {embed: 'list'}, + pin: { name: 'product' }, startware: verify_token, allow_id: true }) - - seneca.act('role:web',{use:{ - prefix:'/product', - pin:'role:api,product:*', + seneca.act('role:web', {use: { + prefix: '/product', + pin: 'role:api,product:*', startware: verify_token, - map:{ - star: { - alias:'/:id/star' + map: { + star: { + alias: '/:id/star' }, - handle_star:{ - PUT:true, - DELETE:true, - alias:'/:id/star' + handle_star: { + PUT: true, + DELETE: true, + alias: '/:id/star' } } }}) @@ -51,66 +43,57 @@ module.exports = function api( options ) { done() }) - var internal_err = { - http$: {status:500}, - why: 'Internal error.' + http$: {status: 500}, + why: 'Internal error.' } var bad_input = { - http$: {status:400}, - why: 'Bad input.' + http$: {status: 400}, + why: 'Bad input.' } - - function verify_token(req,res,next) { + function verify_token (req, res, next) { var token = req.headers['api-access-token'] - - // Hard coded token for this example!!! - if( '1234' == token ) return next(); - - res.writeHead(401,"Access denied.") + // Hard coded token for this example!!! + if('1234' === token) return next() + res.writeHead(401, 'Access denied.') res.end() } - - function hello( args, done ) { - done(null,{msg:'hello!'}) + function hello (args, done) { + done(null, {msg: 'hello!'}) } - function get_star( args, done ) { - this.make$('product').load$(args.id,function(err,res){ - if(err) return done(null,internal_err); - if(!res) return done(null,bad_input); - - return done(null,{ + function get_star (args, done) { + this.make$('product').load$(args.id, function (err, res) { + if(err) return done(null, internal_err) + if(!res) return done(null, bad_input) + return done(null, { product: res.id, - star: res.star + star: res.star }) }) } - function handle_star( args, done ) { + function handle_star (args, done) { this.act( - 'role:product,cmd:star', + 'role:product,cmd:star', { // product id - id:args.id, - + id: args.id, // PUT means star, DELETE means unstar - star:('PUT'===args.req$.method) + star: ('PUT' === args.req$.method) }, - function(err,res){ - if(err) return done(null,internal_err); - if(!res.ok) return done(null,bad_input) - - return done(null,{ - product: res.id, - star: res.star - }) - } - ) + function (err, res) { + if(err) return done(null, internal_err) + if(!res.ok) return done(null, bad_input) + return done(null, { + product: res.id, + star: res.star + }) + } + ) } - } diff --git a/api-server/app.js b/api-server/app.js index 8df455a..5e2b31a 100644 --- a/api-server/app.js +++ b/api-server/app.js @@ -1,16 +1,14 @@ // PUBLIC DOMAIN - var seneca = require('seneca')() .use('./product_catalog') .use('./api') - .ready(function(){ + .ready(function () { this.make$('product') - .make$({id$:0,name:'Apple',price:99,star:0}).save$() - .make$({id$:1,name:'Orange',price:199,star:0}).save$() + .make$({id$: 0, name: 'Apple', price: 99, star: 0}).save$() + .make$({id$: 1, name: 'Orange', price: 199, star: 0}).save$() }) -var app = require('express')() - .use( require('body-parser').json() ) - .use( seneca.export('web') ) +require('express')() + .use(require('body-parser').json()) + .use(seneca.export('web')) .listen(3000) - diff --git a/api-server/client.js b/api-server/client.js index 1622671..5067884 100644 --- a/api-server/client.js +++ b/api-server/client.js @@ -1,51 +1,39 @@ -var util = require('util') +var util = require ('util') -var request = require('request') -var _ = require('lodash') +var Request = require('request') +var _ = require('lodash') var base = 'http://localhost:3000/' -var headers = {'api-access-token':'1234'} - - -;print('get',{url:base+'hello',headers:headers},function(){ - -;print('get',{url:base+'product',headers:headers},function(){ -;print('get',{url:base+'product/0',headers:headers},function(){ -;print('get',{url:base+'product/1',headers:headers},function(){ -;print('get',{url:base+'product/2',headers:headers},function(){ - -;print('post',{url:base+'product',headers:headers,json:{ - id$:2, name:'Pear', price:299 -}},function(){ - -;print('get',{url:base+'product/2',headers:headers},function(){ - -;print('put',{url:base+'product/2',headers:headers,json:{ - name:'Pear', price:Math.floor(300*Math.random()) -}},function(){ - -;print('get',{url:base+'product/2',headers:headers},function(){ - -;print('del',{url:base+'product/1',headers:headers},function(){ -;print('get',{url:base+'product/1',headers:headers},function(){ - -;print('get',{url:base+'product/0/star',headers:headers},function(){ -;print('put',{url:base+'product/0/star',headers:headers},function(){ -;print('get',{url:base+'product/0/star',headers:headers},function(){ - -})})})})})})})})})})})})})}) - - - -// utility function to pretty print request results -function print() { +var headers = {'api-access-token': '1234'} + + +;print('get', {url: base + 'hello', headers: headers}, function () { + ;print('get', {url: base + 'product', headers: headers}, function () { + ;print('get', {url: base + 'product/0', headers: headers}, function () { + ;print('get', {url: base + 'product/1', headers: headers}, function () { + ;print('get', {url: base + 'product/2', headers: headers}, function () { + ;print('post', {url: base + 'product', headers: headers, json: { + id$: 2, name: 'Pear', price: 299 + }}, function () { + ;print('get', {url: base + 'product/2', headers: headers}, function () { + ;print('put', {url: base + 'product/2', headers: headers, json: { + name: 'Pear', price: Math.floor(300 * Math.random()) + }}, function () { + ;print('get', {url: base + 'product/2', headers: headers}, function () { + ;print('del', {url: base + 'product/1', headers: headers}, function () { + ;print('get', {url: base + 'product/1', headers: headers}, function () { + ;print('get', {url: base + 'product/0/star', headers: headers}, function () { + ;print('put', {url: base + 'product/0/star', headers: headers}, function () { + ;print('get', {url: base + 'product/0/star', headers: headers}, function () { + }) }) }) }) }) }) }) }) }) }) }) }) }) }) + + // utility function to pretty print request results +function print () { var a = Array.prototype.slice.call(arguments) var mstr = a[0] - - var n = _.isFunction( a[a.length-1] ) ? a.pop() : null - - request[mstr].apply(request, a.slice(1).concat(function(err,res,body){ - console.log(mstr,res.req.path,res.statusCode,err||body) + var n = _.isFunction(a[ a.length - 1 ]) ? a.pop() : null + Request[mstr].apply(Request, a.slice(1).concat(function (err, res, body) { + console.log(mstr, res.req.path, res.statusCode, err || body) !err && n && n() })) } diff --git a/api-server/package.json b/api-server/package.json index 73cba08..9a46937 100644 --- a/api-server/package.json +++ b/api-server/package.json @@ -7,12 +7,22 @@ "repository": "", "author": "Richard Rodger", "license": "MIT", + "scripts": { + "start": "node app.js --seneca.log.all", + "client": "node client.js" + }, "dependencies": { - "body-parser": "~1.11.0", - "express": "~4.11.1", - "lodash": "~3.0.1", - "request": "~2.51.0", + "body-parser": "~1.15.1", + "express": "~4.13.4", + "lodash": "~4.13.1", + "request": "~2.72.0", "seneca": "plugin", + "seneca-entity": "0.1.1", "seneca-jsonrest-api": "~0.3.1" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/api-server/product_catalog.js b/api-server/product_catalog.js index 07a922a..63b0c0c 100644 --- a/api-server/product_catalog.js +++ b/api-server/product_catalog.js @@ -1,37 +1,31 @@ // PUBLIC DOMAIN -"use strict"; +'use strict' -module.exports = function product_catalog( options ) { +module.exports = function product_catalog (options) { var seneca = this - seneca.add('role:product,cmd:star', cmd_star) - - - function cmd_star( args, done ) { + function cmd_star (args, done) { var seneca = this - seneca .make$('product') - .load$(args.id,function( err, product ) { - if(err) return done(err); - - if( product ) { - product.star = Math.max(0,( - (product.star||0) + - ((args.star||true) ? +1 : -1 ))) - - product.save$(function(err,product){ - if(err) return done(err); - - return done(null,{ - ok: true, - id: product.id, + .load$(args.id, function (err, product) { + if(err) return done(err) + if(product) { + product.star = Math.max(0, ( + (product.star || 0) + + ((args.star && true) ? +1 : -1))) + + product.save$(function (err, product) { + if(err) return done(err) + + return done(null, { + ok: true, + id: product.id, star: product.star }) }) } - else return done(null,{ok:false}) + else return done(null, {ok: false}) }) } - } diff --git a/api-server/seneca.options.js b/api-server/seneca.options.js index 2bcdf80..9667d31 100644 --- a/api-server/seneca.options.js +++ b/api-server/seneca.options.js @@ -1,4 +1,4 @@ module.exports = { - 'mem-store': { web: { dump:true } }, - web: {debug: { service:true } }, + 'mem-store': {web: {dump: true}}, + 'web': {debug: {service: true}} } diff --git a/data-entities/.eslintrc b/data-entities/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/data-entities/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/data-entities/README.md b/data-entities/README.md index 9171e1f..01b1b74 100644 --- a/data-entities/README.md +++ b/data-entities/README.md @@ -1,9 +1,29 @@ -For an introduction to Seneca data entities, please read the -[senecajs.org tutorial](http://senecajs.org/data-entities.html). +## Setup: +``` +npm install +``` + +* Note: seneca-level-store compiles the level modules, as they are +native. If this does not work on your platform (e.g. Windows), just +comment out the level-store code. + + +## Run: +``` +npm run build +``` +``` +npm run start +``` -Notes: +For detailed logging, try: +``` +npm run details +``` + +## Notes: The code in main.js shows you how to save data entities to different places. The two jsonfile-store and single level-store data store @@ -22,18 +42,11 @@ on disk. The data is also loaded independently of Seneca to verify that it actually has been persisted! The entity.native$ method is used to do this, as it exposes the underlying database API. +For an introduction to Seneca data entities, please read the +[senecajs.org tutorial](http://senecajs.org/tutorials/understanding-data-entities.html). -Setup: -$ npm install -Note: seneca-level-store compiles the level modules, as they are -native. If this does not work on your platform (e.g. Windows), just -comment out the level-store code. -Run with: -$ node main.js -For detailed logging, try: -$ node main.js --seneca.log.all diff --git a/data-entities/main.js b/data-entities/main.js index d92e508..7993933 100644 --- a/data-entities/main.js +++ b/data-entities/main.js @@ -1,68 +1,68 @@ -/* Copyright (c) 2013-2015 Richard Rodger, MIT License */ -"use strict"; - -var fs = require('fs') -var util = require('util') +/* Copyright (c) 2013-2016 Richard Rodger, MIT License */ +'use strict' +var Fs = require('fs') +var Util = require('util') var seneca = require('seneca')() -// Use two separate instances of jsonfile-store, each looking after a -// different set of data entities -seneca.use( 'jsonfile-store$foo', { folder:'foo-data', map:{'-/foo/-':'*'}}) -seneca.use( 'jsonfile-store$bar', { folder:'bar-data', map:{'-/bar/-':'*'}}) - +/* Use two separate instances of jsonfile-store, each looking after a + different set of data entities */ +seneca.use('jsonfile-store$foo', {folder: 'foo-data', map: {'-/foo/-': '*'}}) +seneca.use('jsonfile-store$bar', {folder: 'bar-data', map: {'-/bar/-': '*'}}) +if (seneca.version >= '2.0.0') { + seneca.use('entity') +} // Use a different data store for another set of data entities -seneca.use( 'level-store', { folder:'zed-data', map:{'-/zed/-':'*'}}) +seneca.use('level-store', {folder: 'zed-data', map: {'-/zed/-': '*'}}) // Everything else goes into the default mem-store seneca - .make$('foo/red',{a:1}) - .save$(function(err,foo_red){ - if(err) return console.error('foo_red',err); - var fc = fs.readFileSync(__dirname+'/foo-data/foo_red/'+foo_red.id+'.json') - console.log('entity:'+foo_red+', file contents:'+fc) - do_bar_green(this) - }) +.make$('foo/red', {a: 1}) +.save$(function (err, foo_red) { + if(err) return console.error('foo_red', err) + var fc = Fs.readFileSync(__dirname + '/foo-data/foo_red/' + foo_red.id + '.json') + console.log('entity:' + foo_red + ', file contents:' + fc) + do_bar_green(this) +}) -function do_bar_green(seneca) { +function do_bar_green (seneca) { seneca - .make$('bar/green',{b:2}) - .save$(function(err,bar_green){ - if(err) return console.error('bar_green',err); - var fc = fs.readFileSync(__dirname+'/bar-data/bar_green/'+ - bar_green.id+'.json') - console.log('entity:'+bar_green+', file contents:'+fc) - do_zed_blue(seneca) - }) + .make$('bar/green', {b: 2}) + .save$(function (err, bar_green) { + if(err) return console.error('bar_green', err) + var fc = Fs.readFileSync(__dirname + '/bar-data/bar_green/' + + bar_green.id + '.json') + console.log('entity:' + bar_green + ', file contents:' + fc) + do_zed_blue(seneca) + }) } -function do_zed_blue(seneca) { +function do_zed_blue (seneca) { seneca - .make$('zed/blue',{c:3}) - .save$(function(err,zed_blue){ - if(err) return console.error('zed_blue',err); - zed_blue.native$(function(err,level){ - if(err) return console.error('zed_blue.native$',err); - level.get(zed_blue.id,function(err,data){ - console.log('entity:'+zed_blue+' level data:'+util.inspect(data)) - do_qaz_yellow(seneca) - }) + .make$('zed/blue', {c: 3}) + .save$(function (err, zed_blue) { + if(err) return console.error('zed_blue', err) + zed_blue.native$(function (err, level) { + if(err) return console.error('zed_blue.native$', err) + level.get(zed_blue.id, function (err, data) { + if(err) return console.error('zed_blue.id', err) + console.log('entity:' + zed_blue + ' level data:' + Util.inspect(data)) + do_qaz_yellow(seneca) }) }) + }) } -function do_qaz_yellow(seneca) { +function do_qaz_yellow (seneca) { seneca - .make$('qaz/yellow',{d:4}) - .save$(function(err,qaz_yellow){ - if(err) return console.error('qaz_yellow',err); - qaz_yellow.native$(function(err,entmap){ - if(err) return console.error('qaz_yellow.native$',err); - var dc = entmap.qaz.yellow[qaz_yellow.id] - console.log('entity:'+qaz_yellow+ - ', data contents:'+util.inspect(dc)) - }) - }) + .make$('qaz/yellow', {d: 4}) + .save$(function (err, qaz_yellow) { + if(err) return console.error('qaz_yellow', err) + qaz_yellow.native$(function (err, entmap) { + if(err) return console.error('qaz_yellow.native$', err) + var dc = entmap.qaz.yellow[qaz_yellow.id] + console.log('entity:' + qaz_yellow + ', data contents:' + Util.inspect(dc)) + }) + }) } - diff --git a/data-entities/package.json b/data-entities/package.json index 21c3c94..68cbd69 100644 --- a/data-entities/package.json +++ b/data-entities/package.json @@ -7,9 +7,20 @@ "repository": "", "author": "Richard Rodger", "license": "MIT", + "scripts": { + "start": "node main.js ", + "details": "node main.js --seneca.log.all", + "build": "mkdir bar-data foo-data zed-data" + }, "dependencies": { - "seneca": "plugin", - "seneca-level-store": "~0.2.1", - "seneca-jsonfile-store": "~0.2.2" + "seneca": "2.1.0", + "seneca-entity": "0.1.1", + "seneca-jsonfile-store": "~0.2.2", + "seneca-level-store": "~0.2.1" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/micro-services/.eslintrc b/micro-services/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/micro-services/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/micro-services/README.txt b/micro-services/README.md similarity index 75% rename from micro-services/README.txt rename to micro-services/README.md index a86017a..43fddef 100644 --- a/micro-services/README.txt +++ b/micro-services/README.md @@ -1,4 +1,10 @@ +## Setup: +``` +npm install +``` + +## Run: Runs the following services: * offer-service @@ -6,9 +12,15 @@ Runs the following services: * web-app You can run these individually in separate terminals with: - -$ node services/offer-service.js -... etc +``` +node services/offer-service.js +``` +``` +node services/user-details.js +``` +``` +node services/web-app.js +``` Optionally use --seneca.log.all to see the debug logs, or --seneca.log=type:act just to see the actions @@ -17,10 +29,10 @@ In the debug logs, the first field is the local system time, and the second field is a unique identifer for the seneca instance. Use this to observe how messages flow in the system. - For convenience, you can also run everything with: - -$ node index.js +``` +npm run start +``` This always prints merged debug logs for all services. The individual logs are saved to the log folder. @@ -34,8 +46,15 @@ The implementations of the seneca plugins used by the services are in lib/*.js. Notice that the user-details service is just pulling out the standard seneca-user plugin implementation into a separate service. -Open http://localhost:3000 to see the services in action. -Login with username:u1, password:u2 +To see the services in action open: +``` +http://localhost:3000 +``` + +* Login with: +``` +username:user, password:user +``` The offered product depends on your login status. @@ -44,8 +63,8 @@ The user-details delivers user login and logout. The offer-service delivers product offers based on the visitor being identified (i.e. logged in) or not. -This example is a vastly simplified version of Fred George's talk: -http://oredev.org/2013/wed-fri-conference/implementing-micro-service-architectures +This example is a vastly simplified version of Fred George's talk [implementing-micro-service-architectures] +(http://oredev.org/oredev2013/2013/wed-fri-conference/implementing-micro-service-architectures.html) diff --git a/micro-services/index.js b/micro-services/index.js index c3698bd..9223725 100644 --- a/micro-services/index.js +++ b/micro-services/index.js @@ -2,11 +2,11 @@ var fs = require('fs') var spawn = require('child_process').spawn -var services = ['web-app','user-details','offer-service'] +var services = ['web-app', 'user-details', 'offer-service'] -services.forEach(function(service){ - var log = fs.createWriteStream('./log/'+service+'.log') - var proc = spawn('node', ['./services/'+service+'.js','--seneca.log.all']) +services.forEach(function (service) { + var log = fs.createWriteStream('./log/' + service + '.log') + var proc = spawn('node', ['./services/' + service + '.js', ' --seneca.log.all']) proc.stdout.pipe(log) proc.stderr.pipe(log) @@ -14,6 +14,3 @@ services.forEach(function(service){ proc.stdout.pipe(process.stdout) proc.stderr.pipe(process.stderr) }) - - - diff --git a/micro-services/lib/api.js b/micro-services/lib/api.js index bcd51f4..dbd3649 100644 --- a/micro-services/lib/api.js +++ b/micro-services/lib/api.js @@ -3,25 +3,21 @@ module.exports = function( options ) { var seneca = this var plugin = 'api' + seneca.add({role: plugin, end: 'offer'}, end_offer) - seneca.add( {role:plugin, end:'offer'}, end_offer) - - - function end_offer( args, done ) { + function end_offer (args, done) { var user = args.req$.seneca.user || {} - this.act('role:offer,cmd:provide',{nick:user.nick},done) + this.act('role:offer,cmd:provide', {nick: user.nick}, done) } - - - seneca.act({role:'web', use:{ - prefix:'/api/', - pin:{role:plugin,end:'*'}, - map:{ - 'offer': { GET:true }, + seneca.act({role: 'web', use: { + prefix: '/api/', + pin: {role: plugin, end: '*'}, + map: { + 'offer': { GET: true } } }}) - return {name:plugin}; + return {name: plugin} } diff --git a/micro-services/lib/offer.js b/micro-services/lib/offer.js index e19aac6..1733a30 100644 --- a/micro-services/lib/offer.js +++ b/micro-services/lib/offer.js @@ -3,16 +3,11 @@ module.exports = function( options ) { var seneca = this var plugin = 'offer' + seneca.add({role: plugin, cmd: 'provide'}, cmd_provide) - seneca.add( {role:plugin, cmd:'provide'}, cmd_provide) - - - function cmd_provide( args, done ) { - if( args.nick ) return done(null,{product:'Apple'}); - - return done(null,{product:'Orange'}); + function cmd_provide (args, done) { + if (args.nick) return done(null, {product: 'Apple'}) + return done(null, {product: 'Orange'}) } - - - return {name:plugin}; + return {name: plugin} } diff --git a/micro-services/package.json b/micro-services/package.json index f960049..e89064a 100644 --- a/micro-services/package.json +++ b/micro-services/package.json @@ -3,16 +3,23 @@ "version": "0.1.0", "description": "Seneca micro-services example", "main": "index.js", + "author": "Richard Rodger", + "license": "MIT", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js" }, - "author": "", - "license": "MIT", "dependencies": { - "body-parser": "~1.11.0", - "express": "~4.11.2", + "body-parser": "~1.15.1", + "express": "~4.13.4", "seneca": "plugin", - "seneca-auth": "~0.4.0", - "seneca-user": "~0.2.10" + "seneca-entity": "0.1.1", + "seneca-auth": "~1.0.1", + "seneca-user": "~1.0.2" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/micro-services/public/index.html b/micro-services/public/index.html index cb22661..cdaf956 100644 --- a/micro-services/public/index.html +++ b/micro-services/public/index.html @@ -1,55 +1,34 @@ - - + + - - - -

Micro-Services

- - -

Hi There!

-

Have an !

- - -
- -

Login

-
- -

-
- -

- -

-
- -

- -

- -

-
- -
- - - - - - + +

Micro-Services

+

Hi There!

+

Have an !

+
+

Login

+
+

+
+ +

+

+
+ +

+

+ +

+
+
+ diff --git a/micro-services/public/index.js b/micro-services/public/index.js index 7bec0f9..4790ba5 100644 --- a/micro-services/public/index.js +++ b/micro-services/public/index.js @@ -1,31 +1,31 @@ $(function(){ - $('#login').submit(function(){ + $('#login').submit(function() { var data = { username: $('#username').val(), password: $('#password').val() } $.ajax({ - type: 'POST', - url: '/auth/login', - data: JSON.stringify(data), - dataType: 'json', + type: 'POST', + url: '/auth/login', + data: JSON.stringify(data), + dataType: 'json', contentType: 'application/json', - success: showAccount + success: showAccount }) - + console.log('DATA:', data) return false }) $('#logout').click(function(){ $.ajax({ - type: 'POST', - url: '/auth/logout', - data: '{}', - dataType: 'json', + type: 'POST', + url: '/auth/logout', + data: '{}', + dataType: 'json', contentType: 'application/json', - success: showLogin + success: showLogin }) }) @@ -34,21 +34,19 @@ $(function(){ $.ajax({type:'GET',url:'/api/offer',success:showOffer}) }) - -function showAccount(instance) { - if( instance.user ) { +function showAccount (instance) { + if(instance.user) { $('#user_nick').text(instance.user.nick) $('#user_name').text(instance.user.name) $('#content_login').slideUp() $('#content_account').slideDown() - $.ajax({type:'GET',url:'/api/offer',success:showOffer}) } } -function showLogin(instance) { - if( instance.user ) return showAccount(instance) +function showLogin (instance) { + if(instance.user) return showAccount(instance) $('#content_login').slideDown() $('#content_account').slideUp() @@ -57,6 +55,6 @@ function showLogin(instance) { } -function showOffer(offer) { +function showOffer (offer) { $('#offer').text(offer.product) } diff --git a/micro-services/services/offer-service.js b/micro-services/services/offer-service.js index 697e6cb..cd88b1f 100644 --- a/micro-services/services/offer-service.js +++ b/micro-services/services/offer-service.js @@ -1,9 +1,6 @@ -require('seneca')() +require('seneca') () .use('../lib/offer') .listen(10202) - .ready(function(){ - this.act({role:'offer',cmd:'provide'},console.log) + .ready(function () { + this.act({role: 'offer', cmd: 'provide'}, console.log) }) - - - diff --git a/micro-services/services/user-details.js b/micro-services/services/user-details.js index 2751f23..baf81fa 100644 --- a/micro-services/services/user-details.js +++ b/micro-services/services/user-details.js @@ -2,8 +2,6 @@ require('seneca')() .use('user') .listen(10201) - .ready(function(){ - this.act({role:'user',cmd:'register',nick:'u1',name:'U1',password:'u1'}) + .ready(function () { + this.act({role: 'user', cmd: 'register', nick: 'user', name: 'user', password: 'user'}) }) - - diff --git a/micro-services/services/web-app.js b/micro-services/services/web-app.js index 2002d88..22f8e3c 100644 --- a/micro-services/services/web-app.js +++ b/micro-services/services/web-app.js @@ -1,21 +1,18 @@ -var express = require('express') -var bodyParser = require('body-parser') -var seneca = require('seneca')() - +var Express = require('express') +var BodyParser = require('body-parser') +var seneca = require('seneca')() seneca .use('user') .use('auth') .use('../lib/api.js') - .client({port:10202,pin:{role:'offer',cmd:'*'}}) - .client({port:10201,pin:{role:'user',cmd:'*'}}) - -var app = express() + .client({port: 10202, pin: {role: 'offer', cmd: '*'}}) + .client({port: 10201, pin: {role: 'user', cmd: '*'}}) -app.use( bodyParser.json() ) -app.use( seneca.export('web') ) -app.use( express.static('./public') ) +var app = Express() +app.use(BodyParser.json()) +app.use(seneca.export('web')) +app.use(Express.static('./public')) app.listen(3000) - diff --git a/plugin-web/.eslintrc b/plugin-web/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/plugin-web/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/plugin-web/README.txt b/plugin-web/README.md similarity index 88% rename from plugin-web/README.txt rename to plugin-web/README.md index 621088d..b0cdf90 100644 --- a/plugin-web/README.txt +++ b/plugin-web/README.md @@ -1,5 +1,21 @@ -Notes: +## Setup: +``` +npm install +``` + +## Run: + +``` +npm run start +``` + +and then visit: +``` +http://localhost:3000 +``` + +## Notes: The code in app.js shows you how to configure an express server using custom seneca plugins for static page rendering, api communication, and @@ -9,13 +25,13 @@ express app via app.use( seneca.export('web') ). Each of the three plugins is housed in its own directory. Seneca plugins export a function with a signature - + function myPlugin( options ) { ... } where options are passed from the call to seneca.use. Return a description object to give your plugin an explicit name. For example: - return { name:'foo' } - + return { name:'foo' } + When working with middleware plugins, use the role:web,use:[Function] pattern to specify a middle function, acception the usual request, response, and next arguments. @@ -30,27 +46,16 @@ as middleware. The api-plugin uses Express-like capabilities to map a RESTful API call to an http response, using the seneca-web plugin. -For more questions about creating your own custom plugins, check out -http://senecajs.org/api.html#long-m-use. - To learn more about the seneca-web plugin, visit https://github.com/rjrodger/seneca-web. -Feel free to contact me on Twitter if you have any questions! :) @rjrodger - - - -Setup: - -npm install +To learn more about plugins, visit : +http://senecajs.org/plugins/#core-plugins +Feel free to contact me on Twitter if you have any questions! :) @rjrodger -Run with: -node app.js -and then visit: -http://localhost:3000 diff --git a/plugin-web/api-plugin/index.js b/plugin-web/api-plugin/index.js index ebeca56..152efcc 100644 --- a/plugin-web/api-plugin/index.js +++ b/plugin-web/api-plugin/index.js @@ -1,26 +1,22 @@ /* Copyright (c) 2010-2014 Richard Rodger, MIT License */ -"use strict"; - - - -module.exports = function( options ) { +'use strict' +module.exports = function (options) { // you need a middleware function to look for a matching URL // export('web/httprouter') returns a router with Express-like capabilities var router = this.export('web/httprouter') - this.act('role:web',{use:router(function(app){ - app.get("/api/:action",function(req,res){ - + this.act('role:web', {use: router(function (app) { + app.get('/api/:action', function (req, res) { // respond manually - res.writeHead(200,{ + res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end( JSON.stringify({action:req.params.action}) ) + res.end(JSON.stringify({action: req.params.action})) }) })}) // register this plugin with seneca - return { name:'api' } + return { name: 'api' } } diff --git a/plugin-web/app.js b/plugin-web/app.js index 4f6836d..38d7356 100644 --- a/plugin-web/app.js +++ b/plugin-web/app.js @@ -1,25 +1,22 @@ /* Copyright (c) 2013-2015 Richard Rodger, MIT License */ -"use strict"; - +'use strict' // use the http://expressjs.com web framework -var express = require('express') -var bodyParser = require('body-parser') -var cookieParser = require('cookie-parser') -var methodOverride = require('method-override') +var Express = require('express') +var BodyParser = require('body-parser') +var CookieParser = require('cookie-parser') +var MethodOverride = require('method-override') // use https://github.com/substack/node-optimist for parsing the command line var argv = require('optimist').argv - // setup the configuration var conf = { port: argv.p || 3000 } - // create a seneca instance -var seneca = require('seneca')() +var seneca = require('seneca')() // use the example plugins // they are all sub folders @@ -27,28 +24,20 @@ seneca.use('./page-plugin') seneca.use('./api-plugin') seneca.use('./quick-plugin') - - - - // set up express -var app = express() -app.use(cookieParser()) -app.use(express.query()) -app.use(bodyParser.urlencoded({extended: true})) -app.use(methodOverride()) -app.use(bodyParser.json()) +var app = Express() +app.use(CookieParser()) +app.use(Express.query()) +app.use(BodyParser.urlencoded({extended: true})) +app.use(MethodOverride()) +app.use(BodyParser.json()) // this is the top level static content -app.use(express.static(__dirname + '/public')) +app.use(Express.static(__dirname + '/public')) // add in the seneca middleware // this is how the seneca plugins can respond to HTTP requests -app.use( seneca.export('web') ) - +app.use(seneca.export('web')) // start the app! app.listen(conf.port) - - - diff --git a/plugin-web/package.json b/plugin-web/package.json index 285d967..c174677 100644 --- a/plugin-web/package.json +++ b/plugin-web/package.json @@ -4,12 +4,12 @@ "description": "Plugin web example", "subdomain": "plugin-web-seneca-example", "main": "app.js", - "scripts": { - "start": "app.js" - }, "repository": "", "author": "Richard Rodger", "license": "MIT", + "scripts": { + "start": "node app.js" + }, "dependencies": { "express": "~4.11.2", "body-parser": "~1.11.0", @@ -19,5 +19,10 @@ "seneca": "plugin", "connect": "~3.3.4", "serve-static": "~1.8.1" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/plugin-web/page-plugin/index.js b/plugin-web/page-plugin/index.js index be0cc89..2fd5380 100644 --- a/plugin-web/page-plugin/index.js +++ b/plugin-web/page-plugin/index.js @@ -1,50 +1,41 @@ /* Copyright (c) 2010-2013 Richard Rodger, MIT License */ -"use strict"; +'use strict' // use the connect module to deliver the static pages -var connect = require('connect') -var serveStatic = require('serve-static') +var Connect = require('connect') +var ServeStatic = require('serve-static') -module.exports = function( options ) { - - // setup the options - // by default, place this content under the /page URL - // deepextend is a utility function that works like underscore.extend, - // but is careful to recuresively extend also the internal structure of - // properties that have objects as values. +module.exports = function (options) { + /* setup the options + by default, place this content under the /page URL + deepextend is a utility function that works like underscore.extend, + but is careful to recuresively extend also the internal structure of + properties that have objects as values.*/ options = this.util.deepextend({ - prefix:'/page' - },options) + prefix: '/page' + }, options) // create a new connect app - this is separate from the main one in app.js - var app = connect() + var app = Connect() // just serve static content from the web folder - app.use(serveStatic(__dirname+'/web')) - - + app.use(ServeStatic(__dirname + '/web')) // you need a middleware function to trigger the local connect app - this.act('role:web',{use:function(req,res,next) { - + this.act('role:web', {use: function (req, res, next) { // look for that URL prefix, by default /page - if( 0 == req.url.indexOf(options.prefix) ) { - + if(0 === req.url.indexOf(options.prefix)) { // remove the prefix to avoid confusing the local connect app req.url = req.url.substring(options.prefix.length) - // and delegate the work to the local connect app - app( req, res, next ) + app(req, res, next) } - // else this request is nothing to do with us! else next() }}) - - // register this plugin with seneca - return { name:'page' }; + return { name: 'page' } } diff --git a/plugin-web/quick-plugin/index.js b/plugin-web/quick-plugin/index.js index d610413..c61565d 100644 --- a/plugin-web/quick-plugin/index.js +++ b/plugin-web/quick-plugin/index.js @@ -1,18 +1,13 @@ /* Copyright (c) 2010-2014 Richard Rodger, MIT License */ -"use strict"; - - - -module.exports = function( options ) { +'use strict' +module.exports = function (options) { // you need a middleware function to look for a matching URL - this.act('role:web',{use:function(req,res,next) { - + this.act('role:web', {use: function (req, res, next) { // look for a URL - if( 0 == req.url.indexOf('/quick') ) { - + if(0 === req.url.indexOf('/quick')) { // respond manually - res.writeHead(200,{ + res.writeHead(200, { 'Content-Type': 'application/json' }) res.end('{"data":"quick response!"}') @@ -24,6 +19,5 @@ module.exports = function( options ) { // register this plugin with seneca - return { name:'quick' } - + return { name: 'quick' } } diff --git a/shopping-cart/.eslintrc b/shopping-cart/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/shopping-cart/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/shopping-cart/README.txt b/shopping-cart/README.md similarity index 91% rename from shopping-cart/README.txt rename to shopping-cart/README.md index 577bab7..3e8d794 100644 --- a/shopping-cart/README.txt +++ b/shopping-cart/README.md @@ -1,5 +1,29 @@ -Notes: +## Setup: +``` + npm install +``` + +## Run: +``` +npm run start +``` + +and then visit: +``` +http://localhost:3000 +``` + +Access the admin panel on: +``` +http://localhost:3000/admin +``` +For debug db contents go to : +``` +http://localhost:3000/mem-store/dump +``` + +## Notes: This example shows the use of: @@ -42,15 +66,4 @@ Feel free to contact me on Twitter if you have any questions! :) @rjrodger -Run with: - -node app.js -p 3000 - -and then visit: -http://localhost:3000 - - -Access the admin panel on: -http://localhost:3000/admin - diff --git a/shopping-cart/app.js b/shopping-cart/app.js index f4995f6..4212209 100644 --- a/shopping-cart/app.js +++ b/shopping-cart/app.js @@ -1,33 +1,29 @@ /* Copyright (c) 2013-2015 Richard Rodger, MIT License */ -"use strict"; - - -var http = require('http') - -var express = require('express') -var bodyParser = require('body-parser') -var cookieParser = require('cookie-parser') -var methodOverride = require('method-override') -var session = require('express-session') -var serveStatic = require('serve-static') -var argv = require('optimist').argv +'use strict' +var Http = require('http') +var Express = require('express') +var BodyParser = require('body-parser') +var CookieParser = require('cookie-parser') +var MethodOverride = require('method-override') +var ServeStatic = require('serve-static') +var Argv = require('optimist').argv var conf = { - port: argv.p || 3000 + port: Argv.p || 3000 } - // create a seneca instance -var seneca = require('seneca')() - +var seneca = require('seneca')() // enable the /mem-store/dump HTTP end point // this lets you see the entire contents of the database as a JSON object // in the browser - very useful for debugging! -// Go to http://localhost:3333/mem-store/dump to debug db contents -seneca.use('mem-store',{web:{dump:true}}) - +// Go to http://localhost:3000/mem-store/dump to debug db contents +if (seneca.version >= '2.0.0') { + seneca.use('entity') +} +seneca.use('mem-store', {web: {dump: true}}) // use the engage plugin to store extended user sessions // these are known as "engagements" @@ -35,30 +31,21 @@ seneca.use('mem-store',{web:{dump:true}}) // return the next day seneca.use('engage') - - // the shopping cart plugin provides standard web shopping cart business logic // and also a HTTP JSON api seneca.use('cart') - - - - - // set up express -var app = express() +var app = Express() app.enable('trust proxy') -app.use(cookieParser()) -app.use(express.query()) -app.use(bodyParser.urlencoded({extended: true})) -app.use(methodOverride()) -app.use(bodyParser.json()) - -app.use(serveStatic(__dirname + '/public')) - +app.use(CookieParser()) +app.use(Express.query()) +app.use(BodyParser.urlencoded({extended: true})) +app.use(MethodOverride()) +app.use(BodyParser.json()) +app.use(ServeStatic(__dirname + '/public')) // expose the shopping cart api // the seneca.export('web') method returns a single function with the signature @@ -66,94 +53,75 @@ app.use(serveStatic(__dirname + '/public')) // this service method wraps up all the plugin HTTP endpoints // seneca includes the connect utility plugin by default, which // sets the special arguments req$ and res$ on all seneca calls, allowing -// seneca actions to access the current HTTP req and res objects -app.use( seneca.export('web') ) - +// seneca actions to access the current HTTP req and res objects +app.use(seneca.export('web')) // express views for the cart pages -app.engine('ejs',require('ejs-locals')) +app.engine('ejs', require('ejs-locals')) app.set('views', __dirname + '/views') -app.set('view engine','ejs') - - +app.set('view engine', 'ejs') // a utility method -function formatprice(price) { - return '$' + (void 0 == price ? '0.00' : price.toFixed(2)) +function formatprice (price) { + return '$' + (void 0 === price ? '0.00' : price.toFixed(2)) } - -app.get('/', function(req,res,next) { - req.seneca.act('role:cart,cmd:get',function(err,out) { - if( err ) return next(err); - res.render('index.ejs',{locals:{cart:out.cart,formatprice:formatprice}}) +app.get('/', function (req, res, next) { + req.seneca.act('role:cart,cmd:get', function (err, out) { + if (err) return next(err) + res.render('index.ejs', {locals: {cart: out.cart, formatprice: formatprice}}) }) }) - -app.get('/cart', function(req,res,next){ - req.seneca.act('role:cart,cmd:get',function(err,out) { - if( err ) return next(err); - res.render('cart.ejs',{locals:{cart:out.cart,formatprice:formatprice}}) +app.get('/cart', function (req, res, next) { + req.seneca.act('role:cart,cmd:get', function (err, out) { + if(err) return next(err) + res.render('cart.ejs', {locals: {cart: out.cart, formatprice: formatprice}}) }) }) - -app.get('/checkout', function(req,res,next){ - req.seneca.act('role:cart,cmd:get',function(err,out) { - if( err ) return next(err); - res.render('checkout.ejs',{locals:{cart:out.cart,formatprice:formatprice}}) +app.get('/checkout', function (req, res, next) { + req.seneca.act('role:cart,cmd:get', function (err, out) { + if(err) return next(err) + res.render('checkout.ejs', {locals: {cart: out.cart, formatprice: formatprice}}) }) }) - // Use seneca.ready to ensure all plugins fully ready // before we extend action patterns -seneca.ready(function(){ - +seneca.ready(function () { // ensure that cart actions get the cart from the engagement - seneca.wrap({role:'cart',cmd:'*'},function(args,done){ - var seneca = this - var prior = this.prior - - if( !args.req$ ) return this.prior( args, done ); - - this.act('role:engage,cmd:get,key:cart',function(err,out) { - if( err ) return done(err); - + seneca.wrap({role: 'cart', cmd: '*'}, function (args, done) { + var prior = this.prior + if(!args.req$) return this.prior(args, done) + this.act('role:engage,cmd:get,key:cart', function (err, out) { + if(err) return done(err) args.cart = out.value - prior( args, function(err,out) { - if( err ) return done(err); - - this.act('role:engage,cmd:set,key:cart',{value:out.cart.id},function(err){ - if( err ) return done(err); - - done(null,out) + prior(args, function (err, out) { + if(err) return done(err) + this.act('role:engage,cmd:set,key:cart', {value: out.cart.id}, function (err) { + if(err) return done(err) + done(null, out) }) }) }) }) }) - - // use the node.js http api to create a HTTP server // this allows the admin plugin to use websockets -var server = http.createServer(app) +var server = Http.createServer(app) server.listen(conf.port) // unlike the user-accounts example, the local:true // setting means anybody can access the admin panel from localhost -seneca.use('data-editor',{admin:{local:true}}) -seneca.use('admin',{server:server,local:true}) +seneca.use('data-editor', {admin: {local: true}}) +seneca.use('admin', {server: server, local: true}) // set up some test products for the store // create a product entity object -var product_ent = seneca.make$('shop','product') +var product_ent = seneca.make$('shop', 'product') // create new product entities and save them // (the $ suffix avoids namespace collisions with your own properties) -product_ent.make$({name:'apple',price:1,code:'app01'}).save$() -product_ent.make$({name:'orange',price:2,code:'ora02'}).save$() - - - +product_ent.make$({name: 'apple', price: 1, code: 'app01'}).save$() +product_ent.make$({name: 'orange', price: 2, code: 'ora02'}).save$() diff --git a/shopping-cart/package.json b/shopping-cart/package.json index 6a5579b..79dc2b2 100644 --- a/shopping-cart/package.json +++ b/shopping-cart/package.json @@ -5,28 +5,34 @@ "subdomain": "shopping-cart-seneca-example", "main": "app.js", "scripts": { - "start": "app.js" + "start": "node app.js -p 3000" }, "repository": "", "author": "Richard Rodger", "license": "MIT", "dependencies": { - "body-parser": "~1.9.0", - "cookie-parser": "~1.3.2", - "ejs": "~1.0.0", + "body-parser": "~1.15.1", + "cookie-parser": "~1.4.2", + "ejs": "~2.4.2", "ejs-locals": "~1.0.2", - "express": "~4.9.5", - "express-session": "~1.8.2", - "method-override": "~2.2.0", + "express": "~4.13.4", + "express-session": "~1.13.0", + "method-override": "~2.3.6", "optimist": "~0.6.1", "seneca": "plugin", + "seneca-entity": "0.1.1", "seneca-admin": "~0.2.0", - "seneca-auth": "~0.4.0", + "seneca-auth": "~1.0.1", "seneca-cart": "~0.2.0", "seneca-data-editor": "~0.2.0", "seneca-engage": "~0.3.1", "seneca-jsonrest-api": "~0.3.1", - "seneca-user": "~0.2.10", - "serve-static": "~1.6.3" + "seneca-user": "~1.0.2", + "serve-static": "~1.10.2" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/shopping-cart/public/js/shopping-cart.js b/shopping-cart/public/js/shopping-cart.js index 8225f5d..9f00710 100644 --- a/shopping-cart/public/js/shopping-cart.js +++ b/shopping-cart/public/js/shopping-cart.js @@ -5,14 +5,14 @@ $(function(){ ora02:$('#ora02'), } - var prod_form = $('#prod_form') + var prod_form = $('#prod_form') var code_input = $('#code_input') - var cart_form = $('#cart_form') + var cart_form = $('#cart_form') var entry_input = $('#entry_input') - for( var code in prodbox ) { - prodbox[code].click(function(){ + for(var code in prodbox) { + prodbox[code].click(function () { var box = $(this) code_input.val(box.attr('id')) prod_form.submit() @@ -25,4 +25,4 @@ $(function(){ entry_input.val(entry) cart_form.submit() }) -}) \ No newline at end of file +}) diff --git a/shopping-cart/views/cart.ejs b/shopping-cart/views/cart.ejs index 07db105..f722ec1 100644 --- a/shopping-cart/views/cart.ejs +++ b/shopping-cart/views/cart.ejs @@ -1,42 +1,30 @@ <% layout('layout') -%> -

Cart

- -<% if( !cart ) { %> - -Nothing in your cart. - -<% } else { %> - - -<% var entries = cart.entries || [] - for( var i = 0; i < entries.length; i++ ) { - var entry = entries[i] -%> - - - - - -<% } %> - - - - - - - -
<%-entry.name%><%-formatprice(entry.price)%>remove
Total<%-formatprice(cart.total)%>
-<% } %> - - -
-checkout! -
-
-buy more! - - -
- -
+<% if( !cart ) { %> + Nothing in your cart. + <% } else { %> + + <% var entries = cart.entries || [] + for( var i = 0; i < entries.length; i++ ) { + var entry = entries[i] + %> + + + + + + <% } %> + + + + +
<%-entry.name%><%-formatprice(entry.price)%>remove
Total<%-formatprice(cart.total)%>
+ <% } %> +
+ checkout! +
+
+ buy more! +
+ +
diff --git a/shopping-cart/views/checkout.ejs b/shopping-cart/views/checkout.ejs index ff861ba..810096a 100644 --- a/shopping-cart/views/checkout.ejs +++ b/shopping-cart/views/checkout.ejs @@ -3,35 +3,26 @@

Checkout

<% if( !cart ) { %> - -Nothing in your cart. -buy something - -<% } else { %> - - -<% var entries = cart.entries || [] - for( var i = 0; i < entries.length; i++ ) { - var entry = entries[i] -%> - - - - -<% } %> - - - - - - -
<%-entry.name%><%-formatprice(entry.price)%>
Total<%-cart.total%>
- - - - -<% } %> - - -
-cancel + + Nothing in your cart. + buy something + + <% } else { %> + + <% var entries = cart.entries || [] + for( var i = 0; i < entries.length; i++ ) { + var entry = entries[i] + %> + + + + + <% } %> + + + + +
<%-entry.name%><%-formatprice(entry.price)%>
Total<%-cart.total%>
+ <% } %> +
+ cancel diff --git a/shopping-cart/views/index.ejs b/shopping-cart/views/index.ejs index 703002b..a4e986e 100644 --- a/shopping-cart/views/index.ejs +++ b/shopping-cart/views/index.ejs @@ -6,7 +6,7 @@
Buy an Orange!
- +

diff --git a/shopping-cart/views/layout.ejs b/shopping-cart/views/layout.ejs index 9faee4c..728ad90 100644 --- a/shopping-cart/views/layout.ejs +++ b/shopping-cart/views/layout.ejs @@ -1,12 +1,12 @@ - - - + + + -<%- body %> + <%- body %> diff --git a/simple-plugin/.eslintrc b/simple-plugin/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/simple-plugin/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/simple-plugin/README.md b/simple-plugin/README.md index bbe4190..00eea9a 100644 --- a/simple-plugin/README.md +++ b/simple-plugin/README.md @@ -1,9 +1,29 @@ -For an introduction to Seneca data entities, please read the -[senecajs.org tutorial](http://senecajs.org/data-entities.html). +## Setup: +``` + npm install +``` + +## Run: +``` + npm run start +``` +This shows a very common use case for loading and running Seneca +plugins. + + +For detailed logging, try: +``` + npm run details +``` +To run as a micro-service, see micro-service.js +## Test: +``` +npm run test +``` -Notes: +## Notes: The code in simple.js shows you how to create a simple plugin. Plugins are just a way to organise your action patterns into groups so that @@ -38,23 +58,9 @@ level. These lof entries will be annotated with the name of the plugin and the id of the current action, so match them up against other actions. +For an introduction to Seneca data entities, please read the +[senecajs.org tutorial](http://senecajs.org/tutorials/understanding-data-entities.html). -Setup: -$ npm install - - -Run with: -$ node main.js - -This shows a very common use case for loading and running Seneca -plugins. - - -For detailed logging, try: -$ node main.js --seneca.log.all - - -To run as a micro-service, see micro-service.js diff --git a/simple-plugin/package.json b/simple-plugin/package.json index f5f6c3e..11720c2 100644 --- a/simple-plugin/package.json +++ b/simple-plugin/package.json @@ -4,7 +4,9 @@ "description": "Simple plugin example", "main": "simple.js", "scripts": { - "test": "./node_modules/.bin/mocha test/*.test.js" + "test": "./node_modules/.bin/mocha test/*.test.js", + "start": "node main.js", + "details":"node main.js --seneca.log.all" }, "author": "Richard Rodger (http://richardrodger.com)", "license": "MIT", @@ -13,5 +15,10 @@ }, "dependencies": { "seneca": "plugin" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/simple-plugin/simple.js b/simple-plugin/simple.js index 64822fc..352be35 100644 --- a/simple-plugin/simple.js +++ b/simple-plugin/simple.js @@ -1,29 +1,22 @@ -"use strict"; +'use strict' -module.exports = function simple( options ) { +module.exports = function simple (options) { var seneca = this - var suffix = '' - seneca.add('role:simple,cmd:foo', cmd_foo) - seneca.add('init:simple', init) - - function cmd_foo( args, done ) { - done( null, {text:'foo-'+args.text+suffix} ) + function cmd_foo (args, done) { + done(null, {text: 'foo-' + args.text + suffix}) } - - function init( args, done ) { + function init (args, done) { var seneca = this - seneca.log.info("preparing something...") - - setTimeout( function() { + seneca.log.info('preparing something...') + setTimeout(function () { suffix = '-zed' - seneca.log.info("ready!") + seneca.log.info('ready!') done() - }, 111 ) + }, 111) } - } diff --git a/simple-plugin/test/simple.test.js b/simple-plugin/test/simple.test.js index 2d60b66..586584b 100644 --- a/simple-plugin/test/simple.test.js +++ b/simple-plugin/test/simple.test.js @@ -1,23 +1,17 @@ -"use strict"; - -var assert = require('assert') - -var seneca = require('seneca') - - -describe('simple', function(){ - - it('happy', function( fin){ - - seneca({log:'silent',errhandler:fin}) - .use('..') - .act('role:simple,cmd:foo,text:red', - function( err, out ) { - if( err ) return fin(err); - - assert.equal( out.text, 'foo-red-zed' ) - fin() - }) +'use strict' + +var Assert = require('assert') +var Seneca = require('seneca') + +describe('simple', function () { + it('happy', function (fin) { + Seneca({log: 'silent', errhandler: fin}) + .use('..') + .act('role:simple,cmd:foo,text:red', + function (err, out) { + if(err) return fin(err) + Assert.equal(out.text, 'foo-red-zed') + fin() + }) }) - }) diff --git a/user-accounts/.eslintrc b/user-accounts/.eslintrc new file mode 100644 index 0000000..b26e0fd --- /dev/null +++ b/user-accounts/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["seneca"] +} + diff --git a/user-accounts/README.txt b/user-accounts/README.md similarity index 73% rename from user-accounts/README.txt rename to user-accounts/README.md index 331d5fe..56b7812 100644 --- a/user-accounts/README.txt +++ b/user-accounts/README.md @@ -1,5 +1,32 @@ +## Setup: +``` + npm install +``` -Notes: +## Run: +``` +npm run start +``` + +Then visit: +``` +http://localhost:3000 +``` +Access the admin panel on: +``` +http://localhost:3000/admin +``` + +Or to use a custom port: +``` +node app.js --seneca.options.main.port=4000 +``` +## Create config: + +Copy config.template.js to config.mine.js +Optionally add keys for Twitter and Facebook OAuth + +## Notes: This example shows the usage of: @@ -10,7 +37,7 @@ This example shows the usage of: An express server serves the static register page as the starting-point of the example. The registration functionality is handled by the seneca-auth -plugin, which takes the requests to 'auth/*', verifies the inputs, and redirects to the account page. +plugin, which takes the requests to 'auth/, verifies the inputs, and redirects to the account page. The multi-page app login shows how to use ejs templates and the seneca-auth plugin to verify users and conditionally redirect on verification. A few @@ -26,34 +53,13 @@ options are available by creating a config.mine.js file with Twitter and Facebook OAuth keys. seneca-auth uses these keys to authenticate the user, using the same pattern as regular user authentication. -For more questions about the seneca-auth plugin, check out -https://github.com/rjrodger/seneca-auth +For more questions about the seneca-auth plugin, check out [here] +(https://github.com/rjrodger/seneca-auth) -To learn more about the seneca.pin() method, check out -http://senecajs.org/api.html#long-m-pin (more documentation soon) +To learn more about the seneca.pin() method, check out [pin-pattern] +(http://senecajs.org/api/#pin-pin-pattern-) Feel free to contact me on Twitter if you have any questions! :) @rjrodger - - -Create config: - -Copy config.template.js to config.mine.js -Optionally add keys for Twitter and Facebook OAuth - -Run with: - -$ node app.js - -Or to use a custom port: -$ node app.js --seneca.options.main.port=4000 - -Then visit: -http://localhost:3000 - -Access the admin panel on: -http://localhost:3000/admin - - diff --git a/user-accounts/app.js b/user-accounts/app.js index aba0097..470e25a 100644 --- a/user-accounts/app.js +++ b/user-accounts/app.js @@ -1,17 +1,15 @@ /* Copyright (c) 2012-2015 Richard Rodger, MIT License */ -"use strict"; +'use strict' -var http = require('http') - -var express = require('express') -var bodyParser = require('body-parser') -var cookieParser = require('cookie-parser') -var methodOverride = require('method-override') -var session = require('express-session') -var serveStatic = require('serve-static') -var argv = require('optimist').argv +var Http = require('http') +var Express = require('express') +var BodyParser = require('body-parser') +var CookieParser = require('cookie-parser') +var MethodOverride = require('method-override') +var Session = require('express-session') +var ServeStatic = require('serve-static') // create a seneca instance var seneca = require('seneca')() @@ -21,84 +19,73 @@ var seneca = require('seneca')() // copy template config.template.js to config.mine.js and customize var options = seneca.options('config.mine.js') - // use the user and auth plugins // the user plugin gives you user account business logic seneca.use('user') - // the auth plugin handles HTTP authentication -seneca.use('auth',{ +seneca.use('auth', { // redirects after login are needed for traditional multi-page web apps - redirect:{ + redirect: { login: { - win: '/account', + win: '/account', fail: '/login#failed' }, register: { - win: '/account', + win: '/account', fail: '/#failed' } } }) - // use the express module in the normal way -var app = express() +var app = Express() app.enable('trust proxy') -app.use(cookieParser()) -app.use(express.query()) -app.use(bodyParser.urlencoded({extended: true})) -app.use(methodOverride()) -app.use(bodyParser.json()) +app.use(CookieParser()) +app.use(Express.query()) +app.use(BodyParser.urlencoded({extended: true})) +app.use(MethodOverride()) +app.use(BodyParser.json()) // Use in-memory sessions so OAuth will work // In production, use redis or similar -app.use(session({secret:'seneca'})) - -app.use(serveStatic(__dirname + '/public')) - +app.use(Session({secret: 'seneca'})) +app.use(ServeStatic(__dirname + '/public')) // add seneca middleware -app.use( seneca.export('web') ) - +app.use(seneca.export('web')) // some express views -app.engine('ejs',require('ejs-locals')) +app.engine('ejs', require('ejs-locals')) app.set('views', __dirname + '/views') -app.set('view engine','ejs') +app.set('view engine', 'ejs') -app.get('/login', function(req, res){ - res.render('login.ejs',{}) +app.get('/login', function (req, res) { + res.render('login.ejs', {}) }) // when rendering the account page, use the req.seneca.user object // to get user details. This is automatically set up by the auth plugin -app.get('/account', function(req, res){ - res.render('account.ejs',{locals:{user:req.seneca.user}}) +app.get('/account', function (req, res) { + res.render('account.ejs', {locals: {user: req.seneca.user}}) }) - - // create some test accounts -// the "pin" creates a more convenient api, avoiding the need for +// the "pin" creates a more convenient api, avoiding the need for // a full action specification: seneca.act( {role:'user', cmd:'register', ... } ) -var u = seneca.pin({role:'user',cmd:'*'}) -u.register({nick:'u1',name:'nu1',email:'u1@example.com',password:'u1',active:true}) +var u = seneca.pin({role: 'user', cmd: '*'}) +u.register({nick: 'u1', name: 'nu1', email: 'u1@example.com', password: 'u1', active: true}) u.register({nick:'u2',name:'nu2',email:'u2@example.com',password:'u2',active:true}) -u.register({nick:'a1',name:'na1',email:'a1@example.com',password:'a1',active:true,admin:true}) - - +u.register({nick: 'a1', name: 'na1', email: 'a1@example.com', password: 'a1', active: true, admin: true}) // create a HTTP server using the core Node API // this lets the admin plugin use web sockets -var server = http.createServer(app) -server.listen( options.main ? options.main.port : 3000 ) +var server = Http.createServer(app) +server.listen(options.main ? options.main.port : 3000) // visit http://localhost[:port]/admin to see the admin page // you'll need to logged in as an admin - user 'a1' above seneca.use('data-editor') -seneca.use('admin',{server:server}) - +seneca.use('admin', {server: server}) diff --git a/user-accounts/config.template.js b/user-accounts/config.template.js index a998610..3991c5c 100644 --- a/user-accounts/config.template.js +++ b/user-accounts/config.template.js @@ -6,14 +6,14 @@ module.exports = { auth: { service: { twitter: { - key: "TWITTER_KEY", - secret: "TWITTER_SECRET", - urlhost: "http://localhost:3000" + key: 'TWITTER_KEY', + secret: 'TWITTER_SECRET', + urlhost: 'http://localhost:3000' }, facebook: { - key: "FACEBOOK_ID", - secret: "FACEBOOK_SECRET", - urlhost: "http://localhost:3000" + key: 'FACEBOOK_ID', + secret: 'FACEBOOK_SECRET', + urlhost: 'http://localhost:3000' } } } diff --git a/user-accounts/package.json b/user-accounts/package.json index a333084..a5f31d5 100644 --- a/user-accounts/package.json +++ b/user-accounts/package.json @@ -5,27 +5,33 @@ "subdomain": "user-accounts-seneca-example", "main": "app.js", "scripts": { - "start": "app.js" + "start": "node app.js" }, "repository": "", "author": "Richard Rodger", "license": "MIT", "dependencies": { - "express": "~4.9.5", - "body-parser": "~1.9.0", - "cookie-parser": "~1.3.2", - "method-override": "~2.2.0", - "express-session": "~1.8.2", - "serve-static": "~1.6.3", + "express": "~4.13.4", + "body-parser": "~1.15.1", + "cookie-parser": "~1.4.2", + "method-override": "~2.3.6", + "express-session": "~1.13.0", + "serve-static": "~1.10.2", "optimist": "~0.6.1", - "ejs": "~1.0.0", + "ejs": "~2.4.1", "ejs-locals": "~1.0.2", + "node-uuid": "~1.4.0", "seneca": "plugin", - "seneca-user": "~0.2.10", - "seneca-auth": "~0.4.0", + "seneca-user": "~1.0.1", + "seneca-auth": "~1.0.1", "seneca-admin": "~0.2.0", "seneca-jsonrest-api": "~0.3.1", - "seneca-perm": "~0.4.0", + "seneca-perm": "~0.5.6", "seneca-data-editor": "~0.2.0" + }, + "devDependencies": { + "eslint-config-seneca": "2.x.x", + "eslint-plugin-standard": "1.x.x", + "eslint-plugin-hapi": "4.x.x" } } diff --git a/user-accounts/public/js/user-accounts.js b/user-accounts/public/js/user-accounts.js index 8e143c0..370cc27 100644 --- a/user-accounts/public/js/user-accounts.js +++ b/user-accounts/public/js/user-accounts.js @@ -13,15 +13,13 @@ $(function(){ http.post('/auth/logout',{},showLogin) }) - http.get('/auth/instance',showAccount) }) +function showAccount (err, instance) { + if(err) return console.log(err) -function showAccount(err,instance) { - if( err ) return console.log(err); - - if( instance.user ) { + if(instance.user) { $('#user_name').text(instance.user.name) $('#user_email').text(instance.user.email) @@ -30,9 +28,9 @@ function showAccount(err,instance) { } } -function showLogin(err) { - if( err ) return console.log(err); +function showLogin (err) { + if(err) return console.log(err) $('#content_login').slideDown() $('#content_account').slideUp() -} \ No newline at end of file +}