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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

broken link



4 changes: 4 additions & 0 deletions api-server/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["seneca"]
}

42 changes: 25 additions & 17 deletions api-server/README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

117 changes: 50 additions & 67 deletions api-server/api.js
Original file line number Diff line number Diff line change
@@ -1,116 +1,99 @@
// 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'
}
}
}})

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
})
}
)
}

}
14 changes: 6 additions & 8 deletions api-server/app.js
Original file line number Diff line number Diff line change
@@ -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)

74 changes: 31 additions & 43 deletions api-server/client.js
Original file line number Diff line number Diff line change
@@ -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()
}))
}
Loading