Commit 1a83ca16 authored by Eduardo L. Buratti's avatar Eduardo L. Buratti

Add initial implementation source code

parent 80e47b2c
'use strict';
var gulp = require('gulp');
var gutil = require('gulp-util');
var raml = require('gulp-raml');
var rename = require('gulp-rename');
var jshint = require('gulp-jshint');
var size = require('gulp-size');
var jscs = require('gulp-jscs');
var stylish = require('gulp-jscs-stylish');
var mocha = require('gulp-mocha');
var istanbul = require('gulp-istanbul');
var nodemon = require('gulp-nodemon');
var path = require('path');
var raml2html = require('raml2html');
var through = require('through2');
var yaml = require('js-yaml');
var map = require('map-stream');
var srcFiles = [
'src/**/*.js',
'index.js',
'gulpfile.js',
'test/**/*.js'
];
function handleError(err) {
console.error(err.toString());
process.exit(1);
}
function exitOnError(type) {
return map(function(file, callback) {
if (!file[type].success) {
process.exit(1);
}
callback(null, file);
});
}
function generateDoc(options) {
var simplifyMark = function(mark) {
if (mark) {
mark.buffer = mark.buffer
.split('\n', mark.line + 1)[mark.line]
.trim();
}
};
if (!options) {
options = {};
}
switch (options.type) {
case 'json':
options.config = {
template: function(obj) {
return JSON.stringify(obj, null, 2);
}
};
break;
case 'yaml':
options.config = {
template: function(obj) {
return yaml.safeDump(obj, {
skipInvalid: true
});
}
};
break;
default:
options.type = 'html';
if (!options.config) {
options.config = raml2html.getDefaultConfig(
options.https,
options.template,
options.resourceTemplate,
options.itemTemplate
);
}
}
if (!options.extension) {
options.extension = '.' + options.type;
}
var stream = through.obj(function(file, enc, done) {
var fail = function(message) {
done(new gutil.PluginError('raml2html', message));
};
if (file.isBuffer()) {
var cwd = process.cwd();
process.chdir(path.resolve(path.dirname(file.path)));
raml2html
.render(file.contents, options.config)
.then(function(output) {
process.chdir(cwd);
stream.push(new gutil.File({
base: file.base,
cwd: file.cwd,
path: gutil.replaceExtension(
file.path, options.extension),
contents: new Buffer(output)
}));
done();
},
function(error) {
process.chdir(cwd);
simplifyMark(error.context_mark);
simplifyMark(error.problem_mark);
process.nextTick(function() {
fail(JSON.stringify(error, null, 2));
});
}
);
}
else if (file.isStream()) {
fail('Streams are not supported: ' + file.inspect());
}
else if (file.isNull()) {
fail('Input file is null: ' + file.inspect());
}
});
return stream;
}
gulp.task('raml', function() {
gulp.src('specs/*.raml')
.pipe(raml())
.pipe(raml.reporter('default'))
.pipe(exitOnError('raml'));
});
gulp.task('doc', function() {
return gulp.src('specs/*.raml')
.pipe(generateDoc())
.on('error', handleError)
.pipe(rename({ extname: '.html' }))
.pipe(gulp.dest('doc/build'));
});
gulp.task('pre-test', function() {
return gulp.src(['src/**/*.js'])
.pipe(istanbul())
.pipe(istanbul.hookRequire());
});
gulp.task('test', ['pre-test'], function() {
return gulp.src('test/**/*.spec.js', { read: false })
.pipe(mocha({
require: ['./test/common.js'],
reporter: 'spec',
ui: 'bdd',
recursive: true,
colors: true,
timeout: 60000,
slow: 300,
delay: true
}))
.pipe(istanbul.writeReports())
.once('error', function() {
process.exit(1);
})
.once('end', function() {
process.exit();
});
});
gulp.task('lint', function() {
return gulp.src(srcFiles)
.pipe(jshint())
.pipe(jscs())
.pipe(stylish.combineWithHintResults())
.pipe(jshint.reporter('jshint-stylish'))
.pipe(size())
.pipe(exitOnError('jshint'));
});
gulp.task('check', ['raml', 'lint', 'test']);
gulp.task('develop', function() {
return nodemon({
script: 'index.js',
ext: 'js',
tasks: ['lint']
});
});
#!/usr/bin/env node
/*
* Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana
*
* This file is part of blendb.
*
* blendb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* blendb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with blendb. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
// Add the ./src directory to require's search path to facilitate import
// modules later on (avoiding the require('../../../../module') problem).
require('app-module-path').addPath(__dirname + '/src');
// external libraries
const osprey = require('osprey');
const express = require('express');
const path = require('path');
const ramlParser = require('raml-parser');
// connect to mongodb
const mongo = require('core/mongo');
mongo.connect('mongodb://pyke/blend');
// create a new express app
const app = module.exports = express();
// load router
const router = require('api/router-v1.js');
// parse the RAML spec and load osprey middleware
ramlParser.loadFile(path.join(__dirname, 'specs/blendb-api-v1.raml'))
.then(raml => {
app.use('/v1',
osprey.security(raml),
osprey.server(raml),
router);
if (!module.parent) {
let port = process.env.PORT || 3000;
app.listen(port);
if (app.get('env') === 'development') {
console.log('Server listening on port ' + port + '.');
}
}
else {
// signalize to the test suite that the server is ready to be tested
app.ready = true;
}
},
err => {
console.error('RAML Parsing Error: ' + err.message);
process.exit(1);
});
#%RAML 0.8
title: BlenDB API
version: v1
baseUri: http://blendb.c3sl.ufpr.br/api/{version}
mediaType: application/json
securitySchemes:
- oauth_2_0:
description: |
OAuth2 is a protocol that lets apps request authorization to
private details in the system while avoiding the use of passwords.
This is preferred over Basic Authentication because tokens can be
limited to specific types of data, and can be revoked by users at
any time.
type: OAuth 2.0
describedBy:
headers:
Authorization:
description: |
Used to send a valid OAuth 2 access token. Do not use
together with the "access_token" query string parameter.
type: string
queryParameters:
access_token:
description: |
Used to send a valid OAuth 2 access token. Do not use
together with the "Authorization" header.
type: string
responses:
401:
description: |
Bad or expired token. This can happen if access token
has expired or has been revoked by the user.
body:
application/json:
example: |
{
id: "invalid_oauth_token",
message: "Bad or expired token. This can happen if access token has expired or has been revoked by the user."
}
403:
description: |
Bad OAuth2 request (wrong consumer key, bad nonce,
expired timestamp, ...).
body:
application/json:
example: |
{
id: "invalid_oauth_request",
message: "Bad OAuth2 request (wrong consumer key, bad nonce, expired timestamp, ...)."
}
settings:
authorizationUri: http://simmc.c3sl.ufpr.br/oauth/authorize
accessTokenUri: http://simmc.c3sl.ufpr.br/oauth/access_token
authorizationGrants: [ code, token ]
scopes:
- "user"
- "user:email"
resourceTypes:
- base:
get?: &common
responses:
403:
description: API rate limit exceeded.
headers:
X-RateLimit-Limit:
type: integer
X-RateLimit-Remaining:
type: integer
X-RateLimit-Reset:
type: integer
body:
application/json:
example: |
{
id: "too_many_requests",
message: "API Rate limit exceeded."
}
post?: *common
put?: *common
delete?: *common
- collection:
type: base
get?:
description: |
List all of the <<resourcePathName>> (with optional
filtering).
responses:
200:
description: |
A list of <<resourcePathName>>.
body:
application/json:
schema: <<collectionSchema>>
example: <<collectionExample>>
post?:
description: |
Create a new <<resourcePathName|!singularize>>.
responses:
201:
description: |
Sucessfully created a new
<<resourcePathName|!singularize>>.
headers:
Location:
description: |
A link to the newly created
<<resourcePathName|!singularize>>.
type: string
409:
description: |
Failed to create a new
<<resourcePathName|!singularize>> because a conflict
with an already existing
<<resourcePathName|!singularize>> was detected.
body:
application/json:
example: |
{
"id": "already_exists",
"message": "The <<resourcePathName|!singularize>> could not be created due to a conflict with an already existing <<resourcePathName|!singularize>>."
}
- item:
type: base
get?:
description: |
Return a single <<resourcePathName|!singularize>>.
responses:
200:
description: |
A single <<resourcePathName|!singularize>>.
body:
application/json:
schema: <<itemSchema>>
example: <<itemExample>>
404:
description: |
The <<resourcePathName|!singularize>> could not be
found.
body:
application/json:
example: |
{
"id": "not_found",
"message": "The <<resourcePathName|!singularize>> could not be found."
}
put?:
description: |
Update a <<resourcePathName>>.
responses:
204:
description: |
The <<resourcePathName|!singularize>> was updated.
404:
description: |
The <<resourcePathName|!singularize>> could not be
found.
body:
application/json:
example: |
{
"id": "not_found",
"message": "The <<resourcePathName|!singularize>> could not be found."
}
409:
description: |
Failed to update the <<resourcePathName|!singularize>>
because a conflict with another
<<resourcePathName|!singularize>> was detected.
body:
application/json:
example: |
{
"id": "already_exists",
"message": "Failed to update the <<resourcePathName|!singularize>> because a conflict with another <<resourcePathName|!singularize>> was detected."
}
patch?:
description: |
Partially update a <<resourcePathName>>.
responses:
204:
description: |
The <<resourcePathName|!singularize>> was updated.
404:
description: |
The <<resourcePathName|!singularize>> could not be
found.
body:
application/json:
example: |
{
"id": "not_found",
"message": "The <<resourcePathName|!singularize>> could not be found."
}
409:
description: |
Failed to update the <<resourcePathName|!singularize>>
because a conflict with another
<<resourcePathName|!singularize>> was detected.
body:
application/json:
example: |
{
"id": "already_exists",
"message": "Failed to update the <<resourcePathName|!singularize>> because a conflict with another <<resourcePathName|!singularize>> was detected."
}
delete?:
description: |
Removes a <<resourcePathName>>.
responses:
204:
description: |
The <<resourcePathName|!singularize>> was removed.
404:
description: |
The <<resourcePathName|!singularize>> could not be
found.
body:
application/json:
example: |
{
"id": "not_found",
"message": "The <<resourcePathName|!singularize>> could not be found."
}
- index:
type: base
get?:
description: |
Return an index on the <<resourcePathName>> collection.
responses:
200:
description: |
An index on the <<resourcePathName>> collection.
body:
application/json:
traits:
- paged:
queryParameters:
page:
description: Specify the page that you want to retrieve
type: integer
default: 1
example: 1
per_page:
description: The number of items to return per page
type: integer
minimum: 1
maximum: 50
default: 10
example: 20
- searchable:
queryParameters:
query:
description: |
Query string that filters the data returned for your
request.
type: string
- filtered:
queryParameters:
filters:
description: |
Filters that restrict the data returned for your request.
type: string
- projectable:
queryParameters:
fields:
description: |
Fields to be returned.
type: string
/metrics:
description: |
A Metric represents a statistic that can be queried to generate reports.
This collection allows the user to list all the metrics available in the
system and their descriptions.
securedBy: [ null, oauth_2_0 ]
get:
/dimensions:
description: |
A Dimension allows the data to be aggregated by one or more columns.
This collection allows the user to list all the dimensions available in
the system and their descriptions.
securedBy: [ null, oauth_2_0 ]
get:
/data:
description: |
This is the main part of the API. You may query it for report
data by specifying metrics (at least one). You may also supply
additional query parameters such as dimensions, filters, and
start/end dates to refine your query.
type: base
get:
is: [ filtered ]
queryParameters:
metrics:
description: |
A list of comma-separated metrics.
type: string
required: true
example: "met:daysSinceLastContact,met:estimatedNetworkBandwidth"
dimensions:
description: |
A list of comma-separated dimensions.
type: string
required: true
example: "dim:project,dim:point"
start-date:
description: |
Start date for fetching data. Requests can specify a
start date formatted as YYYY-MM-DD, or as a relative date
(e.g., today, yesterday, or NdaysAgo where N is a positive
integer).
type: string
required: false
pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}|today|yesterday|[0-9]+(daysAgo)"
example: 7daysAgo
end-date:
description: |
End date for fetching data. Requests can specify a
end date formatted as YYYY-MM-DD, or as a relative date
(e.g., today, yesterday, or NdaysAgo where N is a positive
integer).
type: string
required: false
pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}|today|yesterday|[0-9]+(daysAgo)"
example: yesterday
filters:
description: |
Filters that restrict the data returned for your request.
type: string
example: "dim:location(4).id%3D%3D10723"
sort:
description: |
A list of comma-separated dimensions and metrics
indicating the sorting order and sorting direction for
the returned data.
type: string
example: "dim:project"
responses:
200:
description: |
Query successfully executed. Data is returned in a table format.
body:
application/json:
400:
description: |
The supplied query is invalid. Specified metric or dimension
doesn't exist, incorrect formatting for a filter, unacceptable
date range, etc.
body:
application/json:
example: |
{
"id": "metric_not_found",
"message": "The specified metric 'met:electricCharge' could not be found."
}
/collect/{class}:
description: |
This API may be used to send data to the monitoring system. There are a
few available data types (like network bandwidth usage, machine
inventory, etc.) and each of them requires a specific format for the
data being sent.
type: base
uriParameters:
class:
description: The class of data that is being collected.
type: string
minLength: 4
maxLength: 64
pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
post: