A corbel-composer is a middleware based in nodeJS with express, to offer developers to make his own specific application API bsed in corbel-js
[text]
[image]
-
install
npm install -g bq/corbel-composer
-
run server
corbel-composer
- Get postman
- Import corbel-composer collection:
https://raw.githubusercontent.com/bq/corbel-composer/master/doc/postman/postman.json
- Import evironment example:
https://raw.githubusercontent.com/bq/corbel-composer/master/doc/postman/environment.example.json
- Import globals:
https://raw.githubusercontent.com/bq/corbel-composer/master/doc/postman/globals.example.json
- Enjoy!
{
"url": "phraseName",
"get": {
"code": "res.render('index', {title: 'hello world'});",
"doc": {
"description": "Phrase description",
"queryParameters": {
"param1": {
"type": "number",
"description": "Param description",
"default": 0
}
},
"responses": {
"200": {
"body": {
"application/json": {
"schema": "{\n\t"$schema": "http://json-schema.org/schema",\n\t"type": "object",\n\t"description": "A canonical song",\n\t"properties": {\n\t\t"title": {\n\t\t\t"type": "String"\n\t\t},\n\t\t"artist": {\n\t\t\t"type": "String"\n\t\t}\n\t},\n\t"required": ["title", "artist"]\n}"
}
}
}
}
}
}
}
{
"url": "countExample",
"get": {
"code": "CORBEL-JS_SNIPPET"
}
}
where code
should be a string with this corbel-js snippet:
var count;
corbelDriver.resources.collection('test:ComposrTest').get(undefined, {
aggregation: {
$count: '*'
}
}).then(function(response) {
count = response.data.count;
return corbelDriver.resources.collection('test:ComposrTest').get();
}).then(function(response) {
res.send({
data: response.data,
'count': count
});
}).catch(function(error) {
res.send(error);
});
{
"url": "paramsExample/:pathparam",
"get": {
"code": "res.status(200).send('path param: ' + req.params.pathparam + ', query param: ' + req.query.queryparam);"
}
}
- corbel-js API
- Request object
- Response object
- RAML for phrase definition
- Use nouns not verbs
- Use plural nouns
Resource | GET (read) | POST (create) | PUT (update) | DELETE |
---|---|---|---|---|
/cars | Returns a list of cars | Create a new ticket | Bulk update of cars | Delete all cars |
/cars/711 | Returns a specific car | Method not allowed (405) | Updates a specific ticket | Deletes a specific ticket |
/purchase | Get al purchases | Create a new purchase | Bulk update of purschases | Delete all purchases |
/purchase/85 | Returns a purchase | Method not allowed (405) | Updates a specific purchase | Delete all purchases |
Resource GET read POST create PUT update DELETE /cars Returns a list of cars Create a new ticket Bulk update of cars Delete all cars /cars/711 Returns a specific car Method not allowed (405) Deletes a specific ticket
A simple way to achieve this is definning the phrase version in the url, like this
{
"url": "v1/paramsExample/:pathparam",
"get": { ... }
}
A phrase version should change only if the phrase contract is broken
- APIgee
- Principios de diseño de APIs REST
- Best Practices for Designing a Pragmatic RESTful API
- REST API Resoruces
-
clone repo
-
build image
docker build -t <username>/corbel-composer .
-
run container
docker run -d -p 3000:3000 --name="corbel-composer" <username>/corbel-composer
-
start/stop container
docker start/stop corbel-composer
npm test
grunt test:coverage
Requires node-inspector
npm install -g node-inspector
-
Server
npm run debug
-
Tests
npm run test:debug
NodeJS example
var corbel = require('corbel-js');
function generateAssertion(claims, clientSecret) {
claims.aud = corbel.Iam.AUD;
return corbel.jwt.generate(claims, clientSecret);
}
var claims = {
iss: credentials.clientId,
scope: credentials.scopes
};
var jwt = generateAssertion(claims, credentials.clientSecret);
//Make a POST request to the login client phrase with jwt in the body
/*
Expect an object containing
{
accessToken : '',
expiresAt : ''
}
*/
if (!req.body || !req.body.jwt) {
throw new ComposerError('error:jwt:undefined', '', 401);
}
var corbelDriver = corbel.generateDriver({iamToken: ''});
/*
* Required claims:
* iss: CLIENT_ID
* aud: 'http://iam.bqws.io'
* scope: 'scope1 scope2'
* exp: epoch + 1h
*/
corbelDriver.iam.token().create({
jwt: req.body.jwt
}).then(function(response) {
res.send(response.data);
}).catch(function(err){
compoSR.run('global:parseError', { err : err, res : res});
});
NodeJS example
var corbel = require('corbel-js');
function generateAssertion(claims, clientSecret) {
claims.aud = corbel.Iam.AUD;
return corbel.jwt.generate(claims, clientSecret);
}
//Note that appCredentials contains the credentials of the client app
var claims = {
iss: appCredentials.clientId,
'basic_auth.username': userCredentials.username,
'basic_auth.password': userCredentials.password,
scope: userCredentials.scopes
};
var jwt = generateAssertion(claims, appCredentials.clientSecret);
//Make a POST request to the login user phrase with jwt in the body
/*
Expect an object containing
{
tokenObject: {
accessToken : '',
expiresAt : '',
refreshToken : ''
},
user: {
...
}
}
*/
if (!req.body || !req.body.jwt) {
throw new ComposerError('error:jwt:undefined', '', 401);
}
var corbelDriver = corbel.generateDriver({iamToken: ''});
var tokenObject;
/*
* Request a session token for the user
* Required claims:
* iss: CLIENT_ID
* basic_auth.username: USERNAME
* basic_auth.password: PASSWORD
* aud: 'http://iam.bqws.io'
* scope: 'scope1 scope2'
* exp: epoch + 1h
*/
corbelDriver.iam.token().create({
jwt : req.body.jwt
}).then(function(response){
//Tenemos el token de usuario, asimismo tambien el refresh y el expires
tokenObject = response.data;
//Recreamos el corbelDriver con los settings del usuario
corbelDriver = corbel.generateDriver({
iamToken : tokenObject
});
//Obtain the logged user data
return corbelDriver.iam.user('me').get();
}).then(function(response){
res.send({
tokenObject: tokenObject,
user: response.data
});
}).catch(function(err){
compoSR.run('global:parseError', { err : err, res : res});
});
NodeJS example
var corbel = require('corbel-js');
function generateAssertion(claims, clientSecret) {
claims.aud = corbel.Iam.AUD;
return corbel.jwt.generate(claims, clientSecret);
}
//Note that appCredentials contains the credentials of the client app
var claims = {
iss: appCredentials.clientId,
'refresh_token': refresh_token,
scope: userCredentials.scopes
};
var jwt = generateAssertion(claims, appCredentials.clientSecret);
//Make a POST request to the refresh token phrase with jwt in the body
/*
Expect an object containing
{
accessToken : '',
expiresAt : '',
refreshToken : ''
}
*/
if (!req.body || !req.body.jwt) {
throw new ComposerError('error:jwt:undefined', '', 401);
}
var corbelDriver = corbel.generateDriver({iamToken: ''});
/*
* Required claims:
* iss: CLIENT_ID
* refresh_token: REFRESH_TOKEN
* aud: 'http://iam.bqws.io'
* scope: 'scope1 scope2'
* exp: epoch + 1h
*/
corbelDriver.iam.token().create({
jwt : req.body.jwt
})
.then(function(response){
res.send(response.data);
})
.catch(function(err){
compoSR.run('global:parseError', { err : err, res : res});
});
NodeJS example
var http = require('http');
var accessToken = "xxxxx":
var post_options = {
host: 'composrendpoint.composr',
path: '/logoutuser',
method: 'POST',
headers: {
'Authorization': accessToken
}
};
//Make a POST request to the logout user phrase with an Authorization header
http.request(post_options, function(res) {
//Expect 204 for a good logout, 401 for unauthorized
});
if (!req.get('Authorization')) {
throw new ComposerError('error:unauthorized', 'Authorization missing', 401);
}
var method = req.params.type && req.params.type === 'all' ? 'disconnect' : 'signOut';
/*
* Disconnects a user session
*/
corbelDriver.iam.user('me')[method]()
.then(function(response){
res.send(response.data);
}).catch(function(err){
compoSR.run('global:parseError', { err : err, res : res});
});
if (!req.get('Authorization')) {
throw new ComposerError('error:unauthorized', 'Authorization missing', 401);
}
/*
* Disconnects a user session
*/
corbelDriver.iam.user('me')
.disconnect()
.then(function(response){
res.send(response.data);
}).catch(function(err){
compoSR.run('global:parseError', { err : err, res : res});
});
corbelDriver.iam.user('me').get();
if (!req.get('Authorization')) {
throw new ComposerError('error:authorization:undefined', '', 401);
}
var books = [];
for( var i = 0; i < 20; i++){
books.push({
_id: Date.now(),
_createdAt: new Date("2015-05-08T14:37:37.628Z"),
_src_id: "Libranda",
_dst_id: "books:Book/7004c092",
isbn: "9788415564430",
distributorId: "LIBR",
title: "Remedio: la geografía",
synopsis: "",
authors: [
{
name: "Luigi Pirandello",
biographicalNote: ""
}
],
rawCategories: [
"FA"
],
storeCategories: [
"F",
"FA"
],
cover: "http://www.nordicalibros.com/upload/fgr02102012145613.jpg",
format: "epub",
language: "spa",
publisherGroupName: "Nordica Libros",
publishingTime: 1347753600000,
_updatedAt: new Date("2015-05-08T14:37:37.628Z"),
_order: 1
});
}
res.send({
data : books,
count : books.length
});
Code snippets are a minor form of phrases
, they are accesible through the compoSR
object on your phrases.
You can run your code snippets by executing compoSR.run('snippetName', params);
where params
is anything you want it to be. From your snippets you will be allowed to access to the params
variable and the compoSR
object itself.
compoSR
will be allowed to access any snippets defined in your domain and your parent domains.
For example, _silkroad:composer
will be able to access all the _silkroad:composer
snippets and all the _silkroad
snippets. If a snippet has the same name on both of the domains, the one with a deepest hierarchy will overwrite the first one.
Let's take a look at it:
- Given this snippets:
var snippets = {
'domainName' : [
{
name : 'myFunction',
code : 'compoSR.run("hello", "world")'
},
{
name : 'hello',
code: 'console.log(params);'
}
],
'domainName:childDomain' : [
{
name : 'hello',
code: 'console.log("I am the child: ", params);'
}
],
}
- If we run the
myFunction
snippet, accesing from a client that belongs to the domain nameddomainName:childDomain
it will show this:
compoSR.run('myFunction');
//=> I am the child: world
- If the client or user belongs to the domain named
domainName
and we execute the same function we'll get:
compoSR.run('myFunction');
//=> hello world
Logs are written to the linux syslog and in the logs folder.
You can set logFile
and logLevel
in your config file.
Available log levels can be found at winston's npm page:
- debug
- info
- warn
- error
You can disable syslog by setting syslog
property to false
in the config file.