Commit 13d805e6 authored by Vytor Calixto's avatar Vytor Calixto 👾

Initial commit

parents
Pipeline #9729 failed with stage
in 3 minutes and 39 seconds
{ 'presets': ['es2015'] }
[
"**/test/*.js"
]
{
"extends": "airbnb",
"root": true,
"plugins": [
"react",
"jsx-a11y",
"import"
],
"rules": {
"jsx-a11y/heading-has-content": [ "off" ],
"jsx-a11y/anchor-has-content": [ "off" ],
"indent": [ "error", 4 ],
"no-unused-vars": [ "error", { "args": "none" }],
"no-param-reassign": [ "off" ]
}
}
.DS_Store
.idea
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
results
npm-debug.log
node_modules/
build/*
.gulp-cache
config.json
docs/
stages:
- test
services:
- mongo:latest
variables:
MONGO_URI: 'mongodb://mongo/app_name'
NODE_ENV: 'test'
before_script:
- npm install --global gulp gulp-cli babel babel-cli babel-core babel-register mocha gulp-mocha gulp-eslint
- npm install
run_tests:
stage: test
script:
- ping -W1 -c1 mongo
- mv config.json.example config.json
- sed -i -e 's/false/true/g' config.json
- sed -i -e 's/simcaq_dev/simcaq_dev3/g' config.json
- gulp build
- gulp test
tags:
- node
cache:
paths:
- node_modules/
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
- Query middleware throws a 404 when the result is empty
- Change filters and dimensions names! No more `_id`. **Breaks compability**
- Parse Params middleware removed
- `ReqQueryFields` middleware added to substitute parseParams and do SQL building automagically with the URL params
- Upgrade to database v2
- config.json.example now has values for development, test and production environments
- Min Node version is 6.8.1
- Cache is now defined per route
- Errors from MonetDB are now returned by the query_exec and query middlewares
- Added user model
- Passport.js added for user authentication
- Added routes to create and authenticate a user
- Added simulation model
- Added routes to save and retrieve simulations in `/simulation`
- Tests are now in different files
## 0.1.0 - 2016-10-10
### Added
**Database**
- MonetDB connection middleware
- MongoDB connection middleware
- query_exec custom middleware (promise that executes an SQL query with optional parameters)
- This version depends on the version 0.1.0 of [MonetDB schema](https://gitlab.c3sl.ufpr.br/simcaq/monetdb)
**Enrollments**
- Enrollments route with all sub dependencies
**Middlewares**
- parseParams middleware to parse HTTP GET params
- query middleware that serves as an interface between squel generated sql and the query execution
- response middleware that gives a standard response (json, csv or xml)
# Contributor code of conduct
As contributors and maintainers of the SimCAQ project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting merge requests, providing feedback in comments, and any other activities.
Communication through any of SimCAQ's channels (GitLab, Mattermos, mailing lists, etc.) must be constructive and never resort to personal attacks, trolling, public or private harrassment, insults, or other unprofessional conduct.
We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the SimCAQ project to do the same.
If any member of the community violates this code of conduct, the maintainers of the SimCAQ project may take action, removing issues, comments, and MRs or blocking accounts as deemed appropriate.
If you are subject to or witness unacceptable behavior, or have any other concerns, please contact us.
\ No newline at end of file
# Contributing to SimCAQ API
## Before you get started
### Code of Conduct
By participating of this project you are expected to hold the [Code of Conduct](https://gitlab.c3sl.ufpr.br/simcaq/simcaq-node/blob/development/CODE_OF_CONDUCT.md).
## How Can I Contribute?
### Reporting Bugs
Bugs are reported and tracked at [simcaq/SCRUM](https://gitlab.c3sl.ufpr.br/simcaq/SCRUM/board) repository. As you determinate which repository your bug is related to, create an issue and label it with the appropriate tags.
## Branch structure
* **master**: protected branch and contains the stable version
* **development**: default branch
* **issue/??**: issue branch - a branch created to solve a issue
* **feature_????**: feature branch - a branch created to add a feature
* **hotfix_????**: hotfix branch - a branch created to fix a problem or error
* **release_vM.m.f**: release branch - a branch created to merge with master and set a release. The verion follows the [semantic versioning](http://semver.org)
## Styleguide
### Commit messages
* Use the present tense ("Add feature" not "Added feature")
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Limit the first line to 72 characters or less
* Reference issues and pull requests liberally
* When only changing documentation, include [ci skip] in the commit description
* Consider starting the commit message with an applicable emoji:
* :art: `:art:` when improving the format/structure of the code
* :racehorse: `:racehorse:` when improving performance
* :memo: `:memo:` when writing docs
* :bug: `:bug:` when fixing a bug
* :fire: `:fire:` when removing code or files
* :green_heart: `:green_heart:` when fixing the CI build
* :white_check_mark: `:white_check_mark:` when adding tests
* :lock: `:lock:` when dealing with security
* :arrow_up: `:arrow_up:` when upgrading dependencies
* :arrow_down: `:arrow_down:` when downgrading dependencies
### JavaScript Styleguide
All JavaScript follows the [AirBnB styleguide](https://github.com/airbnb/javascript) with the following modifications:
* 4 spaces of identation
* No unused vars
* Allow param reassign
This diff is collapsed.
# LDE Admin API
# Dependencies
Previous versions of Node.js do not support ECMAScript6, it is recommended to use **at least** version 6.8.1.
1) Install [NVM (Node Version Manager)](https://github.com/creationix/nvm)
2) Install Node.js via NVM
> source ~/.bashrc
> nvm install v4.5.0
3) Enable Node.js
> nvm use v6.8.1
4) Install the global dependencies
> npm install --global gulp gulp-cli babel babel-cli babel-core babel-register mocha gulp-mocha gulp-eslint istanbul
5) Install project dependencies
> npm install
6) Copy `config.json.example` to `config.json`
> cp config.json.example config.json
{
"development":
{
"port": 3000,
"ip": "127.0.0.1",
"debug" : true,
"mongodb" : {
"uri": "mongodb://localhost/users",
"secret": "SimCAQC3SL"
},
"default": {
"api": {
"version" : "v1"
}
}
},
"test":
{
"port": 3000,
"ip": "127.0.0.1",
"debug" : true,
"mongodb" : {
"uri": "mongodb://localhost/test_users",
"secret": "SimCAQC3SL"
},
"default": {
"api": {
"version" : "v1"
}
}
},
"production":
{
"port": 3000,
"ip": "127.0.0.1",
"debug" : false,
"mongodb" : {
"uri": "mongodb://localhost/users",
"secret": "SimCAQC3SL"
},
"default": {
"api": {
"version" : "v1"
}
}
}
}
require('babel-core/register');
const fs = require('fs');
const gulp = require('gulp');
const babel = require('gulp-babel');
const eslint = require('gulp-eslint');
const mocha = require('gulp-mocha');
const istanbul = require('gulp-istanbul');
const nodemon = require('gulp-nodemon');
const Cache = require('gulp-file-cache');
const docco = require('gulp-docco');
const mkdirp = require('mkdirp');
const cache = new Cache();
function createLogDir() {
const logDirPath = 'build/logs';
mkdirp(logDirPath, (err) => {
if(err) console.error(err);
});
}
/**
* Compile source files
*/
gulp.task('compile', () => {
// compile source to ES5
gulp.src('src/**/*.js')
.pipe(cache.filter()) // cache source files
.pipe(babel()) // compile only modified files
// .pipe(cache.cache()) // cache compiled files
.pipe(gulp.dest('build')); // move compiled files to build directory
});
gulp.task('build', ['compile'], () => {
var filesToCopy = [ 'config.json', 'package.json' ];
// copy configuration file to build directory
gulp.src(filesToCopy)
.pipe(gulp.dest('build'));
createLogDir();
});
gulp.task('docco', () => {
gulp.src('./src/**/*.js')
.pipe(docco())
.pipe(gulp.dest('./docs'));
});
gulp.task('doc', ['docco']);
gulp.task('pre-test', () => {
return gulp.src(['build/**/*.js', '!build/{test,test/**}'])
.pipe(istanbul())
.pipe(istanbul.hookRequire());
});
gulp.task('test', ['pre-test'], () => {
process.chdir('build');
gulp.src(['test/**/*.js'], {read: false})
.pipe(mocha({timeout: 60000}))
.pipe(istanbul.writeReports())
.pipe(istanbul.enforceThresholds({
thresholds: {
global: {
statements: 80,
branches: 70,
lines: 80,
functions: 80
}
}
}))
.on('error', () => {
process.exit(1);
})
.on('end', () => {
process.exit();
});
});
gulp.task('watch', ['compile'], () => {
console.log('Watching source directory for changes');
gulp.watch('src/**/*.js').on('change', () => {
console.log('Recompiling source');
gulp.start('compile');
console.log('Source recompilation done');
});
});
gulp.task('run', () => {
process.chdir('build');
nodemon({
script: 'server.js',
tasks: ['watch'],
ignore: ["test/test.js", "gulpfile.babel.js"],
ext: 'js html json',
env: { 'NODE_ENV': 'development' }
});
});
gulp.task('default', ['run']);
{
"tags": {
"allowUnknownTags": true
},
"plugins": [
"plugins/markdown"
],
"opts": {
"template": "./node_modules/docdash/",
"encoding": "utf8",
"destination": "docs/",
"recurse": true,
"verbose": true
},
"templates": {
"cleverLinks": false,
"monospaceLinks": false
}
}
{
"name": "lde-admin-api",
"version": "0.0.1",
"author": "C3SL",
"description": "Laboratório de dados educaionais",
"private": true,
"engines": {
"node": ">= 6.8.1"
},
"engineStrict": true,
"scripts": {
"start": "cd build && forever start server.js || node server.js",
"test": "cd build && mocha"
},
"dependencies": {
"apicache": "0.7.0",
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.13.1",
"chai": "^3.5.0",
"chai-http": "^3.0.0",
"chalk": "^1.1.3",
"cookie-parser": "^1.3.5",
"cors": "^2.7.1",
"csv-express": "^1.1.0",
"debug": "~2.3.x",
"dirty-chai": "^1.2.2",
"express": "^4.13.0",
"js2xmlparser": "^2.0.2",
"jwt-simple": "^0.5.0",
"lodash": "^4.17.2",
"method-override": "^2.3.3",
"mocha": "^3.1.2",
"monetdb-pool": "0.0.8",
"mongoose": "^4.6.0",
"nconf": "^0.8.x",
"passport": "^0.3.2",
"winston": "^2.2.0"
},
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.11.4",
"babel-core": "^6.14.0",
"babel-preset-es2015": "^6.13.2",
"babel-register": "^6.14.0",
"babelify": "^7.3.0",
"browserify": "^13.1.0",
"chai-xml": "^0.3.1",
"docdash": "^0.4.0",
"eslint": "^3.7.1",
"eslint-config-airbnb": "^13.0.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.0",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-cli": "^1.2.2",
"gulp-docco": "0.0.4",
"gulp-eslint": "^3.0.1",
"gulp-file-cache": "0.0.1",
"gulp-function": "^2.2.0",
"gulp-istanbul": "^1.1.1",
"gulp-jsdoc3": "^1.0.1",
"gulp-mocha": "^3.0.1",
"gulp-nodemon": "^2.1.0",
"gulp-plumber": "^1.1.0",
"gulp-rename": "^1.2.2",
"gulp-uglify": "^2.0.0",
"jsdoc": "^3.4.1",
"mkdirp": "^0.5.1"
}
}
const express = require('express');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const methodOverride = require('method-override');
const cors = require('cors');
const libs = `${process.cwd()}/libs`;
const log = require(`${libs}/log`)(module);
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
const config = require(`${libs}/config`);
const cache = require('apicache').options({ debug: config.debug }).middleware;
const app = express();
const api = require('./routes/api');
const passport = require('passport');
const mongoose = require(`${libs}/db/mongoose`);
const db = mongoose();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
// Enable Cross-Origin Resource Sharing (CORS)
app.use(cors());
app.use(methodOverride());
app.use((req, res, next) => {
res.setHeader('Last-Modified', (new Date()).toUTCString());
next();
});
// Mounts all API routes under /api/v1
app.use('/api/v1', api);
// Catch 404 and forward to error handler
app.use((req, res, next) => {
res.status(404);
log.error('%s %d %s', req.method, res.statusCode, req.url);
res.json({ error: 'Error 404: Page not found' }).end();
});
// Express' default error handler
app.use((err, req, res, next) => {
res.status(err.status || 500);
log.error('%s %d %s', req.method, res.statusCode, err.message);
res.json({ error: err.message }).end();
});
module.exports = app;
let conf = require(`${process.cwd()}/config.json`);
conf = conf[process.env.NODE_ENV];
module.exports = conf;
const MonetDBPool = require('monetdb-pool');
const libs = `${process.cwd()}/libs`;
const config = require(`${libs}/config`);
// Connection options
const poolOptions = {
nrConnections: config.monetdb.nrConnections,
};
// Configuration options
const options = {
host: config.monetdb.host,
port: config.monetdb.port,
dbname: config.monetdb.dbname,
user: config.monetdb.user,
password: config.monetdb.password,
};
// Connection singleton
const conn = new MonetDBPool(poolOptions, options);
conn.connect();
module.exports = conn;
const libs = `${process.cwd()}/libs`;
const config = require(`${libs}/config`);
const log = require(`${libs}/log`)(module);
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
module.exports = () => {
// Get mongodb URI (ip and port) in config file
const mongoUri = process.env.MONGO_URI || config.mongodb.uri;
log.debug(`Connecting to MongoDB on URI ${mongoUri}`);
// Connection singleton
const db = mongoose.connect(mongoUri);
mongoose.connection.once('open', () => { log.info("MongoDB connected"); });
return db;
};
const libs = `${process.cwd()}/libs`;
const log = require(`${libs}/log`)(module);
const conn = require(`${libs}/db/monet`);
// Promise that executes an SQL query with optional parameters
// ```
// Examples:
// Query with no parameters:
// execSqlQuery('SELECT * FROM people');
// Query with one parameter:
// execSqlQuery('SELECT name, age FROM people WHERE id = ?', [1]);
// Query with more than one parameter:
// execSqlQuery('SELECT name, age FROM people WHERE city = ? AND age > ?', ['São Paulo', 35]);
// ```
function execSqlQuery(sqlQuery, sqlQueryParams = []) {
log.debug(`Executing SQL query '${sqlQuery}' with params '${sqlQueryParams}'`);
return new Promise((resolve, reject) => {
// Prepare statement
conn.prepare(sqlQuery, true).then((dbQuery) => {
// Execute query
dbQuery.exec(sqlQueryParams).then((dbResult) => {
// release resources allocated for the prepared statement
dbQuery.release();
resolve(dbResult.data);
}).catch((queryError) => {
log.error(`SQL query execution error: ${queryError.message}`);
log.error(`SQL query: ${sqlQuery} with params: ${sqlQueryParams}`);
// release resources allocated for the prepared statement
dbQuery.release();
reject(new Error(queryError.message));
});
}).catch((prepError) => {
log.error(`SQL prepared statement error: ${prepError.message}`);
log.error(`SQL query: ${sqlQuery} with params: ${sqlQueryParams}`);
reject(new Error(prepError.message));
});
});
}
module.exports = execSqlQuery;
const libs = `${process.cwd()}/libs`;
const config = require(`${libs}/config`);
const winston = require('winston');
winston.emitErrs = true;
function getFilePath(module) {
// using filename in log statements
return module.filename.split('/').slice(-2).join('/');
}
function logger(module) {
const logPath = `${process.cwd()}/logs`;
const maxLogFiles = 5;
const maxLogSize = 5242880;
const log = new winston.Logger({
transports: [
new winston.transports.File({
name: 'info-log',
level: 'info',
filename: `${logPath}/simcaq-info.log`,
handleException: true,
json: false,
maxSize: maxLogSize, // 5MB
maxFiles: maxLogFiles,
colorize: false,
}),
new winston.transports.File({
name: 'error-log',
level: 'error',
filename: `${logPath}/simcaq-error.log`,
handleException: true,
json: false,
maxSize: maxLogSize, // 5MB
maxFiles: maxLogFiles,
colorize: false,
}),
new winston.transports.Console({
name: 'debug-log',
level: (process.env.NODE_ENV === 'development') ? 'debug' : 'error',
label: getFilePath(module),
handleException: true,
json: false,
colorize: true,
}),
],
exitOnError: false,
});
if (!config.debug) {
log.remove('debug-log');
}
return log;
}
module.exports = logger;
const curPath = process.cwd();
const libs = `${process.cwd()}/libs`;
const log = require(`${libs}/log`)(module);
const chalk = require('chalk');
const packageConf = require(`${curPath}/package.json`);
module.exports = () => {
// Parse version number from strings such as 'v4.2.0' or `>=4.0.0'
function parseVersionNumber(versionString) {
return parseFloat(versionString.replace(/[^\d\.]/g, ''));
}
// Ensure minimum supported node version is used
const minNodeVersion = parseVersionNumber(packageConf.engines.node);
const currentNodeVersion = parseVersionNumber(process.version);
if (minNodeVersion > currentNodeVersion) {
log.error(chalk.red(`You must upgrade node to >=${minNodeVersion}.x to use simcaq-node!`));
return false;
} else {
log.info('Node version should work!');
return true;
}
};
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const libs = `${process.cwd()}/libs`;
const config = require(`${libs}/config`);
const User = require(`${libs}/models/user`)
module.exports = function(passport){
var opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = config.get('mongodb:secret');
passport.use(new JwtStrategy(opts, function(jwt_payload, done){
User.find({email: jwt_payload.email}, function(err, user){
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {message: 'Unknown user'});
}
return done(null, user);
});
}));
};
/* To check if a user has access to a route, one must use passport.authenticate() specifying 'JWT' as the strategy in the route declaration, like so:
//pass passportfor configuration