├── api ├── models │ ├── .gitkeep │ ├── Team.js │ ├── Project.js │ ├── Member.js │ ├── Todo.js │ └── Endpoint.js ├── services │ └── .gitkeep ├── controllers │ ├── .gitkeep │ ├── IndexController.js │ ├── MemberController.js │ ├── ProjectController.js │ ├── TeamController.js │ ├── TodosController.js │ └── EndpointController.js ├── utils │ └── debug.js ├── policies │ └── sessionAuth.js └── responses │ ├── ok.js │ ├── created.js │ ├── badRequest.js │ ├── forbidden.js │ ├── serverError.js │ └── notFound.js ├── assets ├── images │ ├── .gitkeep │ └── nebulis-logo.png ├── templates │ └── .gitkeep ├── favicon.ico ├── robots.txt └── styles │ └── importer.less ├── .node-version ├── src ├── routes │ ├── members │ │ ├── view.js │ │ └── index.js │ ├── NotFound.js │ ├── Page2.js │ ├── Page1.js │ ├── Home.js │ ├── Login.js │ ├── Layout.js │ ├── TodosPage.js │ └── projects │ │ └── index.js ├── styles │ ├── about-page.css │ └── styles.scss ├── components │ ├── style.scss │ ├── Counter.js │ ├── MemberListItems.js │ ├── Teams.js │ ├── Todos.js │ ├── AddTeam.js │ ├── ProjectListItems.js │ ├── Team.js │ ├── Members.js │ ├── Projects.js │ └── Form.js ├── reducers │ ├── initialState.js │ ├── index.js │ ├── todoReducers.js │ ├── memberReducers.js │ ├── teamReducers.js │ └── projectReducers.js ├── App.js ├── .eslintrc ├── api │ └── todos.js ├── routes.js ├── redux │ ├── index.js │ └── todos.js ├── constants │ └── actionTypes.js ├── index.js ├── store │ └── configureStore.js ├── actions │ └── nebulisActions.js └── utils │ └── redux-helpers.js ├── test ├── server │ ├── LoginController.test.js │ ├── MembersController.test.js │ ├── TeamsController.test.js │ ├── config.js │ ├── Member.test.js │ ├── Team.test.js │ ├── Todo.test.js │ ├── ProjectsController.test.js │ ├── TodosController.test.js │ ├── Project.test.js │ └── Endpoint.test.js ├── endpoint │ ├── testingProject │ │ ├── index.js │ │ ├── .nebulis.json │ │ ├── .gitignore │ │ └── package.json │ ├── sampleconfig │ └── config.js ├── ghoulies │ ├── config.js │ └── todos.test.js └── client │ ├── projectsPage.test.js │ ├── config.js │ ├── Login.test.js │ ├── Todos.test.js │ ├── Projects.test.js │ └── Members.test.js ├── .dockerignore ├── .sailsrc ├── Procfile ├── config ├── locales │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ └── _README.md ├── bootstrap.js ├── env │ ├── dev.js │ └── prod.js ├── log.js ├── models.js ├── policies.js ├── i18n.js ├── csrf.js ├── globals.js ├── cors.js └── routes.js ├── .babelrc ├── gitnetLifter.js ├── .eslintrc.json ├── tasks ├── createDevEnv.sh ├── config │ ├── clean.js │ ├── uglify.js │ ├── coffee.js │ ├── watch.js │ ├── cssmin.js │ ├── less.js │ ├── sync.js │ ├── concat.js │ ├── copy.js │ └── jst.js ├── register │ ├── syncAssets.js │ ├── compileAssets.js │ ├── linkAssetsBuild.js │ ├── linkAssetsBuildProd.js │ ├── linkAssets.js │ ├── prod.js │ ├── buildProd.js │ ├── build.js │ └── default.js ├── pipeline.js └── README.md ├── public └── styles │ └── global.css ├── docker-compose.yml ├── views ├── index.ejs ├── header.ejs ├── 403.ejs ├── layout.ejs └── 404.ejs ├── .editorconfig ├── gitnet.js ├── Dockerfile ├── .travis.yml ├── nebugit ├── messages.js └── index.js ├── Gruntfile.js ├── app.js ├── webpack.config.js ├── .gitignore ├── README.md └── nginx.conf.template /api/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v6.9.1 2 | -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/members/view.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/server/LoginController.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/server/MembersController.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/server/TeamsController.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "modules": {} 4 | } 5 | } -------------------------------------------------------------------------------- /src/styles/about-page.css: -------------------------------------------------------------------------------- 1 | .alt-header { 2 | color: green; 3 | } -------------------------------------------------------------------------------- /test/endpoint/testingProject/index.js: -------------------------------------------------------------------------------- 1 | const nebulis = require('nebulis-endpoint'); -------------------------------------------------------------------------------- /test/endpoint/testingProject/.nebulis.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "localhost", 3 | "port": 1337 4 | } -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | build: npm run build:prod 2 | web: npm run server:prod 3 | gitnet: npm run gitnet:prod 4 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulisAnalytics/nebulis-server/HEAD/assets/favicon.ico -------------------------------------------------------------------------------- /config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Willkommen", 3 | "A brand new app.": "Eine neue App." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome", 3 | "A brand new app.": "A brand new app." 4 | } 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["react-hot-loader/babel"] 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido", 3 | "A brand new app.": "Una nueva aplicación." 4 | } 5 | -------------------------------------------------------------------------------- /gitnetLifter.js: -------------------------------------------------------------------------------- 1 | const nebugit = require('./gitnet'); 2 | 3 | setTimeout(() => { 4 | nebugit.listen(); 5 | }, 3500); -------------------------------------------------------------------------------- /config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue", 3 | "A brand new app.": "Une toute nouvelle application." 4 | } 5 | -------------------------------------------------------------------------------- /assets/images/nebulis-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulisAnalytics/nebulis-server/HEAD/assets/images/nebulis-logo.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react", 5 | "jsx-a11y", 6 | "import" 7 | ] 8 | } -------------------------------------------------------------------------------- /tasks/createDevEnv.sh: -------------------------------------------------------------------------------- 1 | echo export API_HOST=http://localhost:1337 2 | echo export GIT_HOST=localhost:7000 3 | echo export REPO_LOCATION=/tmp/repos -------------------------------------------------------------------------------- /public/styles/global.css: -------------------------------------------------------------------------------- 1 | HTML, BODY { 2 | font-family: Roboto; 3 | background: white; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | .appbar H1 { 9 | display: inline; 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/NotFound.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class NotFound extends Component { 4 | render() { 5 | return (
Not found
); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/style.scss: -------------------------------------------------------------------------------- 1 | $nebula-blue: #5F6D99; 2 | $codesmith-blue: #3182E4; 3 | $tron-blue: #02C5FF; 4 | $tron-orange: #FF8442; 5 | $blood-orange: #CC4621; 6 | 7 | div.projects{ 8 | 9 | } 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "1337:1337" 7 | volumes: 8 | - .:/usr/src/app 9 | redis: 10 | image: "node:boron" 11 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /api/controllers/IndexController.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | index: function (req, res) { 3 | //res.locals.layout = 'views/layoout.ejs'; 4 | res.render('index', { 5 | NODE_ENV: process.env['NODE_ENV'] 6 | }); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /gitnet.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const config = JSON.parse(fs.readFileSync('./.babelrc')); 3 | 4 | require('babel-core/register')(config); 5 | require('babel-polyfill'); 6 | 7 | const nebugit = require('./nebugit'); 8 | 9 | module.exports = nebugit; -------------------------------------------------------------------------------- /src/reducers/initialState.js: -------------------------------------------------------------------------------- 1 | export default function getInitialState() { 2 | return { 3 | error: null, 4 | loading: false, 5 | projects: [], 6 | isAddingProject: false, 7 | project: null, 8 | teams: null, 9 | team: {}, 10 | downloading:false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /assets/robots.txt: -------------------------------------------------------------------------------- 1 | # The robots.txt file is used to control how search engines index your live URLs. 2 | # See http://sailsjs.org/documentation/anatomy/my-app/assets/robots-txt for more information. 3 | 4 | 5 | 6 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 7 | # User-Agent: * 8 | # Disallow: / 9 | -------------------------------------------------------------------------------- /test/ghoulies/config.js: -------------------------------------------------------------------------------- 1 | // require the app 2 | require('../../app.js'); 3 | 4 | var ghoulies = require('ghoulies'); 5 | 6 | before(function(done) { 7 | 8 | // listen to server event defined in /config/http.js 9 | 10 | ghoulies.on('SERVER_LOADED', function(app) { 11 | ghoulies.app = app; 12 | done(); 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | # This sails app will start at port 1337 15 | EXPOSE 1337 16 | 17 | # Start the sails backend server 18 | CMD ["npm", "run", "prod"] 19 | -------------------------------------------------------------------------------- /test/endpoint/sampleconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = true 4 | bare = false 5 | logallrefupdates = true 6 | ignorecase = true 7 | precomposeunicode = true 8 | [remote "github-nebulis"] 9 | url = https://github.com/LabUser/coding-challenge-1.git 10 | fetch = +refs/heads/*:refs/remotes/github-nebulis/* 11 | [branch "master"] 12 | remote = user 13 | merge = refs/heads/master -------------------------------------------------------------------------------- /test/endpoint/testingProject/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OS X 3 | .DS_Store* 4 | Icon? 5 | ._* 6 | 7 | # Windows 8 | Thumbs.db 9 | ehthumbs.db 10 | Desktop.ini 11 | 12 | # Linux 13 | .directory 14 | *~ 15 | 16 | 17 | # npm 18 | node_modules 19 | *.log 20 | *.gz 21 | 22 | 23 | # Coveralls 24 | coverage 25 | 26 | # Benchmarking 27 | benchmarks/graphs 28 | .nebugit 29 | /lib 30 | /yarn.lock 31 | /package-lock.json 32 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import routes from './routes'; 3 | import { Route, Router, IndexRoute, browserHistory } from 'react-router'; 4 | 5 | export default class App extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | render() { 10 | return ( 11 | 12 | {routes} 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | notifications: 3 | email: true 4 | slack: codesmith-15:pJzGP558zsfbulY3uQSdl2n0 5 | node_js: 6 | - '8' 7 | cache: 8 | directories: 9 | - node_modules 10 | script: 11 | - npm run test:endpoint 12 | - npm run test:server 13 | - npm run test:client 14 | addons: 15 | apt: 16 | sources: 17 | - ubuntu-toolchain-r-test 18 | packages: 19 | - libstdc++-4.9-dev -------------------------------------------------------------------------------- /src/routes/Page2.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Layout from './Layout'; 3 | 4 | export default class Page2 extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | page: 2 9 | }; 10 | } 11 | 12 | render() { 13 | return ( 14 | 15 |
16 | page { this.state.page } 17 |
18 |
); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/routes/Page1.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Layout from './Layout'; 3 | 4 | export default class Page1 extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | page: 1 9 | }; 10 | } 11 | 12 | render() { 13 | return ( 14 | 15 |
16 | page { this.state.page } 17 |
18 |
); 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /api/utils/debug.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | 3 | function debug(s) { 4 | if (process.env['NODE_DEBUG'] === 'true') { 5 | var args = Array.prototype.slice.call(arguments); 6 | args = args.map(function (arg) { 7 | if (typeof arg === 'object' || typeof arg === 'number') return arg; 8 | else return chalk.green(arg); 9 | }); 10 | console.log.apply(null, args); 11 | } 12 | } 13 | 14 | module.exports = debug; 15 | -------------------------------------------------------------------------------- /test/client/projectsPage.test.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | const expect = chai.expect; 3 | import React, {Component} from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import TestUtils from 'react-addons-test-utils'; 6 | import { getStore } from '../../src/store/configureStore'; 7 | 8 | 9 | describe('Store', () => { 10 | it('store console log', () => { 11 | console.log(getStore().getState().projectsModel.projects) 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/client/config.js: -------------------------------------------------------------------------------- 1 | var jsdom = require('jsdom'); 2 | 3 | function createDOM(done) { 4 | jsdom.env( 5 | '', 6 | ["http://code.jquery.com/jquery.js"], 7 | function (err, window) { 8 | global.document = window.document; 9 | global.window = window; 10 | global.$ = window.jQuery; 11 | done(); 12 | } 13 | ); 14 | } 15 | 16 | before(function(done) { 17 | this.timeout(20000); 18 | createDOM(done); 19 | }); 20 | -------------------------------------------------------------------------------- /test/endpoint/testingProject/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testingproject", 3 | "version": "1.0.0", 4 | "description": "a testing project", 5 | "main": "index.js", 6 | "scripts": { 7 | "track": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "The Project Author", 11 | "license": "ISC", 12 | "dependencies": { 13 | "colors": "^1.1.2", 14 | "nebulis-endpoint": "^0.4.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/controllers/MemberController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * MemberController 3 | * 4 | * @description :: Server-side logic for managing members 5 | * @help :: See http://sailsjs.org/#!/documentation/concepts/Controllers 6 | */ 7 | 8 | module.exports = { 9 | getMembers: function (req, res) { 10 | Member.find({}).exec(function (err, members) { 11 | if (err) { 12 | res.status(500); 13 | res.send(err); 14 | } 15 | else res.send(members); 16 | }); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /views/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/server/config.js: -------------------------------------------------------------------------------- 1 | const sails = require('sails'); 2 | const nebugit = require('../../gitnet'); 3 | const fs = require('fs'); 4 | const rimraf = require('rimraf'); 5 | 6 | before(function(done) { 7 | this.timeout(20000); 8 | rimraf('.tmp/localDiskTestingDb.db', () => { 9 | fs.mkdir(`${process.env['REPO_LOCATION']}`, function (err) { 10 | nebugit.listen(); 11 | sails.lift(function() { 12 | done(); 13 | }); 14 | }); 15 | }); 16 | }); 17 | 18 | after((done) => { 19 | nebugit.stop(); 20 | done(); 21 | }); -------------------------------------------------------------------------------- /tasks/config/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `clean` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Remove the files and folders in your Sails app's web root 7 | * (conventionally a hidden directory called `.tmp/public`). 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-clean 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('clean', { 16 | dev: ['.tmp/public/**'], 17 | build: ['www'] 18 | }); 19 | 20 | grunt.loadNpmTasks('grunt-contrib-clean'); 21 | }; 22 | -------------------------------------------------------------------------------- /tasks/config/uglify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `uglify` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minify client-side JavaScript files using UglifyJS. 7 | * 8 | * For usage docs see: 9 | * https://github.com/gruntjs/grunt-contrib-uglify 10 | * 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('uglify', { 15 | dist: { 16 | src: ['.tmp/public/concat/production.js'], 17 | dest: '.tmp/public/min/production.min.js' 18 | } 19 | }); 20 | 21 | grunt.loadNpmTasks('grunt-contrib-uglify'); 22 | }; 23 | -------------------------------------------------------------------------------- /test/endpoint/config.js: -------------------------------------------------------------------------------- 1 | const sails = require('sails'); 2 | const nebugit = require('../../gitnet'); 3 | const fs = require('fs'); 4 | var rimraf = require('rimraf'); 5 | 6 | before(function(done) { 7 | this.timeout(20000); 8 | rimraf(`.tmp/localDiskTestingDb.db`, () => { 9 | fs.mkdir(`${process.env['REPO_LOCATION']}`, function (err) { 10 | nebugit.listen(); 11 | sails.lift(function() { 12 | done(); 13 | }); 14 | }); 15 | }); 16 | }); 17 | 18 | after((done) => { 19 | nebugit.stop(); 20 | done(); 21 | }); 22 | 23 | exports.nebugit = nebugit; 24 | -------------------------------------------------------------------------------- /tasks/register/syncAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `syncAssets` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `watch` task (`tasks/config/watch.js`). 8 | * 9 | * For more information see: 10 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/sync-assets-js 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | grunt.registerTask('syncAssets', [ 15 | 'jst:dev', 16 | 'less:dev', 17 | 'sync:dev', 18 | 'coffee:dev' 19 | ]); 20 | }; 21 | -------------------------------------------------------------------------------- /api/models/Team.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Team.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/documentation/concepts/models-and-orm/models 6 | */ 7 | 8 | module.exports = { 9 | 10 | attributes: { 11 | project: { 12 | model: 'project', 13 | }, 14 | members: { 15 | collection: 'member', 16 | via: 'teams', 17 | dominant: true, 18 | }, 19 | endpoints: { 20 | collection: 'endpoint', 21 | via: 'team', 22 | }, 23 | name: { 24 | type: 'string', 25 | }, 26 | }, 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /tasks/register/compileAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `compileAssets` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `default`, `prod`, `build`, and 8 | * `buildProd` tasklists. 9 | * 10 | * For more information see: 11 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/compile-assets-js 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | grunt.registerTask('compileAssets', [ 16 | 'clean:dev', 17 | 'jst:dev', 18 | 'less:dev', 19 | 'copy:dev', 20 | 'coffee:dev' 21 | ]); 22 | }; 23 | -------------------------------------------------------------------------------- /config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * (sails.config.bootstrap) 4 | * 5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 7 | * 8 | * For more information on bootstrapping your app, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.bootstrap.html 10 | */ 11 | 12 | module.exports.bootstrap = function(cb) { 13 | 14 | // It's very important to trigger this callback method when you are finished 15 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 16 | cb(); 17 | }; 18 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "rules": { 7 | "indent": 0, 8 | "no-script-url": 0, 9 | "react/jsx-indent-props": 0, 10 | "no-console": 0, 11 | "comma-dangle": 0, 12 | "prefer-template": 0, 13 | "semi": 0, 14 | "max-len": 0, 15 | "space-infix-ops": 0, 16 | "brace-style": 0, 17 | "react/jsx-space-before-closing": 0, 18 | "no-else-return": 0, 19 | "spaced-comment": 0, 20 | "no-trailing-spaces": 0, 21 | "padded-blocks": 0, 22 | "quotes": 0, 23 | "no-mixed-spaces-and-tabs": 0, 24 | "space-in-parens": 0, 25 | "arrow-body-style": 0, 26 | "one-var-declaration-per-line": 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | $vin-blue: #5bb7db; 3 | $vin-green: #60b044; 4 | $vin-red: #ff0000; 5 | 6 | /* Styles */ 7 | body { 8 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 9 | line-height: 1.4em; 10 | color: #4d4d4d; 11 | min-width: 230px; 12 | max-width: 550px; 13 | margin: 0 auto; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-font-smoothing: antialiased; 16 | font-smoothing: antialiased; 17 | font-weight: 300; 18 | } 19 | 20 | td { 21 | padding: 12px; 22 | } 23 | 24 | h2 { 25 | color: $vin-blue; 26 | } 27 | 28 | .savings { color: $vin-green; } 29 | .loss { color: $vin-red; } 30 | input.small { width: 46px; } 31 | td.fuel-savings-label { width: 175px; } 32 | -------------------------------------------------------------------------------- /tasks/register/linkAssetsBuild.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `linkAssetsBuild` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `build` tasklist. 8 | * 9 | * For more information see: 10 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/link-assets-build-js 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | grunt.registerTask('linkAssetsBuild', [ 15 | 'sails-linker:devJsRelative', 16 | 'sails-linker:devStylesRelative', 17 | 'sails-linker:devTpl', 18 | 'sails-linker:devJsRelativeJade', 19 | 'sails-linker:devStylesRelativeJade', 20 | 'sails-linker:devTplJade' 21 | ]); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | export default class Counter extends Component { 3 | constructor(props) { 4 | super(props); 5 | if (typeof window.counter === 'undefined') { 6 | window.counter = 0; 7 | } 8 | this.state = { 9 | value : window.counter, 10 | mounted : true 11 | }; 12 | 13 | this.interval = setInterval(() => { 14 | if (this.state.mounted) { 15 | window.counter++; 16 | this.setState({ 17 | value: window.counter 18 | }); 19 | } 20 | },1000); 21 | } 22 | componentWillUnmount() { 23 | clearInterval(this.interval); 24 | this.setState({ 25 | mounted : false 26 | }); 27 | } 28 | render() { 29 | return (
Counter: { this.state.value }
); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /api/models/Project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Project.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/documentation/concepts/models-and-orm/models 6 | */ 7 | 8 | module.exports = { 9 | 10 | attributes: { 11 | name: { 12 | type: 'string', 13 | required: true 14 | }, 15 | slug: { 16 | type: 'string', 17 | unique: true 18 | }, 19 | gitLink: { 20 | type: 'url', 21 | required: true, 22 | unique: true 23 | }, 24 | endpoints: { 25 | collection: 'endpoint', 26 | via: 'project', 27 | }, 28 | teams: { 29 | collection: 'team', 30 | via: 'project', 31 | }, 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /tasks/register/linkAssetsBuildProd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `linkAssetsBuildProd` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `buildProd` tasklist. 8 | * 9 | * For more information see: 10 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/link-assets-build-prod-js 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | grunt.registerTask('linkAssetsBuildProd', [ 15 | 'sails-linker:prodJsRelative', 16 | 'sails-linker:prodStylesRelative', 17 | 'sails-linker:devTpl', 18 | 'sails-linker:prodJsRelativeJade', 19 | 'sails-linker:prodStylesRelativeJade', 20 | 'sails-linker:devTplJade' 21 | ]); 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/register/linkAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `linkAssets` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `default` tasklist and the `watch` task 8 | * (but only if the `grunt-sails-linker` package is in use). 9 | * 10 | * For more information see: 11 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/link-assets-js 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | grunt.registerTask('linkAssets', [ 16 | 'sails-linker:devJs', 17 | 'sails-linker:devStyles', 18 | 'sails-linker:devTpl', 19 | 'sails-linker:devJsJade', 20 | 'sails-linker:devStylesJade', 21 | 'sails-linker:devTplJade' 22 | ]); 23 | }; 24 | -------------------------------------------------------------------------------- /api/policies/sessionAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sessionAuth 3 | * 4 | * @module :: Policy 5 | * @description :: Simple policy to allow any authenticated user 6 | * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;` 7 | * @docs :: http://sailsjs.org/#!/documentation/concepts/Policies 8 | * 9 | */ 10 | module.exports = function(req, res, next) { 11 | 12 | // User is allowed, proceed to the next policy, 13 | // or if this is the last policy, the controller 14 | if (req.session.authenticated) { 15 | return next(); 16 | } 17 | 18 | // User is not allowed 19 | // (default res.forbidden() behavior can be overridden in `config/403.js`) 20 | return res.forbidden('You are not permitted to perform this action.'); 21 | }; 22 | -------------------------------------------------------------------------------- /api/models/Member.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Member.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/documentation/concepts/models-and-orm/models 6 | */ 7 | 8 | module.exports = { 9 | 10 | attributes: { 11 | gitAccess: { 12 | type: 'string' 13 | }, 14 | admin: { 15 | type: 'boolean', 16 | required: true, 17 | defaultsTo: false 18 | }, 19 | endpoints: { 20 | collection: 'endpoint', 21 | via: 'member', 22 | }, 23 | username: { 24 | type: 'string', 25 | unique: true, 26 | }, 27 | fullname: { 28 | type: 'string', 29 | }, 30 | teams: { 31 | collection: 'team', 32 | via: 'members', 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /tasks/register/prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `prod` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist will be executed instead of `default` when 7 | * your Sails app is lifted in a production environment (e.g. using 8 | * `NODE_ENV=production node app`). 9 | * 10 | * For more information see: 11 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/prod-js 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | grunt.registerTask('prod', [ 16 | 'compileAssets', 17 | 'concat', 18 | 'uglify', 19 | 'cssmin', 20 | 'sails-linker:prodJs', 21 | 'sails-linker:prodStyles', 22 | 'sails-linker:devTpl', 23 | 'sails-linker:prodJsJade', 24 | 'sails-linker:prodStylesJade', 25 | 'sails-linker:devTplJade' 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {responsiveStateReducer} from 'redux-responsive'; 2 | import {combineReducers} from 'redux'; 3 | import {responsiveDrawer} from 'material-ui-responsive-drawer'; 4 | import {reducer as projectsModel} from './../reducers/projectReducers'; 5 | import {reducer as teamsModel} from './../reducers/teamReducers'; 6 | import {reducer as membersModel} from './../reducers/memberReducers'; 7 | 8 | import ghoulie from 'ghoulie'; 9 | // ghoulieReducer intercepts redux events and outputs them to console.log for debugging 10 | let ghoulieReducer = ghoulie.reducer(); 11 | 12 | const rootReducer = combineReducers({ 13 | browser: responsiveStateReducer, 14 | responsiveDrawer: responsiveDrawer, 15 | projectsModel, 16 | teamsModel, 17 | membersModel, 18 | ghoulieReducer 19 | }); 20 | 21 | export default rootReducer; 22 | -------------------------------------------------------------------------------- /tasks/config/coffee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `coffee` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Compile CoffeeScript files located in `assets/js` into Javascript 7 | * and generate new `.js` files in `.tmp/public/js`. 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-coffee 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('coffee', { 16 | dev: { 17 | options: { 18 | bare: true, 19 | sourceMap: true, 20 | sourceRoot: './' 21 | }, 22 | files: [{ 23 | expand: true, 24 | cwd: 'assets/js/', 25 | src: ['**/*.coffee'], 26 | dest: '.tmp/public/js/', 27 | ext: '.js' 28 | }] 29 | } 30 | }); 31 | 32 | grunt.loadNpmTasks('grunt-contrib-coffee'); 33 | }; 34 | -------------------------------------------------------------------------------- /src/api/todos.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | // example of how to manually perform a fetch() outside of the redux-helpers lib 4 | 5 | import es6promise from 'es6-promise'; 6 | es6promise.polyfill(); 7 | import fetch from 'isomorphic-fetch'; 8 | 9 | function createTodo(name, callback) { 10 | fetch('/api/todos', { 11 | method: 'POST', 12 | credentials: 'include', 13 | headers: { 14 | 'Accept': 'application/json', 15 | 'Content-Type': 'application/json' 16 | }, 17 | body: JSON.stringify({ 18 | name: name 19 | }) 20 | }) 21 | .then(function (response) { 22 | if (response.status >= 400) { 23 | throw new Error("Bad response from server"); 24 | } 25 | return response.json(); 26 | }) 27 | .then(function (todos) { 28 | callback(null, todos); 29 | }) 30 | .catch(function (err) { 31 | console.log(err); 32 | callback(err); 33 | }); 34 | } 35 | */ 36 | -------------------------------------------------------------------------------- /tasks/config/watch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `watch` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Run predefined tasks whenever watched file patterns are added, changed or deleted. 7 | * 8 | * Watch for changes on: 9 | * - files in the `assets` folder 10 | * - the `tasks/pipeline.js` file 11 | * and re-run the appropriate tasks. 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-watch 15 | * 16 | */ 17 | module.exports = function(grunt) { 18 | 19 | grunt.config.set('watch', { 20 | assets: { 21 | 22 | // Assets to watch: 23 | files: ['assets/**/*', 'tasks/pipeline.js', '!**/node_modules/**'], 24 | 25 | // When assets are changed: 26 | tasks: ['syncAssets' , 'linkAssets' ] 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-contrib-watch'); 31 | }; 32 | -------------------------------------------------------------------------------- /src/routes/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import Layout from './Layout'; 4 | 5 | export default class IndexPage extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | const styles = { 12 | logoContainer: { 13 | opacity: 0.15, 14 | paddingTop: '100', 15 | margin: '0 auto', 16 | textAlign: 'center', 17 | objectPosition: '400px 50px' 18 | }, 19 | logo: { 20 | height: '50%', 21 | width: '50%', 22 | } 23 | } 24 | 25 | return ( 26 | 27 |
28 | nebulis-logo 29 |
30 |
); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tasks/config/cssmin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compress CSS files. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minify the intermediate concatenated CSS stylesheet which was 7 | * prepared by the `concat` task at `.tmp/public/concat/production.css`. 8 | * 9 | * Together with the `concat` task, this is the final step that minifies 10 | * all CSS files from `assets/styles/` (and potentially your LESS importer 11 | * file from `assets/styles/importer.less`) 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-cssmin 15 | * 16 | */ 17 | module.exports = function(grunt) { 18 | 19 | grunt.config.set('cssmin', { 20 | dist: { 21 | src: ['.tmp/public/concat/production.css'], 22 | dest: '.tmp/public/min/production.min.css' 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/config/less.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `less` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Compile your LESS files into a CSS stylesheet. 7 | * 8 | * By default, only the `assets/styles/importer.less` is compiled. 9 | * This allows you to control the ordering yourself, i.e. import your 10 | * dependencies, mixins, variables, resets, etc. before other stylesheets) 11 | * 12 | * For usage docs see: 13 | * https://github.com/gruntjs/grunt-contrib-less 14 | * 15 | */ 16 | module.exports = function(grunt) { 17 | 18 | grunt.config.set('less', { 19 | dev: { 20 | files: [{ 21 | expand: true, 22 | cwd: 'assets/styles/', 23 | src: ['importer.less'], 24 | dest: '.tmp/public/styles/', 25 | ext: '.css' 26 | }] 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-contrib-less'); 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/register/buildProd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `buildProd` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist will be executed instead of `build` if you 7 | * run `sails www` in a production environment, e.g.: 8 | * `NODE_ENV=production sails www` 9 | * 10 | * This generates a folder containing your compiled (and usually minified) 11 | * assets. The most common use case for this is bundling up files to 12 | * deploy to a CDN. 13 | * 14 | * For more information see: 15 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/build-prod-js 16 | * 17 | */ 18 | module.exports = function(grunt) { 19 | grunt.registerTask('buildProd', [ 20 | 'compileAssets', 21 | 'concat', 22 | 'uglify', 23 | 'cssmin', 24 | 'linkAssetsBuildProd', 25 | 'clean:build', 26 | 'copy:build' 27 | ]); 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /api/models/Todo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | attributes: { 3 | name: { 4 | type: 'string', 5 | defaultsTo: 'null' 6 | }, 7 | completed: { 8 | type: 'boolean', 9 | defaultsTo: false 10 | } 11 | }, 12 | getAll: function (callback) { 13 | this.find({}).exec(callback); 14 | }, 15 | createTodo: function (name, callback) { 16 | var values; 17 | if (name) { 18 | values = { 19 | name: name 20 | }; 21 | } 22 | this.create(values).exec(callback); 23 | }, 24 | deleteAll: function (callback) { 25 | this.destroy({}).exec(callback); 26 | }, 27 | updateTodo: function (data, callback) { 28 | var id = data.id; 29 | this.update({ 30 | id: id 31 | }, data).exec(callback); 32 | }, 33 | deleteTodo: function (id, callback) { 34 | this.destroy({ 35 | id: id 36 | }).exec(callback); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /tasks/config/sync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `sync` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Synchronize files from the `assets` folder to `.tmp/public`, 7 | * smashing anything that's already there. 8 | * 9 | * This task synchronizes one directory with another (like rsync). 10 | * In the default Sails asset pipeline, it plays very similar role 11 | * to `grunt-contrib-copy`, but copies only those files that have 12 | * actually changed since the last time the task was run. 13 | * 14 | * For usage docs see: 15 | * https://github.com/tomusdrw/grunt-sync 16 | * 17 | */ 18 | module.exports = function(grunt) { 19 | 20 | grunt.config.set('sync', { 21 | dev: { 22 | files: [{ 23 | cwd: './assets', 24 | src: ['**/*.!(coffee|less)'], 25 | dest: '.tmp/public' 26 | }] 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-sync'); 31 | }; 32 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Router, IndexRoute, browserHistory } from 'react-router'; 3 | 4 | import Home from './routes/Home'; 5 | import Login from './routes/Login'; 6 | import ProjectPage from './routes/projects/index'; 7 | import ProjectContainer from './routes/projects/view'; 8 | import MembersContainer from './routes/members/index'; 9 | import MemberContainer from './routes/members/view'; 10 | import Page2 from './routes/Page2'; 11 | import NotFound from './routes/NotFound'; 12 | 13 | export default ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); -------------------------------------------------------------------------------- /config/env/dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Development environment settings 3 | * 4 | * This file can include shared settings for a development team, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the development * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'someMongodbServer' 22 | // } 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/MemberListItems.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Members } from './Members'; 4 | import { ListItem } from 'material-ui/List'; 5 | import AccountPlus from 'material-ui/svg-icons/social/person'; 6 | import Avatar from 'material-ui/Avatar'; 7 | 8 | export default class MemberListItems extends Component { 9 | render() { 10 | const member = this.props.member; 11 | return ( 12 | } />}> 17 | 18 | )} 19 | } 20 | 21 | 22 | 23 | // 24 | // 25 | // 26 | // 27 | // 28 | // 29 | // 30 | // × 31 | // 32 | -------------------------------------------------------------------------------- /src/redux/index.js: -------------------------------------------------------------------------------- 1 | import es6promise from 'es6-promise'; 2 | es6promise.polyfill(); 3 | 4 | import { combineReducers, createStore, applyMiddleware } from 'redux'; 5 | import thunk from 'redux-thunk'; 6 | import ghoulie from 'ghoulie'; 7 | 8 | // REDUCERS ---------------------------- 9 | 10 | // import the generated reducer and actions 11 | import {reducer as todosModel, actions as todosActions} from './todos'; 12 | 13 | // export the actions so they can be used elsewhere in the app 14 | export {todosActions}; 15 | 16 | // ghoulieReducer intercepts redux events and outputs them to console.log for debugging 17 | let ghoulieReducer = ghoulie.reducer(); 18 | 19 | // combine the reducers 20 | const rootReducer = combineReducers({ 21 | todosModel, 22 | ghoulieReducer 23 | }); 24 | 25 | // create the redux store 26 | export const store = createStore( 27 | rootReducer, 28 | applyMiddleware(thunk) 29 | ); 30 | 31 | export function getStore() { 32 | return store; 33 | } 34 | 35 | window.getStore = getStore; 36 | -------------------------------------------------------------------------------- /tasks/register/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `build` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist will be executed if you run `sails www` or 7 | * `grunt build` in a development environment. It generates a 8 | * folder containing your compiled assets, e.g. for troubleshooting 9 | * issues with other Grunt plugins, bundling assets for an Electron 10 | * or PhoneGap app, or deploying your app's flat files to a CDN. 11 | * 12 | * Note that when running `sails www` in a production environment (with the 13 | * `NODE_ENV` environment variable set to 'production') the `buildProd` task 14 | * will be run instead of this one. 15 | * 16 | * For more information see: 17 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/build-js 18 | * 19 | */ 20 | module.exports = function(grunt) { 21 | grunt.registerTask('build', [ 22 | 'compileAssets', 23 | 'linkAssetsBuild', 24 | 'clean:build', 25 | 'copy:build' 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /assets/styles/importer.less: -------------------------------------------------------------------------------- 1 | /** 2 | * importer.less 3 | * 4 | * By default, new Sails projects are configured to compile this file 5 | * from LESS to CSS. Unlike CSS files, LESS files are not compiled and 6 | * included automatically unless they are imported below. 7 | * 8 | * The LESS files imported below are compiled and included in the order 9 | * they are listed. Mixins, variables, etc. should be imported first 10 | * so that they can be accessed by subsequent LESS stylesheets. 11 | * 12 | * (Just like the rest of the asset pipeline bundled in Sails, you can 13 | * always omit, customize, or replace this behavior with SASS, SCSS, 14 | * or any other Grunt tasks you like.) 15 | */ 16 | 17 | 18 | 19 | // For example: 20 | // 21 | // @import 'variables/colors.less'; 22 | // @import 'mixins/foo.less'; 23 | // @import 'mixins/bar.less'; 24 | // @import 'mixins/baz.less'; 25 | // 26 | // @import 'styleguide.less'; 27 | // @import 'pages/login.less'; 28 | // @import 'pages/signup.less'; 29 | // 30 | // etc. 31 | -------------------------------------------------------------------------------- /tasks/config/concat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `concat` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Concatenates the contents of multiple JavaScript and/or CSS files 7 | * into two new files, each located at `concat/production.js` and 8 | * `concat/production.css` respectively in `.tmp/public/concat`. 9 | * 10 | * This is used as an intermediate step to generate monolithic files 11 | * that can then be passed in to `uglify` and/or `cssmin` for minification. 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-concat 15 | * 16 | */ 17 | module.exports = function(grunt) { 18 | 19 | grunt.config.set('concat', { 20 | js: { 21 | src: require('../pipeline').jsFilesToInject, 22 | dest: '.tmp/public/concat/production.js' 23 | }, 24 | css: { 25 | src: require('../pipeline').cssFilesToInject, 26 | dest: '.tmp/public/concat/production.css' 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-contrib-concat'); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/Teams.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Team from './Team'; 3 | import {Link} from 'react-router'; 4 | import {List, ListItem } from 'material-ui/List'; 5 | import Subheader from 'material-ui/Subheader'; 6 | 7 | export default class Teams extends Component { 8 | render() { 9 | return ( 10 |
11 | { this.renderTeams() } 12 |
); 13 | } 14 | 15 | renderTeams() { 16 | if (this.props.teams.length) { 17 | return ( 18 | 19 | Teams 20 | { this.props.teams.map((team, index) => { 21 | let onTeamTouch = () => { 22 | this.props.onTeamTouch(team.id); 23 | }; 24 | let onDownload = () => { 25 | this.props.onDownload(team.id); 26 | } 27 | return ( 28 | 32 | ); 33 | })} 34 | 35 | ); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/controllers/ProjectController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ProjectController 3 | * 4 | * @description :: Server-side logic for managing projects 5 | * @help :: See http://sailsjs.org/#!/documentation/concepts/Controllers 6 | */ 7 | 8 | module.exports = { 9 | getProjects: function (req, res) { 10 | Project.find({}).exec(function (err, projects) { 11 | if (err) { 12 | res.status(500); 13 | res.send(err); 14 | } 15 | else res.send(projects); 16 | }); 17 | }, 18 | getProject: function (req, res) { 19 | Project.find({id: req.params.id}).exec(function (err, project) { 20 | if (err) { 21 | res.status(500); 22 | res.send(err); 23 | } else res.send(project); 24 | }) 25 | }, 26 | 27 | create: async (req, res) => { 28 | try { 29 | const name = req.body.name; 30 | const gitLink = req.body.gitLink; 31 | const slug = gitLink.split('/')[4]; 32 | const project = await Project.create({ name, slug, gitLink }); 33 | res.send(project); 34 | } catch (err) { 35 | res.send(err); 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /api/controllers/TeamController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TeamController 3 | * 4 | * @description :: Server-side logic for managing teams 5 | * @help :: See http://sailsjs.org/#!/documentation/concepts/Controllers 6 | */ 7 | 8 | module.exports = { 9 | 10 | //TODO: create test for this 11 | create: async (req, res) => { 12 | try { 13 | team = await Team.create({ 14 | name: 'unnamed group', 15 | project: req.body.project 16 | }); 17 | await team.members.add(req.body.members); 18 | await team.save(); 19 | res.send(team); 20 | } catch (err) { 21 | sails.log(err); 22 | res.send(err); 23 | } 24 | }, 25 | index: (req, res) => { 26 | sails.log('create team'); 27 | sails.log(req.body); 28 | res.send('success'); 29 | }, 30 | projectIndex: (req, res) => { 31 | sails.log('view teams for project'); 32 | 33 | Team.find({project: req.params['id']}).populate('members').exec((err, teams) => { res.send(teams)}); 34 | }, 35 | view: (req, res) => { 36 | sails.log('create team'); 37 | sails.log(req.body); 38 | res.send('success'); 39 | } 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /tasks/register/default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `default` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This is the default Grunt tasklist that will be executed if you 7 | * run `grunt` in the top level directory of your app. It is also 8 | * called automatically when you start Sails in development mode using 9 | * `sails lift` or `node app`. 10 | * 11 | * Note that when lifting your app with a custom environment setting 12 | * (i.e. `sails.config.environment`), Sails will look for a tasklist file 13 | * with the same name and run that instead of this one. 14 | * 15 | * > Note that as a special case for compatibility/historial reasons, if 16 | * > your environment is "production", and Sails cannot find a tasklist named 17 | * > `production.js`, it will attempt to run the `prod.js` tasklist as well 18 | * > before defaulting to `default.js`. 19 | * 20 | * For more information see: 21 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/default-js 22 | * 23 | */ 24 | module.exports = function (grunt) { 25 | grunt.registerTask('default', ['compileAssets', 'linkAssets', 'watch']); 26 | }; 27 | -------------------------------------------------------------------------------- /src/reducers/todoReducers.js: -------------------------------------------------------------------------------- 1 | import {createReducer} from '../utils/redux-helpers.js'; 2 | import getInitialState from './initialState' 3 | 4 | // export default function getInitialState() { 5 | // return { 6 | // error: null, 7 | // loading: false, 8 | // todos: [] 9 | // } 10 | // } 11 | 12 | // TODOS REDUCER 13 | 14 | 15 | const reducer = createReducer(getInitialState, { 16 | GET_TODOS: function(state, action) { 17 | return Object.assign({}, state, { 18 | loading: true 19 | }); 20 | }, 21 | GET_TODOS_SUCCESS: function(state, action) { 22 | return Object.assign({}, state, { 23 | loading: false, 24 | todos: action.results // response JSON body is available in action.results 25 | }); 26 | }, 27 | 28 | CREATE_TODO: function(state, action) { 29 | return Object.assign({}, state, { 30 | loading: true 31 | }); 32 | }, 33 | CREATE_TODO_SUCCESS: function(state, action) { 34 | return Object.assign({}, state, { 35 | loading: false 36 | }); 37 | }, 38 | 39 | TODOS_ERROR: function(state, action) { 40 | return Object.assign({}, state, { 41 | loading: false, 42 | error: action.error 43 | }); 44 | }, 45 | }); 46 | 47 | export {reducer}; 48 | -------------------------------------------------------------------------------- /test/client/Login.test.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | const expect = chai.expect; 3 | import React, {Component} from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import TestUtils from 'react-addons-test-utils'; 6 | import { shallow } from 'enzyme'; 7 | import { assert } from 'chai'; 8 | 9 | import Login from '../../src/routes/Login'; 10 | 11 | let testComponent; 12 | 13 | describe('Login component', () => { 14 | // before(function(done) { 15 | // testComponent = TestUtils.renderIntoDocument( 16 | // 17 | // ); 18 | // done(); 19 | // }); 20 | it('should render', () => { 21 | const wrapper = shallow(); 22 | assert.ok(wrapper); 23 | }); 24 | 25 | // it('renders correctly', () => { 26 | // 27 | // let testNode = ReactDOM.findDOMNode(testComponent); 28 | // 29 | // expect(testNode.nodeName).to.be.equal('DIV'); 30 | // expect(testNode.className).to.be.equal('loginButton'); 31 | // expect(testNode.getElementsByTagName('A').length).to.be.equal(1); 32 | // expect(testNode.getElementsByTagName('BUTTON')[0]).to.be.an('object'); 33 | // expect(testNode.getElementsByTagName('A')[0]).to.be.an('object'); 34 | // }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/reducers/memberReducers.js: -------------------------------------------------------------------------------- 1 | import {createReducer} from '../utils/redux-helpers.js'; 2 | import getInitialState from './initialState' 3 | 4 | // export default function getInitialState() { 5 | // return { 6 | // error: null, 7 | // loading: false, 8 | // members: [] 9 | // } 10 | // } 11 | 12 | // MEMBERS REDUCER 13 | 14 | 15 | const reducer = createReducer(getInitialState, { 16 | GET_MEMBERS: function(state, action) { 17 | return Object.assign({}, state, { 18 | loading: true 19 | }); 20 | }, 21 | GET_MEMBERS_SUCCESS: function(state, action) { 22 | return Object.assign({}, state, { 23 | loading: false, 24 | members: action.results // response JSON body is available in action.results 25 | }); 26 | }, 27 | CREATE_MEMBER: function(state, action) { 28 | return Object.assign({}, state, { 29 | loading: true, 30 | }); 31 | }, 32 | CREATE_MEMBER_SUCCESS: function(state, action) { 33 | return Object.assign({}, state, { 34 | loading: false, 35 | }); 36 | }, 37 | DELETE_MEMBER: function(state, action) { 38 | return Object.assign({}, state, { 39 | // loading: false, TODO: Add functionality 40 | 41 | }); 42 | }, 43 | }); 44 | 45 | export {reducer}; 46 | -------------------------------------------------------------------------------- /test/server/Member.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiHttp = require('chai-http'); 3 | 4 | const should = chai.should(); 5 | chai.use(chaiHttp); 6 | const expect = chai.expect; 7 | 8 | describe('Member Model Relationships', function() { 9 | let member; 10 | let endpoint; 11 | let team; 12 | 13 | before(async function() { 14 | member = await Member.create({username: 'trustyPartner'}); 15 | endpoint = await Endpoint.create({member: member.id}); 16 | team = await Team.create({name: 'greatness'}); 17 | member.teams.add(team.id); 18 | await member.save(); 19 | }); 20 | after(async function() { 21 | await Endpoint.destroy(endpoint.id); 22 | await Member.destroy(member.id); 23 | await Team.destroy(team.id); 24 | }); 25 | it('should be able to get a list of it\'s endpoints', async function() { 26 | const members = await Member.find(member.id).populate('endpoints'); 27 | expect(members[0].endpoints.length).to.be.equal(1); 28 | }); 29 | it('should be able to list it\'s team memberships', async function() { 30 | const members = await Member.find(member.id).populate('teams'); 31 | expect(members[0].teams.length).to.be.equal(1); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/components/Todos.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class Todos extends Component { 4 | render() { 5 | return ( 6 |
7 | { this.renderTodos() } 8 |
); 9 | } 10 | 11 | renderTodos() { 12 | if (this.props.todos.length) { 13 | return (
    14 | { this.props.todos.map((todo, index) => { 15 | let completeHandler = () => { 16 | this.props.onToggleCompleted(todo.id, !todo.completed); 17 | }; 18 | let deleteHandler = () => { 19 | this.props.onDelete(todo.id); 20 | }; 21 | return (); 22 | }) } 23 |
); 24 | } 25 | } 26 | } 27 | 28 | class Todo extends Component { 29 | render() { 30 | const todo = this.props.todo; 31 | const decoration = todo.completed ? 'line-through' : 'none'; 32 | return ( 33 |
  • 34 | 35 | { todo.name } 36 | 37 |   38 | 39 | ✓ 40 | 41 |   42 | 43 | × 44 | 45 |
  • ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

    <%= __('Welcome to PencilPals!') %>

    23 |

    <%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

    24 |

    <%= i18n('That\'s right-- you can use either i18n() or __()') %>

    25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /src/components/AddTeam.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dialog from 'material-ui/Dialog'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | import { List, ListItem } from 'material-ui/List'; 5 | import Checkbox from 'material-ui/Checkbox'; 6 | 7 | export default function AddTeam({members, open, onClose, onSave, handleMemberClick}) { 8 | console.log('model members', members) 9 | const renderModel = ( 10 |
    11 | 12 | {members && members.map((member, i) => { 13 | return ( 14 | handleMemberClick(member)}/>} 17 | primaryText={`Username: ${member.username}`} 18 | />); 19 | })} 20 | 21 | 27 | 33 |
    34 | ); 35 | return ( 36 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /tasks/config/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `copy` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Copy files and/or folders from your `assets/` directory into 7 | * the web root (`.tmp/public`) so they can be served via HTTP, 8 | * and also for further pre-processing by other Grunt tasks. 9 | * 10 | * #### Normal usage (`sails lift`) 11 | * Copies all directories and files (except CoffeeScript and LESS) 12 | * from the `assets/` folder into the web root -- conventionally a 13 | * hidden directory located `.tmp/public`. 14 | * 15 | * #### Via the `build` tasklist (`sails www`) 16 | * Copies all directories and files from the .tmp/public directory into a www directory. 17 | * 18 | * For usage docs see: 19 | * https://github.com/gruntjs/grunt-contrib-copy 20 | * 21 | */ 22 | module.exports = function(grunt) { 23 | 24 | grunt.config.set('copy', { 25 | dev: { 26 | files: [{ 27 | expand: true, 28 | cwd: './assets', 29 | src: ['**/*.!(coffee|less)'], 30 | dest: '.tmp/public' 31 | }] 32 | }, 33 | build: { 34 | files: [{ 35 | expand: true, 36 | cwd: '.tmp/public', 37 | src: ['**/*'], 38 | dest: 'www' 39 | }] 40 | } 41 | }); 42 | 43 | grunt.loadNpmTasks('grunt-contrib-copy'); 44 | }; 45 | -------------------------------------------------------------------------------- /test/server/Team.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiHttp = require('chai-http'); 3 | 4 | const should = chai.should(); 5 | chai.use(chaiHttp); 6 | const expect = chai.expect; 7 | 8 | describe('Team Model Relationships', function() { 9 | let team; 10 | let member1; 11 | let member2; 12 | let endpoint; 13 | 14 | before(async function() { 15 | team = await Team.create({name: 'greatness'}); 16 | member1 = await Member.create({username: 'user1'}); 17 | member2 = await Member.create({username: 'user2'}); 18 | endpoint = await Endpoint.create({team: team.id}); 19 | 20 | await team.members.add([member1.id, member2.id]); 21 | await team.save(); 22 | }); 23 | after(async function() { 24 | await Team.destroy(team.id); 25 | await Member.destroy({username: 'user1'}); 26 | await Member.destroy({username: 'user2'}); 27 | await Endpoint.destroy({team: team.id}); 28 | }); 29 | it('should be able to get a list of it\'s members', async function() { 30 | const teams = await Team.find(team.id).populate('members') 31 | expect(teams[0].members.length).to.be.equal(2); 32 | }); 33 | it('should be able to list it\'s endpoints', async function() { 34 | const teams = await Team.find(team.id).populate('endpoints'); 35 | expect(teams[0].endpoints.length).to.be.equal(1); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /api/models/Endpoint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Endpoint.js 3 | * 4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 5 | * @docs :: http://sailsjs.org/documentation/concepts/models-and-orm/models 6 | */ 7 | var rimraf = require('rimraf'); 8 | const request = require('request-promise'); 9 | 10 | module.exports = { 11 | 12 | attributes: { 13 | project: { 14 | model: 'project', 15 | }, 16 | member: { 17 | model: 'member', 18 | }, 19 | team: { 20 | model: 'team', 21 | }, 22 | }, 23 | 24 | //lifecycle callbacks 25 | 26 | afterCreate: (newRecord, cb) => { 27 | let res; 28 | const resetGit = async () => { 29 | try { 30 | res = await request.post(`http://localhost:7010/reset`); 31 | cb(); 32 | } catch (err) { 33 | sails.log.info('waiting for git sub system'); 34 | setTimeout(() => { 35 | resetGit(); 36 | }, 250); 37 | }; 38 | }; 39 | resetGit(); 40 | }, 41 | 42 | //TODO: make this work for multiple records 43 | afterDestroy: function(destroyedRecords, cb) { 44 | try { 45 | rimraf(process.env['REPO_LOCATION'] + '/' + destroyedRecords[0].id + '.git', () => { 46 | cb(); 47 | }); 48 | } 49 | catch (err) { 50 | cb(err); 51 | } 52 | }, 53 | }; -------------------------------------------------------------------------------- /src/constants/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const GET_PROJECTS = "GET_PROJECTS"; 2 | export const GET_PROJECTS_SUCCESS = "GET_PROJECTS_SUCCESS"; 3 | export const GET_PROJECT = "GET_PROJECT"; 4 | export const GET_PROJECT_SUCCESS = "GET_PROJECT_SUCCESS"; 5 | export const CREATE_PROJECT = "CREATE_PROJECT"; 6 | export const CREATE_PROJECT_SUCCESS = "CREATE_PROJECT_SUCCESS"; 7 | export const DOWNLOAD_PROJECT = "DOWNLOAD_PROJECT"; 8 | export const DOWNLOAD_PROJECT_SUCCESS = "DOWNLOAD_PROJECT_SUCCESS"; 9 | export const PROJECTS_ERROR = "PROJECTS_ERROR"; 10 | export const ADD_PROJECT = "ADD_PROJECT"; 11 | export const CLOSE_PROJECT = "CLOSE_PROJECT"; 12 | export const GET_TEAMS = "GET_TEAMS"; 13 | export const GET_TEAMS_SUCCESS = "GET_TEAMS_SUCCESS"; 14 | export const CREATE_TEAM = "CREATE_TEAM"; 15 | export const CREATE_TEAM_SUCCESS = "CREATE_TEAM_SUCCESS"; 16 | export const ADD_TEAM_MEMBER = "ADD_TEAM_MEMBER"; 17 | export const REMOVE_TEAM_MEMBER = "REMOVE_TEAM_MEMBER"; 18 | export const CLOSE_ADD_TEAM = "CLOSE_ADD_TEAM"; 19 | export const TEAMS_ERROR = "TEAMS_ERROR"; 20 | export const GET_MEMBERS = "GET_MEMBERS"; 21 | export const GET_MEMBERS_SUCCESS = "GET_MEMBERS_SUCCESS"; 22 | export const CREATE_MEMBER = "CREATE_MEMBER"; 23 | export const CREATE_MEMBER_SUCCESS = "CREATE_MEMBER_SUCCESS"; 24 | export const MEMBERS_ERROR = "MEMBERS_ERROR"; 25 | export const DELETE_MEMBER = "DELETE_MEMBER"; 26 | -------------------------------------------------------------------------------- /src/routes/Login.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import nebulisActions from './../actions/nebulisActions'; 3 | import Layout from './Layout'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import Paper from 'material-ui/Paper'; 6 | import FontIcon from 'material-ui/FontIcon'; 7 | import { GithubCircleIcon } from 'mdi-material-ui'; 8 | 9 | const styles = { 10 | button: { 11 | margin: 'auto', 12 | }, 13 | h1: { 14 | padding: 'auto', 15 | marginTop: '47px', 16 | color: '#02C5FF' 17 | }, 18 | paper: { 19 | height: 300, 20 | width: 400, 21 | margin: 'auto', 22 | marginTop: '40px', 23 | textAlign: 'center', 24 | display: 'flex', 25 | flexDirection: 'column', 26 | justifyContent: 'space-around', 27 | alignItems: 'center' 28 | } 29 | } 30 | 31 | export default class Login extends Component { 32 | render() { 33 | return ( 34 | 35 | 36 |

    Nebulis Analytics

    37 | } 44 | /> 45 |
    46 |
    47 | ); 48 | } 49 | 50 | loginFunc() { 51 | nebulisActions.login(null); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Built-in Log Configuration 3 | * (sails.config.log) 4 | * 5 | * Configure the log level for your app, as well as the transport 6 | * (Underneath the covers, Sails uses Winston for logging, which 7 | * allows for some pretty neat custom transports/adapters for log messages) 8 | * 9 | * For more information on the Sails logger, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Logging 11 | */ 12 | 13 | module.exports.log = { 14 | 15 | /*************************************************************************** 16 | * * 17 | * Valid `level` configs: i.e. the minimum log level to capture with * 18 | * sails.log.*() * 19 | * * 20 | * The order of precedence for log levels from lowest to highest is: * 21 | * silly, verbose, info, debug, warn, error * 22 | * * 23 | * You may also set the level to "silent" to suppress all logs. * 24 | * * 25 | ***************************************************************************/ 26 | 27 | // level: 'info' 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/ProjectListItems.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { List, ListItem } from 'material-ui/List'; 4 | import Avatar from 'material-ui/Avatar'; 5 | import Project from './Projects'; 6 | import Team from './Teams' 7 | import FileFolder from 'material-ui/svg-icons/file/folder'; 8 | 9 | 10 | 11 | export default class ProjectListItems extends Component { 12 | 13 | render() { 14 | const styles = { 15 | item: { 16 | textDecoration: 'none' 17 | } 18 | } 19 | 20 | const project = this.props.project; 21 | //format timestamp 22 | let dateArr = project.updatedAt.split('T') 23 | let date = dateArr[0] 24 | let time = dateArr[1].split('.')[0] 25 | return ( 26 | 27 | } />} 33 | > 34 | {/*
    {project.name}
    */} 35 |
    36 | 37 | ); 38 | 39 | } 40 | } 41 | 42 | 43 | {/* 44 | 45 | 46 | 47 | 48 | 49 | */} 50 | -------------------------------------------------------------------------------- /test/client/Todos.test.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | const expect = chai.expect; 3 | import React, {Component} from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import TestUtils from 'react-addons-test-utils'; 6 | 7 | import Todos from '../../src/components/Todos'; 8 | 9 | const todos = [{ 10 | id: 1, 11 | name: "one", 12 | completed: false 13 | }]; 14 | 15 | let testComponent; 16 | 17 | before(function(done) { 18 | const onDelete = function() {}; 19 | const onComplete = function() {}; 20 | 21 | testComponent = TestUtils.renderIntoDocument( 22 | 23 | ); 24 | 25 | done(); 26 | }); 27 | 28 | describe('JSDom', () => { 29 | it('loads window, document, and jQuery', () => { 30 | expect(window).to.be.an('object'); 31 | expect(document).to.be.an('object'); 32 | expect($).to.be.a('function'); 33 | }); 34 | }); 35 | 36 | describe('Todos component', () => { 37 | it('renders correctly', () => { 38 | 39 | let testNode = ReactDOM.findDOMNode(testComponent); 40 | 41 | expect(testNode.nodeName).to.be.equal('DIV'); 42 | expect(testNode.className).to.be.equal('todos'); 43 | expect(testNode.getElementsByTagName('LI').length).to.be.equal(1); 44 | expect(testNode.getElementsByTagName('LI')[0]).to.be.an('object'); 45 | expect(testNode.getElementsByTagName('SPAN')[0].textContent).to.be.equal(todos[0].name); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/components/Team.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ListItem } from 'material-ui/List'; 3 | import FloatingActionButton from 'material-ui/FloatingActionButton'; 4 | import { DownloadIcon } from 'mdi-material-ui'; 5 | 6 | export default function Team ({team, onTeamOpen, onDownload}) { 7 | // const downloadButton = ( 8 | // 9 | // 10 | // 11 | // ) 12 | 13 | //format timestamp 14 | let dateArr = team.updatedAt.split('T') 15 | let date = dateArr[0] 16 | let time = dateArr[1].split('.')[0] 17 | 18 | return ( 19 |
    20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
    33 | ) 34 | } 35 | 36 | 37 | 38 | const styles = { 39 | download : { 40 | float: 'right', 41 | marginRight: 15 42 | }, 43 | container: { 44 | }, 45 | button: { 46 | marginRight: '20px' 47 | }, 48 | listItem: { 49 | textAlign: 'center', 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/env/prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Production environment settings 3 | * 4 | * This file can include shared settings for a production environment, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the production * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | models: { 21 | connection: 'productionPostgresqlServer' 22 | }, 23 | 24 | /*************************************************************************** 25 | * Set the port in the production environment to 80 * 26 | ***************************************************************************/ 27 | 28 | port: 80, 29 | 30 | /*************************************************************************** 31 | * Set the log level in production environment to "silent" * 32 | ***************************************************************************/ 33 | 34 | // log: { 35 | // level: "silent" 36 | // } 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /api/controllers/TodosController.js: -------------------------------------------------------------------------------- 1 | var debug = require('../utils/debug'); 2 | 3 | module.exports = { 4 | getTodos: function (req, res) { 5 | Todo.getAll(function (err, todos) { 6 | if (err) { 7 | res.status(500); 8 | res.send(err); 9 | } 10 | else res.send(todos); 11 | }); 12 | }, 13 | getTodo: function (req, res) { 14 | var id = req.params.id; 15 | Todo.findOne({id: id}, function (err, todo) { 16 | debug('getTodo '+id, todo); 17 | res.send(todo); 18 | }); 19 | }, 20 | createTodo: function (req, res) { 21 | debug('creating', req.body); 22 | 23 | var name = req.body.name; 24 | Todo.createTodo(name, function (err, results) { 25 | debug('created todo', results); 26 | res.send(results); 27 | }); 28 | }, 29 | updateTodo: function (req, res) { 30 | var id = req.params.id; 31 | var values = req.body; 32 | var s = new Date().getTime(); 33 | debug('updating todo', values); 34 | Todo.update(id, values).exec(function (err, results) { 35 | debug('updated Todo ' + id, results); 36 | res.send(results); 37 | var e = new Date().getTime(); 38 | }); 39 | }, 40 | deleteTodo: function (req, res) { 41 | var id = req.params.id; 42 | Todo.deleteTodo(id, function (err, results) { 43 | res.send(results); 44 | }); 45 | }, 46 | deleteAll: function (req, res) { 47 | Todo.deleteAll(function (err, results) { 48 | debug('deleteAll', results); 49 | res.send(results); 50 | }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/Members.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { List, ListItem } from 'material-ui/List'; 4 | import MemberListItems from './MemberListItems'; 5 | 6 | export default class Members extends Component { 7 | render() { 8 | return ( 9 |
    10 | { this.renderMembers() } 11 |
    ); 12 | } 13 | 14 | renderMembers() { 15 | if (this.props.members && this.props.members.length) { 16 | return ( 17 | 18 | { this.props.members.map((member, index) => { 19 | let adminHandler = () => { 20 | this.props.makeAdmin(member.admin, !member.added); 21 | }; 22 | let deleteHandler = () => { 23 | this.props.onDelete(member.id); 24 | }; 25 | return (); 26 | }) 27 | } 28 | ); 29 | } 30 | } 31 | } 32 | // 33 | // class Member extends Component { 34 | // render() { 35 | // const member = this.props.member; 36 | // return ( 37 | // 38 | //
  • 39 | // 40 | // Name: { member.username } 41 | // 42 | //   43 | // 44 | // ✓ 45 | // 46 | //   47 | // 48 | // × 49 | // 50 | //
  • ); 51 | // } 52 | // } 53 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader'; 2 | import React from 'react'; 3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 4 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 5 | import ReactDOM from 'react-dom'; 6 | import {store} from './store/configureStore' 7 | import { Provider } from 'react-redux' 8 | 9 | 10 | // http://stackoverflow.com/a/34015469/988941 11 | import injectTapEventPlugin from 'react-tap-event-plugin'; 12 | 13 | 14 | import App from './App'; 15 | 16 | injectTapEventPlugin(); 17 | const muiTheme = getMuiTheme({ 18 | palette: { 19 | primary1Color: '#FF6A1A', 20 | accent1Color: '#02C5FF', 21 | // textColor: '#00BCD4', 22 | }}) 23 | 24 | const rootEl = document.getElementById('app'); 25 | 26 | function render() { 27 | ReactDOM.render( 28 | 29 | 30 | 31 | 32 | 33 | 34 | , 35 | rootEl 36 | ); 37 | } 38 | 39 | if (rootEl) { 40 | render(); 41 | } 42 | else { 43 | console.log('no rootEl'); 44 | } 45 | 46 | if (module.hot) { 47 | module.hot.accept('./App', () => { 48 | const NextApp = require('./App').default; 49 | ReactDOM.render( 50 | 51 | 52 | 53 | 54 | 55 | 56 | , 57 | rootEl 58 | ); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /src/reducers/teamReducers.js: -------------------------------------------------------------------------------- 1 | import {createReducer} from '../utils/redux-helpers.js'; 2 | import getInitialState from './initialState' 3 | 4 | 5 | // TEAMS REDUCER 6 | 7 | 8 | const reducer = createReducer(getInitialState, { 9 | GET_TEAMS: function(state, action) { 10 | return { 11 | ...state, 12 | loading: true 13 | } 14 | }, 15 | GET_TEAMS_SUCCESS: function(state, action) { 16 | return { 17 | ...state, 18 | loading: false, 19 | teams: action.results 20 | } 21 | }, 22 | TEAMS_ERROR: function(state, action) { 23 | return { 24 | ...state, 25 | loading: false, 26 | error: action.error 27 | } 28 | }, 29 | ADD_TEAM_MEMBER: function(state, action) { 30 | const newState = { 31 | ...state, 32 | team: { 33 | ...state.team 34 | } 35 | }; 36 | newState.team[action.results.id] = action.results; 37 | return newState; 38 | }, 39 | REMOVE_TEAM_MEMBER: function(state, action) { 40 | const newState = { 41 | ...state, 42 | team: { 43 | ...state.team 44 | } 45 | }; 46 | delete newState.team[action.results.id]; 47 | return newState; 48 | }, 49 | CLOSE_ADD_TEAM: function(state, action) { 50 | return { 51 | ...state, 52 | team: {} 53 | } 54 | }, 55 | DOWNLOAD_PROJECT: function(state, action) { 56 | return { 57 | ...state, 58 | downloading: true 59 | } 60 | }, 61 | DOWNLOAD_PROJECT_SUCCESS: function(state, action) { 62 | return { 63 | ...state, 64 | downloading:false 65 | } 66 | } 67 | }); 68 | 69 | export {reducer}; 70 | -------------------------------------------------------------------------------- /test/server/Todo.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiHttp = require('chai-http'); 3 | 4 | const should = chai.should(); 5 | chai.use(chaiHttp); 6 | const expect = chai.expect; 7 | 8 | describe('.getAll()', function() { 9 | it('should return array of todos', function(done) { 10 | Todo.getAll(function(err, todos) { 11 | expect(todos).to.be.an('array'); 12 | done(); 13 | }); 14 | }); 15 | }); 16 | 17 | describe('.createTodo()', function() { 18 | it('creates a todo', function(done) { 19 | Todo.createTodo(null, function(err, todo) { 20 | expect(todo).to.be.an('object'); 21 | expect(todo.name).to.be.equal('null'); 22 | expect(todo.completed).to.be.equal(false); 23 | Todo.getAll(function(err, todos) { 24 | expect(todos.length).to.be.above(0); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('.deleteTodo()', function() { 32 | it('deletes a todo', function(done) { 33 | Todo.createTodo('test', function(err, results) { 34 | var id = results.id; 35 | Todo.deleteTodo(id, function(err, results) { 36 | expect(results).to.have.length(1); 37 | Todo.findOne({id:id}, function(err, todo) { 38 | expect(todo).to.be.equal(undefined); 39 | done(); 40 | }); 41 | }); 42 | }) 43 | }); 44 | }); 45 | 46 | describe('.deleteAll()', function() { 47 | it('deletes all todos', function(done) { 48 | Todo.createTodo(null, function(err, todo) { 49 | Todo.deleteAll(function(err, results) { 50 | Todo.getAll(function(err, todos) { 51 | expect(todos.length).to.be.equal(0); 52 | done(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/components/Projects.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { List, ListItem } from 'material-ui/List'; 4 | import Subheader from 'material-ui/Subheader'; 5 | import ProjectListItems from './ProjectListItems'; 6 | // import s from './style.scss'; 7 | 8 | export default class Projects extends Component { 9 | render() { 10 | return ( 11 |
    12 | { this.renderProjects() } 13 |
    ); 14 | } 15 | 16 | renderProjects() { 17 | if (this.props.projects.length) { 18 | return ( 19 | 20 | Projects 21 | { this.props.projects.map((project, index) => { 22 | return (); 23 | })} 24 | 25 | ); 26 | } 27 | } 28 | } 29 | 30 | 31 | // project completed and delete functions 32 | // let completeHandler = () => { 33 | // this.props.onToggleCompleted(project.id, !project.completed); 34 | // }; 35 | // let deleteHandler = () => { 36 | // this.props.onDelete(project.id); 37 | // }; 38 | 39 | // export class Project extends Component { 40 | // render() { 41 | // const project = this.props.project; 42 | // return ( 43 | // 44 | // 48 | // {/* 49 | // 50 | // 51 | // 52 | // 53 | // 54 | // */} 55 | // 56 | // 57 | // ); 58 | // 59 | // } 60 | // } 61 | -------------------------------------------------------------------------------- /config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#!/documentation/concepts/ORM 10 | */ 11 | 12 | 13 | 14 | module.exports.models = { 15 | 16 | /*************************************************************************** 17 | * * 18 | * Your app's default connection. i.e. the name of one of your app's * 19 | * connections (see `config/connections.js`) * 20 | * * 21 | ***************************************************************************/ 22 | connection: (() => { 23 | if(process.env['NODE_ENV'] === 'test') { 24 | return 'localDiskTestingDb'; 25 | } else if (process.env['NODE_ENV'] === 'dev') { 26 | return 'localDiskDb'; 27 | } else return 'productionPostgresqlServer'; 28 | })(), 29 | 30 | /*************************************************************************** 31 | * * 32 | * How and whether Sails will attempt to automatically rebuild the * 33 | * tables/collections/etc. in your schema. * 34 | * * 35 | * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html * 36 | * * 37 | ***************************************************************************/ 38 | migrate: 'alter' 39 | 40 | }; -------------------------------------------------------------------------------- /nebugit/messages.js: -------------------------------------------------------------------------------- 1 | import colors from 'colors'; 2 | const logoText = ` .,,,, 3 | ,;;;;;;,. 4 | Nebulis Analytics ,;;;;: 5 | Code Monitoring Server :;;;, 6 | v%s :;;; 7 | .;;;. 8 | .;;; .:;;;;;;;;;;. 9 | ;;;, ;;;';. ,;;;;; 10 | ;;;. :, :;;; 11 | :;; ;;; 12 | ;;; :;;;;;; ;;; 13 | .;;;; ;;;;;;;; ;;; 14 | ;;;;;, :;;;;;, .;, :;; 15 | ;;;; :;;. 16 | .;;;;: ;;;. 17 | ;;;;; ,;;: 18 | ' ,;;;;; ';, 19 | .;;; .;;;;;;. '; 20 | ;;;;;;::,,::;;;;;;;;;:. 21 | ,:;;;;;;;;;:,.\n`; 22 | 23 | const connectionText = `🔥 Nebulis git subsystem is listening on %s:%s`.magenta; 24 | const listenerConnectionText = '🔥 Nebulis Listener is listening on http://%s:%s'.magenta; 25 | 26 | const messages = { 27 | logo: () => { console.log(logoText, '0.1.0'); }, 28 | connectionInfo: (ip, port) => { console.log(connectionText, ip, port)}, 29 | listenerConnectionInfo: (host, port) => {console.log(listenerConnectionText, host, port)}, 30 | killed: () => { console.log(killedText); } 31 | }; 32 | 33 | export { messages as default }; -------------------------------------------------------------------------------- /test/client/Projects.test.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | const expect = chai.expect; 3 | import React, {Component} from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import TestUtils from 'react-addons-test-utils'; 6 | import { shallow } from 'enzyme'; 7 | import {List, ListItem } from 'material-ui/List'; 8 | import {Link} from 'react-router'; 9 | 10 | import Projects from '../../src/components/Projects'; 11 | import ProjectListItems from '../../src/components/ProjectListItems'; 12 | 13 | const projects = [{ 14 | id: 1, 15 | name: "one", 16 | updatedAt: 'Sept 2, 1990' 17 | }, 18 | { 19 | id: 2, 20 | name: "two", 21 | updatedAt: 'Feb 14, 1995' 22 | }]; 23 | 24 | let testComponent; 25 | 26 | before(function(done) { 27 | const onDelete = function() {}; 28 | const onClick = function() {}; 29 | 30 | testComponent = shallow( 31 | 32 | ); 33 | 34 | done(); 35 | }); 36 | 37 | describe('JSDom', () => { 38 | it('should render window, document, and jQuery', () => { 39 | expect(window).to.be.an('object'); 40 | expect(document).to.be.an('object'); 41 | expect($).to.be.a('function'); 42 | }); 43 | }); 44 | 45 | // describe('Project component', () => { TODO:: Fix these 46 | // it(`should render 'ProjectListItems' correctly`, () => { 47 | // testComponent = shallow() 48 | // console.log('kenny loggin', testComponent.node) 49 | // // expect(testComponent.node.props.className).to.be.equal('project'); 50 | // expect(testComponent.find(Link).find(ListItem).length).to.be.equal(1); 51 | // // expect(testComponent.find(Link).find(ListItem).props. 52 | // it('should render list of all projects in db', () => { 53 | // expect(testComponent.find(div).find(Link).length).to.be.equal(1); 54 | // }); 55 | // }); 56 | // }) 57 | -------------------------------------------------------------------------------- /src/components/Form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextField from 'material-ui/TextField'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | 5 | const Form = (props) => { 6 | 7 | const cancelButton = props.cancel ? : ''; 12 | 13 | const handleSubmit = (e) => { 14 | e.preventDefault(); 15 | props.handleSubmit(form); 16 | } 17 | 18 | let form; 19 | 20 | return ( 21 |
    form = f} 27 | > 28 |
    {props.validation}
    29 | { 30 | props.fields.map((field, i) => { 31 | return ( 32 | ); 41 | } 42 | ) 43 | } 44 |
    45 | {cancelButton} 46 | 52 |
    53 | 54 | ); 55 | } 56 | 57 | const styles = { 58 | form: { 59 | display: 'flex', 60 | flexDirection: 'column', 61 | alignItems: 'center', 62 | justifyContent: 'space-around' 63 | }, 64 | button: { 65 | alignSelf: 'flex-end', 66 | marginLeft: 5, 67 | marginBottom: 20, 68 | marginTop: 14 69 | }, 70 | validation : { 71 | color: 'red' 72 | } 73 | } 74 | 75 | export default Form; 76 | -------------------------------------------------------------------------------- /tasks/config/jst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `jst` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Precompile HTML templates using Underscore/Lodash notation into 7 | * functions, creating a `.jst` file. This can be brought into your HTML 8 | * via a 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /nginx.conf.template: -------------------------------------------------------------------------------- 1 | 2 | server { 3 | listen [::]:1337; 4 | listen 1337; 5 | 6 | access_log /var/log/nginx/nebulis-access.log; 7 | error_log /var/log/nginx/nebulis-error.log; 8 | 9 | location / { 10 | 11 | gzip on; 12 | gzip_min_length 1100; 13 | gzip_buffers 4 32k; 14 | gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; 15 | gzip_vary on; 16 | gzip_comp_level 6; 17 | 18 | proxy_pass http://nebulis-1337; 19 | proxy_http_version 1.1; 20 | proxy_set_header Upgrade $http_upgrade; 21 | proxy_set_header Connection "upgrade"; 22 | proxy_set_header Host $http_host; 23 | proxy_set_header X-Forwarded-Proto $scheme; 24 | proxy_set_header X-Forwarded-For $remote_addr; 25 | proxy_set_header X-Forwarded-Port $server_port; 26 | proxy_set_header X-Request-Start $msec; 27 | } 28 | include /home/dokku/nebulis/nginx.conf.d/*.conf; 29 | 30 | error_page 400 401 402 403 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 426 428 429 431 444 449 450 451 /400-error.html; 31 | location /400-error.html { 32 | root /var/lib/dokku/data/nginx-vhosts/dokku-errors; 33 | internal; 34 | } 35 | 36 | error_page 404 /404-error.html; 37 | location /404-error.html { 38 | root /var/lib/dokku/data/nginx-vhosts/dokku-errors; 39 | internal; 40 | } 41 | 42 | error_page 500 501 502 503 504 505 506 507 508 509 510 511 /500-error.html; 43 | location /500-error.html { 44 | root /var/lib/dokku/data/nginx-vhosts/dokku-errors; 45 | internal; 46 | } 47 | 48 | } 49 | 50 | server { 51 | listen [::]:7000; 52 | listen 7000; 53 | 54 | access_log /var/log/nginx/nebulis-access.log; 55 | error_log /var/log/nginx/nebulis-error.log; 56 | 57 | location / { 58 | 59 | gzip on; 60 | gzip_min_length 1100; 61 | gzip_buffers 4 32k; 62 | gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; 63 | gzip_vary on; 64 | gzip_comp_level 6; 65 | 66 | proxy_pass http://nebulis-7000; 67 | proxy_http_version 1.1; 68 | proxy_set_header Upgrade $http_upgrade; 69 | proxy_set_header Connection "upgrade"; 70 | proxy_set_header Host $http_host; 71 | proxy_set_header X-Forwarded-Proto $scheme; 72 | proxy_set_header X-Forwarded-For $remote_addr; 73 | proxy_set_header X-Forwarded-Port $server_port; 74 | proxy_set_header X-Request-Start $msec; 75 | } 76 | include /home/dokku/nebulis/nginx.conf.d/*.conf; 77 | 78 | error_page 400 401 402 403 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 426 428 429 431 444 449 450 451 /400-error.html; 79 | location /400-error.html { 80 | root /var/lib/dokku/data/nginx-vhosts/dokku-errors; 81 | internal; 82 | } 83 | 84 | error_page 404 /404-error.html; 85 | location /404-error.html { 86 | root /var/lib/dokku/data/nginx-vhosts/dokku-errors; 87 | internal; 88 | } 89 | 90 | error_page 500 501 502 503 504 505 506 507 508 509 510 511 /500-error.html; 91 | location /500-error.html { 92 | root /var/lib/dokku/data/nginx-vhosts/dokku-errors; 93 | internal; 94 | } 95 | 96 | } 97 | 98 | upstream nebulis-1337 { 99 | 100 | server 172.17.0.3:5000; 101 | } 102 | 103 | upstream nebulis-7000 { 104 | 105 | server 172.17.0.3:7000; 106 | } 107 | 108 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | * For more information on this configuration file, see: 25 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.cors.html 26 | * 27 | */ 28 | 29 | module.exports.cors = { 30 | 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | 40 | // allRoutes: false, 41 | 42 | /*************************************************************************** 43 | * * 44 | * Which domains which are allowed CORS access? This can be a * 45 | * comma-delimited list of hosts (beginning with http:// or https://) or * 46 | * "*" to allow all domains CORS access. * 47 | * * 48 | ***************************************************************************/ 49 | 50 | // origin: '*', 51 | 52 | /*************************************************************************** 53 | * * 54 | * Allow cookies to be shared for CORS requests? * 55 | * * 56 | ***************************************************************************/ 57 | 58 | // credentials: true, 59 | 60 | /*************************************************************************** 61 | * * 62 | * Which methods should be allowed for CORS requests? This is only used in * 63 | * response to preflight requests (see article linked above for more info) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | // methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 68 | 69 | /*************************************************************************** 70 | * * 71 | * Which headers should be allowed for CORS requests? This is only used in * 72 | * response to preflight requests. * 73 | * * 74 | ***************************************************************************/ 75 | 76 | // headers: 'content-type' 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 35 | 36 | 40 | 41 |
    42 |
    43 | 44 |
    45 | 46 |
    47 |

    48 | Something's fishy here. 49 |

    50 |

    51 | <% if (typeof data!== 'undefined') { %> 52 | <%= data %> 53 | <% } else { %> 54 | The page you were trying to reach doesn't exist. 55 | <% } %> 56 |

    57 |

    58 | Why might this be happening? 59 |

    60 |
    61 | 62 | 67 |
    68 | 69 | -------------------------------------------------------------------------------- /src/routes/members/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Layout from './../Layout'; 4 | import FloatingActionButton from 'material-ui/FloatingActionButton'; 5 | import Members from '../../components/Members'; 6 | import MemberListItems from '../../components/MemberListItems' 7 | import * as actions from '../../actions/nebulisActions.js' 8 | import { getStore, makeAdmin, removeAdmin } from './../../store/configureStore'; 9 | 10 | import { PlusIcon } from 'mdi-material-ui'; 11 | import ghoulie from 'ghoulie'; 12 | 13 | 14 | export default class MembersContainer extends Component { 15 | constructor(props) { 16 | super(props); 17 | 18 | this.state = { 19 | 20 | // map the model to state 21 | membersModel: getStore().getState().membersModel 22 | }; 23 | 24 | // when the store changes re-map the model to state 25 | getStore().subscribe(() => { 26 | this.setState({ 27 | membersModel: getStore().getState().membersModel 28 | }, () => { 29 | 30 | }); 31 | }); 32 | 33 | // notify ghoulie when this component is instantiated 34 | ghoulie.emit('MEMBERS_PAGE_LOADED'); 35 | 36 | // when ghoulie receives a RELOAD_MEMBERS event (from within a 37 | // unit test) log the event and call getMembers() 38 | ghoulie.on('RELOAD_MEMBERS', () => { 39 | ghoulie.log('RELOADING MEMBERS !!!!'); 40 | this.getMembers(); 41 | }); 42 | } 43 | 44 | componentDidMount() { 45 | this.getMembers(); 46 | } 47 | 48 | getMembers() { 49 | ghoulie.log('getting members...'); 50 | actions.getMembers().then(store => { 51 | 52 | // store returned is same as getStore().getState() 53 | ghoulie.log('got members', store); 54 | 55 | // map the model to state 56 | this.setState({ 57 | membersModel: store.membersModel 58 | }, () => { 59 | 60 | 61 | // emit TODOS_LOADED event for ghoulie test to use 62 | const members = store.membersModel.members; 63 | ghoulie.emit('MEMBERS_LOADED', members); 64 | }); 65 | 66 | }).catch(function(e, store) { 67 | console.log('CAUGHT ERROR', e); 68 | debugger; 69 | }); 70 | } 71 | 72 | render() { 73 | const styles = { 74 | action: { 75 | margin: 20, 76 | } 77 | } 78 | 79 | 80 | return ( 81 | 82 |
    83 | 84 | { this.renderLoading() } 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
    93 |
    94 | 95 |
    96 |
    97 |
    98 |
    ); 99 | } 100 | 101 | renderLoading() { 102 | if (this.state.membersModel && this.state.membersModel.loading) { 103 | return (
    Loading...
    ); 104 | } 105 | } 106 | 107 | // @ TODO 108 | onAdd() { 109 | 110 | // replaces current list with a form to insert Github URL and member title 111 | // Toggle state between current member list display OR new member form 112 | // Add one at a time for now 113 | getStore().dispatch(addMember()); 114 | 115 | // const name = ReactDOM.findDOMNode(this.refs.member).value; 116 | // nebulisActions.createMember({ 117 | // name 118 | // }).then(store => { 119 | // ReactDOM.findDOMNode(this.refs.member).value = ''; 120 | // this.getMembers(); 121 | // }); 122 | console.log('adding member(s)...') 123 | } 124 | 125 | // retrive member info from github 126 | 127 | // add member info to DB 128 | 129 | // move member name from unadded list to added list 130 | 131 | 132 | onDelete(id) { 133 | // destroys selected member(s) in DB by id 134 | nebulisActions.deleteMember(null, { 135 | id 136 | // then pulls updated list and replaces old list 137 | }).then(::this.getMembers); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/actions/nebulisActions.js: -------------------------------------------------------------------------------- 1 | import {createActions} from '../utils/redux-helpers.js'; 2 | import * as types from '../constants/actionTypes'; 3 | import {getStore} from './../store/configureStore.js'; 4 | 5 | 6 | // NEBULIS ACTIONS 7 | 8 | const fetchOptions = { 9 | credentials: 'include', 10 | headers: { 11 | 'Accept': 'application/json', 12 | 'Content-Type': 'application/json' 13 | }, 14 | cache: 'default', 15 | mode: 'same-origin' // if using CORS use mode: 'cors' 16 | }; 17 | 18 | // PROJECTS ACTIONS 19 | 20 | const actions = createActions(getStore, fetchOptions, { 21 | createProject: { 22 | method: 'post', 23 | url: '/api/projects', 24 | request: types.CREATE_PROJECT, 25 | success: types.CREATE_PROJECT_SUCCESS, 26 | error: types.PROJECTS_ERROR 27 | }, 28 | getProjects: { 29 | method: 'get', 30 | url: '/api/projects', 31 | request: types.GET_PROJECTS, 32 | success: types.GET_PROJECTS_SUCCESS, 33 | error: types.PROJECTS_ERROR 34 | }, 35 | updateProject: { 36 | method: 'put', 37 | url: '/api/projects/:id', 38 | request: types.UPDATE_PROJECTS, 39 | success: types.UPDATE_PROJECTS_SUCCESS, 40 | error: types.PROJECTS_ERROR 41 | }, 42 | getProject: { 43 | method: 'get', 44 | url: '/api/projects/:id', 45 | request: types.GET_PROJECT, 46 | success: types.GET_PROJECT_SUCCESS, 47 | error: types.PROJECTS_ERROR 48 | }, 49 | deleteProject: { 50 | method: 'delete', 51 | url: '/api/projects/:id', 52 | request: types.DELETE_PROJECT, 53 | success: types.DELETE_PROJECT_SUCCESS, 54 | error: types.PROJECTS_ERROR 55 | }, 56 | deleteAllProjects: { 57 | method: 'delete', 58 | url: '/api/projects', 59 | request: types.DELETE_PROJECTS, 60 | success: types.DELETE_PROJECTS_SUCCESS, 61 | error: types.PROJECTS_ERROR 62 | }, 63 | downloadTeamProject: { 64 | method: 'get', 65 | url: '/api/teams/:id/download', 66 | request: types.DOWNLOAD_PROJECT, 67 | success: types.DOWNLOAD_PROJECT_SUCCESS, 68 | error: types.PROJECTS_ERROR 69 | }, 70 | createMember: { 71 | method: 'post', 72 | url: '/api/members', 73 | request: types.CREATE_MEMBER, 74 | success: types.CREATE_MEMBER_SUCCESS, 75 | error: types.MEMBERS_ERROR 76 | }, 77 | getMembers: { 78 | method: 'get', 79 | url: '/api/members', 80 | request: types.GET_MEMBERS, 81 | success: types.GET_MEMBERS_SUCCESS, 82 | error: types.MEMBERS_ERROR 83 | }, 84 | deleteMember: { 85 | method: 'delete', 86 | url: '/api/members/:id', 87 | request: types.DELETE_MEMBER, 88 | success: types.DELETE_MEMBER_SUCCESS, 89 | error: types.MEMBER_ERROR 90 | }, 91 | // get teams 92 | getTeams: { 93 | method: 'get', 94 | url: '/api/projects/:id/teams', 95 | request: types.GET_TEAMS, 96 | success: types.GET_TEAMS_SUCCESS, 97 | error: types.TEAMS_ERROR 98 | }, 99 | createTeam: { 100 | method: 'post', 101 | url: '/api/teams', 102 | request: types.CREATE_TEAM, 103 | success: types.CREATE_TEAM_SUCCESS, 104 | error: types.TEAMS_ERROR 105 | } 106 | }); 107 | 108 | function addTeamMember(teamMember) { 109 | return { 110 | type: types.ADD_TEAM_MEMBER, 111 | results: teamMember 112 | } 113 | } 114 | 115 | function removeTeamMember(teamMember) { 116 | return { 117 | type: types.REMOVE_TEAM_MEMBER, 118 | results: teamMember 119 | } 120 | } 121 | 122 | function closeAddTeam() { 123 | return { 124 | type: types.CLOSE_ADD_TEAM 125 | } 126 | } 127 | 128 | module.exports = { 129 | ...actions, 130 | addTeamMember, 131 | removeTeamMember, 132 | closeAddTeam 133 | }; 134 | 135 | 136 | export function addProject(){ 137 | return {type: types.ADD_PROJECT} 138 | } 139 | export function closeProject(){ 140 | return {type: types.CLOSE_PROJECT} 141 | } 142 | export function makeAdmin(){ 143 | return {type: types.MAKE_ADMIN} 144 | } 145 | export function removeAdmin(){ 146 | return {type: types.REMOVE_ADMIN} 147 | } 148 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `api/responses/notFound.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on configuring custom routes, check out: 20 | * http://sailsjs.org/#!/documentation/concepts/Routes/RouteTargetSyntax.html 21 | */ 22 | 23 | var routes = { 24 | /*************************************************************************** 25 | * * 26 | * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, * 27 | * etc. depending on your default view engine) your home page. * 28 | * * 29 | * (Alternatively, remove this and add an `index.html` file in your * 30 | * `assets` directory) * 31 | * * 32 | ***************************************************************************/ 33 | 34 | // the default sails homepage when creating a new app 35 | '/sailshomepage': { 36 | view: 'sailshomepage' 37 | }, 38 | 39 | /*************************************************************************** 40 | * * 41 | * Custom routes here... * 42 | * * 43 | * If a request to a URL doesn't match any of the custom routes above, it * 44 | * is matched against Sails route blueprints. See `config/blueprints.js` * 45 | * for configuration options and examples. * 46 | * * 47 | ***************************************************************************/ 48 | 'get /auth/github': 'GitController.githubLogin', 49 | 'get /auth/github/callback': 'GitController.githubCallback', 50 | 51 | 'post /api/todos': 'TodosController.createTodo', 52 | 'put /api/todos/:id': 'TodosController.updateTodo', 53 | 'get /api/todos': 'TodosController.getTodos', 54 | 'get /api/todos/:id': 'TodosController.getTodo', 55 | 'delete /api/todos/:id': 'TodosController.deleteTodo', 56 | 'delete /api/todos': 'TodosController.deleteAll', 57 | 58 | 'get /api/projects': 'ProjectController.getProjects', 59 | 'get /api/projects/:id': 'ProjectController.getProject', 60 | 'get /api/projects/:id/teams': 'TeamController.projectIndex', 61 | 'post /api/projects': 'ProjectController.create', 62 | 63 | 'get /api/members': 'MemberController.getMembers', 64 | // 'get /api/members/:id': 'MemberController.getMembers', 65 | 66 | 'post /api/endpoints/establish': 'EndpointController.establish', 67 | 'get /api/endpoints': 'EndpointController.index', 68 | 69 | 'get /api/teams/:id/download': 'EndpointController.download', 70 | 'get /api/teams': 'TeamController.index', 71 | 'get /api/teams/:id': 'TeamController.view', 72 | 'post /api/teams': 'TeamController.create', 73 | }; 74 | 75 | // the same app will be rendered at all these routes 76 | var indexRoutes = [ 77 | '/', 78 | '/login', 79 | '/projects', 80 | '/members', 81 | '/projects/:id']; 82 | indexRoutes.forEach(function (r) { 83 | routes['GET ' + r] = 'IndexController.index'; 84 | }); 85 | 86 | module.exports.routes = routes; 87 | -------------------------------------------------------------------------------- /test/server/Endpoint.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiHttp = require('chai-http'); 3 | const fs = require('fs'); 4 | const request = require('request'); 5 | 6 | const should = chai.should(); 7 | chai.use(chaiHttp); 8 | const expect = chai.expect; 9 | 10 | describe('Endpoint Model Relationships', function() { 11 | let endpoint; 12 | let project; 13 | let member; 14 | let team; 15 | 16 | before(async function() { 17 | this.timeout(3500); 18 | //create 19 | endpoint = await Endpoint.create(); 20 | project = await Project.create({ 21 | name: 'cool project', 22 | slug: 'cool-proj', 23 | gitLink: 'github.com/something', 24 | }); 25 | member = await Member.create({}); 26 | team = await Team.create({}); 27 | 28 | //associate 29 | await Endpoint.update(endpoint, { 30 | member: member.id, 31 | team: team.id, 32 | project: project.id, 33 | }); 34 | }); 35 | after(async function() { 36 | await Endpoint.destroy(endpoint.id); 37 | await Project.destroy(project.id); 38 | await Member.destroy(member.id); 39 | await Team.destroy(team.id); 40 | }); 41 | it('should be able to get it\'s owning member', function(done) { 42 | Endpoint.find(endpoint.id) 43 | .populate('member') 44 | .exec(function(err, endpoint) { 45 | expect(endpoint[0].member.admin).to.be.equal(false); 46 | done(); 47 | }); 48 | }); 49 | it('should be able to get it\'s team', function(done) { 50 | Endpoint.find(endpoint.id) 51 | .populate('team') 52 | .exec(function(err, endpoint) { 53 | expect(endpoint[0].team.id).to.be.equal(team.id); 54 | done(); 55 | }); 56 | }); 57 | it('should be able to get the project it belongs to', function(done) { 58 | Endpoint.find(endpoint.id) 59 | .populate('project') 60 | .exec(function(err, endpoint) { 61 | expect(endpoint[0].project.name).to.be.equal('cool project'); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | 67 | describe('Endpoint Git Subsystem', function() { 68 | this.timeout(3000); 69 | let endpoint; 70 | let project; 71 | 72 | before(function (done) { 73 | Project.create({ 74 | name: 'cool project', 75 | slug: 'cool-proj', 76 | gitLink: 'github.com/something', 77 | }).exec((err, res) => { 78 | project = res; 79 | Endpoint.create({ project: project.id }).exec((err, res) => { 80 | endpoint = res; 81 | setTimeout(() => { 82 | done(); 83 | }, 1000); 84 | }); 85 | }); 86 | }); 87 | after(async () => { 88 | await Project.destroy(project.id); 89 | }); 90 | it('should be able to connect to the git listener api', (done) => { 91 | request.post(`http://localhost:7010/reset`, (err, httpResponse, body) => { 92 | expect(JSON.parse(body).message).to.be.equal('updating server endpoint list'); 93 | done(); 94 | }); 95 | }); 96 | it('should create a new repo when a new endpoint is created.', (done) => { 97 | let found = false; 98 | fs.readdirSync(process.env['REPO_LOCATION']).forEach(file => { 99 | if (file === endpoint.id + '.git') found = true; 100 | }); 101 | expect(found).to.be.equal(true); 102 | done(); 103 | }); 104 | xit('should listen on port 7000', () => {}); 105 | xit('should receive pushes from an endpoint', () => { 106 | //try pushing to endpoint. 107 | }); 108 | xit('should not allow pulls from an endpoint [SECURITY]', () => {}); 109 | it('should delete a repo when destroying an endpoint', (done) => { 110 | Endpoint.destroy(endpoint.id).exec((err, res) => { 111 | let found = false; 112 | fs.readdirSync(process.env['REPO_LOCATION']).forEach(file => { 113 | if (file === endpoint.id + '.git') found = true; 114 | }); 115 | expect(found).to.be.equal(false); 116 | done(); 117 | }); 118 | }); 119 | xit('should be able to delete multiple repos when destroying multiple endpoints', (done) => {}); 120 | xit('repo folder count should be the same as before starting the test', (done) => { 121 | //TODO: this one will fail 122 | }); 123 | }); -------------------------------------------------------------------------------- /src/utils/redux-helpers.js: -------------------------------------------------------------------------------- 1 | import ifetch from 'isomorphic-fetch'; 2 | import qs from 'qs'; 3 | 4 | const HTTP_STATUS_CODES = { 5 | 400: 'BAD_REQUEST', 6 | 401: 'UNAUTHORIZED', 7 | 402: 'PAYMENT_REQUIRED', 8 | 403: 'FORBIDDEN', 9 | 404: 'NOT_FOUND', 10 | 500: 'INTERNAL_SERVER_ERROR', 11 | 502: 'BAD_GATEWAY', 12 | 503: 'SERVICE_UNAVAILABLE', 13 | 504: 'GATEWAY_TIMEOUT' 14 | }; 15 | 16 | export function createActions(getStore, fetchOptions, endpoints) { 17 | let actions = {}, 18 | actionName, 19 | actionOptions; 20 | for (actionName in endpoints) { 21 | actionOptions = endpoints[actionName]; 22 | actions[actionName] = createServerAction(getStore, actionOptions, fetchOptions); 23 | } 24 | return actions; 25 | } 26 | 27 | export function createFetch(fetchOptions, params) { 28 | return function (actionOptions, args) { 29 | let url = actionOptions.url; 30 | 31 | let value, newurl; 32 | for (let param in params) { 33 | value = params[param]; 34 | newurl = url.replace(new RegExp('/(:' + param + '\/?)(/|$)', "gm"), '/' + value + '$2'); 35 | url = newurl; 36 | } 37 | 38 | const method = actionOptions.method ? actionOptions.method.toUpperCase() : 'GET'; 39 | 40 | const options = Object.assign({}, fetchOptions, { 41 | method 42 | }); 43 | 44 | if (typeof args === 'object' && args !== null) { 45 | if (method === 'GET') { 46 | if (url.indexOf('?') === -1) url += '?'; 47 | url += qs.stringify(args); 48 | } 49 | else { 50 | options.body = JSON.stringify(args); 51 | } 52 | } 53 | return ifetch(url, options); 54 | } 55 | } 56 | export function createServerAction(getStore, actionOptions, fetchOptions) { 57 | return createDispatcher(getStore, (args, params) => { 58 | return (dispatch) => { 59 | dispatch({ 60 | type: actionOptions.request, 61 | }); 62 | 63 | let status; 64 | const _fetch = createFetch(fetchOptions, params); 65 | 66 | return _fetch(actionOptions, args) 67 | .then((response) => { 68 | status = response.status; 69 | return response.json(); 70 | }) 71 | .then((json) => { 72 | if (status >= 400) { 73 | if (json.error && json.error.type) { 74 | 75 | if (actionOptions.error) { 76 | const errorType = json.error.type; 77 | 78 | const a = { 79 | type: actionOptions.error, 80 | success: false, 81 | requestType: actionOptions.request, 82 | requestArgs: args, 83 | requestParams: params, 84 | status: status, 85 | error: { 86 | type: errorType, 87 | data: json 88 | } 89 | }; 90 | dispatch(a); 91 | } 92 | } 93 | else { 94 | const httpType = (status in HTTP_STATUS_CODES) ? HTTP_STATUS_CODES[status] : 'HTTP_ERROR'; 95 | throw { 96 | type: httpType, 97 | success: false, 98 | status: status, 99 | requestType: actionOptions.request, 100 | requestArgs: args, 101 | requestParams: params, 102 | results: json 103 | }; 104 | } 105 | } 106 | else { 107 | const a = { 108 | type: actionOptions.success, 109 | success: true, 110 | status: status, 111 | requestType: actionOptions.request, 112 | requestArgs: args, 113 | requestParams: params, 114 | results: json 115 | }; 116 | dispatch(a); 117 | } 118 | }); 119 | }; 120 | }); 121 | } 122 | 123 | export function createDispatcher(getStore, actionFunction) { 124 | 125 | const getStoreState = function () { 126 | return getStore().getState(); 127 | }; 128 | 129 | return function (args, params) { 130 | return new Promise((resolve, reject) => { 131 | getStore().dispatch(actionFunction.call(null, args, params)).then(() => { 132 | resolve(getStoreState()); 133 | }).catch(function (err) { 134 | reject(err, getStoreState()); 135 | }); 136 | }); 137 | } 138 | 139 | } 140 | 141 | export function createReducer(getInitialState, responses) { 142 | return function (state = getInitialState(), action) { 143 | let type, fn; 144 | for (type in responses) { 145 | if (type === action.type) { 146 | fn = responses[type]; 147 | return fn(state, action); 148 | } 149 | } 150 | return state; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /api/controllers/EndpointController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EndpointController 3 | * 4 | * @description :: Server-side logic for managing endpoints 5 | * @help :: See http://sailsjs.org/#!/documentation/concepts/Controllers 6 | */ 7 | 8 | const request = require('request-promise'); 9 | const git = require('nodegit'); 10 | var zipFolder = require('zip-folder'); 11 | var rimraf = require('rimraf'); 12 | 13 | module.exports = { 14 | 15 | // the route for connections from the endpoints. Sets up a new repo and sends 16 | // repo remote back to endpoint. 17 | // POST /api/endpoints/establish 18 | // debug: { owners: [{username: 'NebulisAnalytics', fullname: 'Nebulis Analytics'}], project: 'nebulis-endpoint' } 19 | establish: async (req, res) => { 20 | if (!req.body.project) { 21 | sails.log.info('Request missing project name'); 22 | return res.send({error: 'ENDPOINT ERROR'}); } 23 | if (!req.body.owners) { 24 | sails.log.info('Request missing project owners'); 25 | return res.send({error: 'INPUT ERROR'}); } 26 | //find project 27 | const projects = await Project.find({ slug: req.body.project }); 28 | if (projects.length < 1) { 29 | sails.log.info(`Request for unknown project: ${req.body.project}`); 30 | return res.send({error: 'INPUT ERROR'}); } 31 | 32 | const users = req.body.owners; 33 | const memberIDs = []; 34 | const userFullnames = []; 35 | let members; 36 | for (let i = 0; i < users.length; i++) { 37 | members = await Member.find({ username: users[i].username }); 38 | //if user not found try to find on github to confirm existence before creating new user. 39 | if (members.length < 1) { 40 | sails.log.info(`Request for unknown user: ${users[i].username}`); 41 | const re = /(<\s*title[^>]*>(.+?)<\s*\/\s*title)>/gi; 42 | let response; 43 | try { 44 | response = await request(`https://github.com/${users[i].username}`); 45 | } catch (err) { 46 | sails.log.error('User not found on Github. Disregarding this endpoint request.'); 47 | return res.send({error: 'INPUT ERROR: This is not a github user.'}); 48 | } 49 | //assuming the user exists on github,` so creating in the db. 50 | members = [await Member.create({ 51 | username: users[i].username, 52 | fullname: users[i].fullName 53 | })]; 54 | } 55 | userFullnames.push(users[i].fullName); 56 | memberIDs.push(members[0].id); 57 | } 58 | 59 | let endpoints = await Endpoint.find({ 60 | where: { 61 | project: projects[0].id, 62 | member: memberIDs[0], 63 | }, 64 | }); 65 | if (endpoints.length < 1) { 66 | sails.log.info(`Request for new endpoint creation.`); 67 | 68 | endpoints = [await Endpoint.create({ 69 | member: memberIDs[0], 70 | project: projects[0].id, 71 | })]; 72 | 73 | const teamName = userFullnames.reduce((acc, name) => { 74 | acc += ` & ${name}`; 75 | return acc; 76 | }); 77 | 78 | // Create new team from members 79 | let team = await Team.create({ 80 | name: teamName, 81 | project: projects[0].id, 82 | }) 83 | await team.members.add(memberIDs); 84 | await team.endpoints.add(endpoints[0].id); 85 | await team.save(); 86 | } 87 | 88 | res.json({ 89 | id: endpoints[0].id, 90 | remote: `http://nebu:lis@${process.env['GIT_HOST']}/${endpoints[0].id}.git`}); 91 | }, 92 | 93 | //used by git server to get the list of endpoints 94 | index: (req, res) => { 95 | Endpoint.find({}).exec(function (err, endpoints) { 96 | if (err) { 97 | res.status(500); 98 | res.send(err); 99 | } 100 | else res.send(endpoints); 101 | }); 102 | }, 103 | 104 | //TODO: pick the latest file in the repo, not the first occurence in the db. 105 | //TODO: add error handling to rimraf 106 | download: (req, res) => { 107 | const store = '/tmp'; 108 | Team.find(req.param('id')).populate('members').exec((err, team) => { 109 | Member.find(team[0].members[0].id).populate('endpoints').exec((err, members) => { 110 | console.log(members); 111 | if (members[0].endpoints.length > 0) { 112 | const eid = members[0].endpoints[0].id; 113 | const path = `${process.env['REPO_LOCATION']}/${eid}.git`; 114 | console.log(path); 115 | git.Clone.clone(path, `${store}/browse`).then(function(repository) { 116 | console.log('repo',repository) 117 | zipFolder(`${store}/browse/`, `${store}/archive.zip`, function(err) { 118 | rimraf(`${store}/browse/.git/`, () => { 119 | rimraf(`${store}/browse/`, () => { 120 | if(!err) { 121 | res.sendfile(`${store}/archive.zip`); 122 | } else { 123 | res.send('error preparing files'); 124 | } 125 | }); 126 | }); 127 | }); 128 | }); 129 | } else { 130 | res.send({error: 'no endpoints found'}); 131 | } 132 | }); 133 | }); 134 | } 135 | 136 | 137 | }; 138 | -------------------------------------------------------------------------------- /src/routes/projects/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Layout from './../Layout'; 4 | import Projects from '../../components/Projects'; 5 | import ProjectListItems from '../../components/ProjectListItems'; 6 | import FloatingActionButton from 'material-ui/FloatingActionButton'; 7 | import Dialog from 'material-ui/Dialog'; 8 | import Form from '../../components/Form'; 9 | 10 | import { PlusIcon } from 'mdi-material-ui'; 11 | 12 | import * as actions from '../../actions/nebulisActions.js' 13 | import { getStore, addProject, closeProject } from './../../store/configureStore'; 14 | 15 | import ghoulie from 'ghoulie'; 16 | 17 | export default class ProjectsPage extends Component { 18 | constructor(props) { 19 | super(props); 20 | this.handleClose = this.handleClose.bind(this); 21 | this.onSave = this.onSave.bind(this); 22 | 23 | this.state = { 24 | open: false, 25 | validation: '', 26 | // map the model to state 27 | projectsModel: getStore().getState().projectsModel 28 | }; 29 | // when the store changes re-map the model to state 30 | getStore().subscribe(() => { 31 | this.setState({ 32 | projectsModel: getStore().getState().projectsModel 33 | }, () => { 34 | 35 | }); 36 | }); 37 | 38 | // notify ghoulie when this component is instantiated 39 | ghoulie.emit('PROJECTS_PAGE_LOADED'); 40 | 41 | // when ghoulie receives a RELOAD_PROJECTS event (from within a 42 | // unit test) log the event and call getProjects() 43 | ghoulie.on('RELOAD_PROJECTS', () => { 44 | ghoulie.log('RELOADING PROJECTS !!!!'); 45 | this.getProjects(); 46 | }); 47 | } 48 | 49 | componentDidMount() { 50 | this.getProjects(); 51 | } 52 | 53 | getProjects() { 54 | ghoulie.log('getting projects...'); 55 | actions.getProjects().then(store => { 56 | 57 | // store returned is same as getStore().getState() 58 | ghoulie.log('got projects', store); 59 | 60 | // map the model to state 61 | this.setState({ 62 | projectsModel: store.projectsModel 63 | }, () => { 64 | 65 | // emit TODOS_LOADED event for ghoulie test to use 66 | const projects = store.projectsModel.projects; 67 | ghoulie.emit('PROJECTS_LOADED', projects); 68 | 69 | }); 70 | 71 | }).catch(function(e, store) { 72 | console.log('CAUGHT ERROR', e); 73 | debugger; 74 | }); 75 | } 76 | 77 | render() { 78 | const styles = { 79 | action: { 80 | margin: 20, 81 | } 82 | } 83 | 84 | return ( 85 | 86 |
    87 | 88 | { this.renderLoading() } 89 | {/* Projects:
    */} 90 | 91 | 92 |
    93 | {/*will add styling */} 94 |
    95 | 96 |
    97 | 98 | 99 | 100 | 101 | ]} 117 | /> 118 |
    119 |
    120 |
    ); 121 | } 122 | 123 | renderLoading() { 124 | if (this.state.projectsModel.loading) { 125 | return (
    Loading...
    ); 126 | } 127 | } 128 | 129 | onSave(form) { 130 | ghoulie.emit('CREATE_PROJECT'); 131 | let {name, gitLink} = form; 132 | actions.createProject({name: name.value, gitLink: gitLink.value}).then(store => { 133 | 134 | // store returned is same as getStore().getState() 135 | ghoulie.log('got response', store); 136 | 137 | // map the model to state 138 | this.setState({ 139 | projectsModel: store.projectsModel, 140 | validation: store.projectsModel.results.summary 141 | }, () => { 142 | console.log('this.state.validation',this.state.validation); 143 | if(this.state.validation === undefined) { 144 | this.getProjects(); 145 | this.handleClose(); 146 | } 147 | }); 148 | 149 | }).catch(function(e, store) { 150 | console.log('CAUGHT ERROR', e); 151 | debugger; 152 | }); 153 | } 154 | 155 | onAdd(e) { 156 | // replaces current list with a form to insert Github URL and project title 157 | // Toggle state between current project list display OR new project form 158 | // Add one at a time for now 159 | this.setState({open: true}); 160 | } 161 | 162 | handleClose(e) { 163 | this.setState({open: false}); 164 | } 165 | // retrive project info from github 166 | 167 | // add project info to DB 168 | 169 | // move project name from unadded list to added list 170 | 171 | 172 | onDelete(id) { 173 | // destroys selected project(s) in DB by id 174 | nebulisActions.deleteProject(null, { 175 | id 176 | // then pulls updated list and replaces old list 177 | }).then(::this.getProjects); 178 | } 179 | } 180 | --------------------------------------------------------------------------------