├── client
├── static
│ ├── .gitkeep
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── mstile-150x150.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── browserconfig.xml
│ ├── manifest.json
│ └── safari-pinned-tab.svg
├── src
│ ├── store
│ │ ├── modules
│ │ │ ├── .gitkeep
│ │ │ └── profile.js
│ │ ├── module-types.js
│ │ └── index.js
│ ├── filters
│ │ └── index.js
│ ├── components
│ │ ├── misc
│ │ │ ├── Agreement.vue
│ │ │ ├── AutosizeTextarea.vue
│ │ │ └── ideaConstants.js
│ │ ├── Callback.vue
│ │ ├── shared
│ │ │ ├── MainFooter.vue
│ │ │ ├── MainNavbar.vue
│ │ │ ├── ModalDialog.vue
│ │ │ ├── IdeaTags.vue
│ │ │ ├── IdeaLinks.vue
│ │ │ └── IdeaType.vue
│ │ ├── users
│ │ │ └── UserProfile.vue
│ │ ├── ideas
│ │ │ ├── Dashboard.vue
│ │ │ └── CreateIdea.vue
│ │ └── Home.vue
│ ├── api
│ │ └── index.js
│ ├── auth
│ │ ├── utils.js
│ │ └── index.js
│ ├── main.js
│ ├── utils
│ │ └── localstorage.js
│ ├── assets
│ │ ├── eye-sym.svg
│ │ ├── shield_w.svg
│ │ ├── rocket_w.svg
│ │ ├── shield.svg
│ │ ├── github.svg
│ │ ├── feedback_w.svg
│ │ ├── rocket.svg
│ │ ├── idea-nebulae-logo.svg
│ │ └── feedback.svg
│ ├── router
│ │ └── index.js
│ └── App.vue
├── test
│ ├── unit
│ │ ├── specs
│ │ │ ├── ideas
│ │ │ │ └── CreateIdea.spec.js
│ │ │ ├── shared
│ │ │ │ ├── MainFooter.spec.js
│ │ │ │ └── MainNavbar.spec.js
│ │ │ ├── Home.spec.js
│ │ │ └── users
│ │ │ │ └── UserProfile.spec.js
│ │ ├── .eslintrc
│ │ ├── index.js
│ │ └── karma.conf.js
│ └── e2e
│ │ ├── specs
│ │ └── test.js
│ │ ├── custom-assertions
│ │ └── elementCount.js
│ │ ├── nightwatch.conf.js
│ │ └── runner.js
├── .eslintignore
├── config
│ ├── prod.env.js
│ ├── test.env.js
│ ├── dev.env.js
│ └── index.js
├── .postcssrc.js
├── styles
│ └── var.styl
├── build
│ ├── dev-client.js
│ ├── vue-loader.conf.js
│ ├── webpack.test.conf.js
│ ├── build.js
│ ├── webpack.dev.conf.js
│ ├── check-versions.js
│ ├── utils.js
│ ├── webpack.base.conf.js
│ ├── dev-server.js
│ └── webpack.prod.conf.js
├── .babelrc
├── index.html
├── .eslintrc.js
├── .gitignore
└── package.json
├── Procfile
├── server
├── .babelrc
├── documentation
│ └── IdeaNebulae Runtime Architecture.jpg
├── utils
│ ├── decodeToken.js
│ └── authCheck.js
├── .sequelizerc
├── router
│ ├── index.js
│ ├── reviews.js
│ └── profiles.js
├── server.js
├── db
│ ├── methods
│ │ ├── documentMethods.js
│ │ ├── reviewMethods.js
│ │ └── agreementMethods.js
│ ├── misc
│ │ └── ideaConstants.js
│ ├── models
│ │ ├── document.js
│ │ ├── agreement.js
│ │ ├── profile.js
│ │ ├── index.js
│ │ ├── review.js
│ │ ├── idea.js
│ │ └── postgres_views.sql
│ ├── migrations
│ │ ├── 20171219011522-create-profile.js
│ │ ├── 2017122012080-create-document.js
│ │ ├── 20171219140454-create-agreement.js
│ │ ├── 20171219142211-create-review.js
│ │ └── 20171219012348-create-idea.js
│ ├── config.js
│ └── seeders
│ │ ├── profile_seed.js
│ │ ├── agreement_seed.js
│ │ ├── document_seed.js
│ │ ├── review_seed.js
│ │ └── idea_seed.js
├── services
│ └── config.js
├── .gitignore
└── package.json
├── issue_template.md
├── readme.rdoc
├── readme.rst
├── .editorconfig
├── server.js
└── LICENSE
/client/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
--------------------------------------------------------------------------------
/client/src/store/modules/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/test/unit/specs/ideas/CreateIdea.spec.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/server/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/client/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/client/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/favicon.ico
--------------------------------------------------------------------------------
/client/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/favicon-16x16.png
--------------------------------------------------------------------------------
/client/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/favicon-32x32.png
--------------------------------------------------------------------------------
/client/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/mstile-150x150.png
--------------------------------------------------------------------------------
/client/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/client/src/store/module-types.js:
--------------------------------------------------------------------------------
1 | export const SET_PROFILE_DATA = 'SET_PROFILE_DATA';
2 | export const BEST_THING_EVER = 'EMPANADAS';
3 |
--------------------------------------------------------------------------------
/client/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/client/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/client/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/issue_template.md:
--------------------------------------------------------------------------------
1 | **_Issue Description & Expected Outcome:_**
2 |
3 | **_Symptoms:_**
4 |
5 | **_Steps to Recreate:_**
6 |
7 | **_Resolution:_**
8 |
--------------------------------------------------------------------------------
/client/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/server/documentation/IdeaNebulae Runtime Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chingu-voyage3/ideanebulae/HEAD/server/documentation/IdeaNebulae Runtime Architecture.jpg
--------------------------------------------------------------------------------
/client/config/test.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var devEnv = require('./dev.env')
3 |
4 | module.exports = merge(devEnv, {
5 | NODE_ENV: '"testing"'
6 | })
7 |
--------------------------------------------------------------------------------
/readme.rdoc:
--------------------------------------------------------------------------------
1 | {
}[https://waffle.io/chingu-voyage3/ideanebulae]
--------------------------------------------------------------------------------
/readme.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://badge.waffle.io/chingu-voyage3/ideanebulae.svg?columns=all
2 | :target: https://waffle.io/chingu-voyage3/ideanebulae
3 | :alt: 'Waffle.io - Columns and their card count'
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/client/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"',
6 | API_HOST: '"http://localhost:7000/api"',
7 | });
8 |
--------------------------------------------------------------------------------
/client/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/client/src/filters/index.js:
--------------------------------------------------------------------------------
1 | const truncate = (text, value) => {
2 | if (text.length < value - 3) {
3 | return text;
4 | }
5 | return `${text.substring(0, value - 3)}...`;
6 | };
7 |
8 | export default {
9 | truncate,
10 | };
11 |
--------------------------------------------------------------------------------
/client/src/components/misc/Agreement.vue:
--------------------------------------------------------------------------------
1 |
2 | Agreement Component
3 |
4 |
5 |
10 |
11 |
14 |
--------------------------------------------------------------------------------
/client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | import profile from './modules/profile';
5 |
6 | Vue.use(Vuex);
7 |
8 | const store = new Vuex.Store({
9 | modules: {
10 | profile,
11 | },
12 | });
13 |
14 | export default store;
15 |
--------------------------------------------------------------------------------
/server/utils/decodeToken.js:
--------------------------------------------------------------------------------
1 | const jwtDecode = require('jwt-decode');
2 |
3 | // Receives a token and returns the object
4 | // representation of the token, also known as
5 | // decoding the token
6 | const decodeToken = (token) => jwtDecode(token);
7 |
8 | module.exports = decodeToken;
9 |
--------------------------------------------------------------------------------
/server/.sequelizerc:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | 'config': path.resolve('db', 'config.js'),
5 | 'migrations-path': path.resolve('db', 'migrations'),
6 | 'models-path': path.resolve('db', 'models'),
7 | 'seeders-path': path.resolve('db', 'seeders'),
8 | };
9 |
--------------------------------------------------------------------------------
/client/styles/var.styl:
--------------------------------------------------------------------------------
1 | $dkblue = #3023AE
2 | $purple = #7c48c2
3 | $pink = #C86DD7
4 | $gray_text = #545454
5 | $gray_bkgrd = lighten($gray_text, 80%)
6 | $medblue = #4b83dc
7 | $aqua = #00cbf7
8 | $ltblue = lighten($aqua, 40%)
9 | $sky = #3877ce
10 | $ltred = #ee4f5c
11 | $dkred = #9a041a
12 |
--------------------------------------------------------------------------------
/client/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/client/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2d89ef
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/client/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { getAccessToken } from '@/auth';
3 |
4 | // Create http object to send the access token
5 | // along with every single request
6 | // eslint-disable-next-line new-cap
7 | const http = new axios.create({
8 | baseURL: process.env.API_HOST,
9 | timeout: 5000,
10 | headers: {
11 | Authorization: `Bearer ${getAccessToken()}`,
12 | },
13 | });
14 |
15 | export default http;
16 |
--------------------------------------------------------------------------------
/server/utils/authCheck.js:
--------------------------------------------------------------------------------
1 | const jwt = require('express-jwt');
2 | const jwks = require('jwks-rsa');
3 |
4 | const authCheck = jwt({
5 | secret: jwks.expressJwtSecret({
6 | cache: true,
7 | rateLimit: true,
8 | jwksRequestsPerMinute: 5,
9 | jwksUri: 'https://.well-known/jwks.json',
10 | }),
11 | audience: 'API-AUDIENCE-ATTR',
12 | issuer: 'https://auth0-domain.auth0.com/',
13 | algorithms: ['RS256'],
14 | });
15 |
16 | module.exports = authCheck;
17 |
--------------------------------------------------------------------------------
/client/src/components/misc/AutosizeTextarea.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/server/router/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const authCheck = require('../utils/authCheck');
3 | const decodeToken = require('../utils/decodeToken');
4 | const ideasRouter = require('./ideas');
5 | const profilesRouter = require('./profiles');
6 | const reviewsRouter = require('./reviews');
7 |
8 | const router = express.Router();
9 |
10 | router.use(ideasRouter);
11 | router.use(profilesRouter);
12 | router.use(reviewsRouter);
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/client/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Idea Nebulae",
3 | "icons": [
4 | {
5 | "src": "/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image/png"
8 | },
9 | {
10 | "src": "/android-chrome-512x512.png",
11 | "sizes": "512x512",
12 | "type": "image/png"
13 | }
14 | ],
15 | "theme_color": "#ffffff",
16 | "background_color": "#ffffff",
17 | "display": "standalone"
18 | }
--------------------------------------------------------------------------------
/client/test/unit/specs/shared/MainFooter.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'avoriaz';
2 | import MainFooter from '@/components/shared/MainFooter';
3 |
4 | describe('MainFooter.vue', () => {
5 | it('has a footer as wrapper', () => {
6 | const wrapper = mount(MainFooter);
7 | expect(wrapper.is('footer')).to.equal(true);
8 | });
9 |
10 | it('has a wrapper with class footer', () => {
11 | const wrapper = mount(MainFooter);
12 | expect(wrapper.hasClass('footer')).to.equal(true);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/client/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('../config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: isProduction
8 | ? config.build.productionSourceMap
9 | : config.dev.cssSourceMap,
10 | extract: isProduction
11 | }),
12 | transformToRequire: {
13 | video: 'src',
14 | source: 'src',
15 | img: 'src',
16 | image: 'xlink:href'
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/auth/utils.js:
--------------------------------------------------------------------------------
1 | import decode from 'jwt-decode';
2 |
3 | // Return the token expiration date in UTC
4 | export function getTokenExpirationDate(encodedToken) {
5 | const token = decode(encodedToken);
6 | if (!token.exp) return null;
7 | const date = new Date(0);
8 | date.setUTCSeconds(token.exp);
9 | return date;
10 | }
11 |
12 | // Check if the token has expired already
13 | export function isTokenExpired(token) {
14 | const expirationDate = getTokenExpirationDate(token);
15 | return expirationDate < new Date();
16 | }
17 |
--------------------------------------------------------------------------------
/client/src/components/Callback.vue:
--------------------------------------------------------------------------------
1 |
2 | Callback
3 |
4 |
5 |
22 |
--------------------------------------------------------------------------------
/client/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue';
4 | import VeeValidate from 'vee-validate';
5 | import App from './App';
6 | import router from './router';
7 | import store from './store';
8 |
9 | Vue.config.productionTip = false;
10 |
11 | Vue.use(VeeValidate);
12 |
13 | /* eslint-disable no-new */
14 | new Vue({
15 | el: '#app',
16 | router,
17 | store,
18 | template: '',
19 | components: { App },
20 | });
21 |
--------------------------------------------------------------------------------
/client/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import Vue from 'vue';
3 |
4 | Vue.config.productionTip = false;
5 |
6 | // require all test files (files that ends with .spec.js)
7 | const testsContext = require.context('./specs', true, /\.spec$/);
8 | testsContext.keys().forEach(testsContext);
9 |
10 | // require all src files except main.js for coverage.
11 | // you can also change this to match only the subset of files that
12 | // you want coverage for.
13 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
14 | srcContext.keys().forEach(srcContext);
15 |
--------------------------------------------------------------------------------
/client/test/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // For authoring Nightwatch tests, see
2 | // http://nightwatchjs.org/guide#usage
3 |
4 | module.exports = {
5 | 'default e2e tests': function test(browser) {
6 | // automatically uses dev Server port from /config.index.js
7 | // default: http://localhost:8080
8 | // see nightwatch.conf.js
9 | const devServer = browser.globals.devServerURL;
10 |
11 | browser
12 | .url(devServer)
13 | .waitForElementVisible('#app', 5000)
14 | .assert.elementPresent('.hello')
15 | .assert.containsText('h1', 'Welcome to Your Vue.js App')
16 | .assert.elementCount('img', 1)
17 | .end();
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/client/src/utils/localstorage.js:
--------------------------------------------------------------------------------
1 | function lsTest() {
2 | const test = 'test123321';
3 | try {
4 | localStorage.setItem(test, test);
5 | localStorage.removeItem(test);
6 | return true;
7 | } catch (e) {
8 | return false;
9 | }
10 | }
11 |
12 | const setObject = (key, value) => {
13 | if (lsTest()) {
14 | localStorage.setItem(key, JSON.stringify(value));
15 | }
16 | return null;
17 | };
18 |
19 | const getObject = (key) => {
20 | if (lsTest()) {
21 | const value = localStorage.getItem(key);
22 | return value && JSON.parse(value);
23 | }
24 | return null;
25 | };
26 |
27 | export default {
28 | setObject,
29 | getObject,
30 | };
31 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const bodyParser = require('body-parser');
4 | const config = require('./services/config');
5 | const router = require('./router');
6 | const models = require('./db/models');
7 |
8 | const app = express();
9 |
10 | // default port where dev server listens for incoming traffic
11 | const port = process.env.PORT || 7000;
12 |
13 | app.use(bodyParser.json());
14 | app.use(bodyParser.urlencoded({ extended: true }));
15 | app.use(cors());
16 |
17 | // The routes for the app
18 | app.use('/api', router);
19 |
20 | models.sequelize.sync().then(() => {
21 | app.listen(port, () => {
22 | console.log(`The server is running on port ${port}`);
23 | });
24 | });
25 |
26 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const bodyParser = require('body-parser');
4 | const config = require('./services/config');
5 | const router = require('./router');
6 | const models = require('./db/models');
7 |
8 | const app = express();
9 |
10 | // default port where dev server listens for incoming traffic
11 | const port = process.env.PORT || 7000;
12 |
13 | app.use(bodyParser.json());
14 | app.use(bodyParser.urlencoded({ extended: true }));
15 | app.use(cors());
16 |
17 | // The routes for the app
18 | app.use('/api', router);
19 |
20 | models.sequelize.sync().then(() => {
21 | app.listen(port, () => {
22 | console.log(`The server is running on port ${port}`);
23 | });
24 | });
25 |
26 |
--------------------------------------------------------------------------------
/client/src/assets/eye-sym.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/db/methods/documentMethods.js:
--------------------------------------------------------------------------------
1 | const models = require('../models');
2 |
3 | export default class documentMethods {
4 |
5 | /**
6 | * @description Retrieve all documents associated with a given idea.
7 | * @param {any} ideaId The id value of the owning idea
8 | * @returns {Object} An object containing all documents realated to the idea id
9 | * @memberof agreementMethods
10 | */
11 | static async findByIdea(ideaId) {
12 | return await models.sequelize.query(
13 | `SELECT id,
14 | username,
15 | idea_id,
16 | title,
17 | idea_type,
18 | url,
19 | description
20 | FROM idea_documents \
21 | WHERE idea_id = ${ideaId}`,
22 | { type: models.sequelize.QueryTypes.SELECT});
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/store/modules/profile.js:
--------------------------------------------------------------------------------
1 | /* eslint no-param-reassign:
2 | ["error", { "props": true, "ignorePropertyModificationsFor": ["state"] }]
3 | */
4 | import * as types from '../module-types';
5 |
6 | // Actions
7 | const actions = {
8 | SET_PROFILE_DATA({ commit }, profile) {
9 | commit(types.SET_PROFILE_DATA, profile);
10 | },
11 | };
12 |
13 | // Mutations
14 | const mutations = {
15 | [types.SET_PROFILE_DATA](state, profile) {
16 | const { sub, nickname, name, picture } = profile;
17 | state.sub = sub;
18 | state.nickname = nickname;
19 | state.name = name;
20 | state.picture = picture;
21 | },
22 | };
23 |
24 | // Initial state
25 | const state = {
26 | sub: '',
27 | nickname: '',
28 | name: '',
29 | picture: '',
30 | };
31 |
32 | export default {
33 | state,
34 | actions,
35 | mutations,
36 | };
37 |
--------------------------------------------------------------------------------
/server/db/misc/ideaConstants.js:
--------------------------------------------------------------------------------
1 | // Constants required by virtual typeCode field getter and setter
2 | //
3 | // Due to they way the app is deployed to Heroku this file is a duplicate
4 | // of the one located at /server/db/misc/ideaConstants.js. This is because the
5 | // client and server parts of the application are deployed to different Heroku
6 | // Dynos and there is no shared file system.
7 | //
8 | // TODO: Find a way to single source these constants across both the client and
9 | // server components of the application.
10 | const PUBLIC_IDEA = 0;
11 | const PRIVATE_IDEA = 1;
12 | const COMMERCIAL_IDEA = 2;
13 | const IDEA_TYPES = [
14 | { type: PUBLIC_IDEA, name: 'public' },
15 | { type: PRIVATE_IDEA, name: 'private' },
16 | { type: COMMERCIAL_IDEA, name: 'commercial' },
17 | ];
18 |
19 | export { PUBLIC_IDEA, PRIVATE_IDEA, COMMERCIAL_IDEA, IDEA_TYPES };
20 |
--------------------------------------------------------------------------------
/client/src/components/misc/ideaConstants.js:
--------------------------------------------------------------------------------
1 | // Constants required by virtual typeCode field getter and setter
2 | //
3 | // Due to they way the app is deployed to Heroku this file is a duplicate
4 | // of the one located at /server/db/misc/ideaConstants.js. This is because the
5 | // client and server parts of the application are deployed to different Heroku
6 | // Dynos and there is no shared file system.
7 | //
8 | // TODO: Find a way to single source these constants across both the client and
9 | // server components of the application.
10 | const PUBLIC_IDEA = 0;
11 | const PRIVATE_IDEA = 1;
12 | const COMMERCIAL_IDEA = 2;
13 | const IDEA_TYPES = [
14 | { type: PUBLIC_IDEA, name: 'public' },
15 | { type: PRIVATE_IDEA, name: 'private' },
16 | { type: COMMERCIAL_IDEA, name: 'commercial' },
17 | ];
18 |
19 | export { PUBLIC_IDEA, PRIVATE_IDEA, COMMERCIAL_IDEA, IDEA_TYPES };
20 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Idea Nebulae
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/client/src/components/shared/MainFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
19 |
20 |
40 |
--------------------------------------------------------------------------------
/client/test/e2e/custom-assertions/elementCount.js:
--------------------------------------------------------------------------------
1 | // A custom Nightwatch assertion.
2 | // the name of the method is the filename.
3 | // can be used in tests like this:
4 | //
5 | // browser.assert.elementCount(selector, count)
6 | //
7 | // for how to write custom assertions see
8 | // http://nightwatchjs.org/guide#writing-custom-assertions
9 | exports.assertion = function (selector, count) {
10 | this.message = 'Testing if element <' + selector + '> has count: ' + count;
11 | this.expected = count;
12 | this.pass = function (val) {
13 | return val === this.expected;
14 | }
15 | this.value = function (res) {
16 | return res.value;
17 | }
18 | this.command = function (cb) {
19 | var self = this;
20 | return this.api.execute(function (selector) {
21 | return document.querySelectorAll(selector).length;
22 | }, [selector], function (res) {
23 | cb.call(self, res);
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/db/methods/reviewMethods.js:
--------------------------------------------------------------------------------
1 | const models = require('../models');
2 |
3 | export default class reviewMethods {
4 |
5 | /**
6 | * @description Retrieve all reviews associated with a given idea.
7 | * @param {any} ideaId The id value of the owning idea
8 | * @returns {Object} An object containing all reviews related to the idea id
9 | * @memberof agreementMethods
10 | */
11 | static async findByIdea(ideaId) {
12 | return await models.sequelize.query(
13 | `SELECT id, \
14 | idea_id, \
15 | idea_creator, \
16 | title, \
17 | idea_type, \
18 | reviewer_id, \
19 | reviewer, \
20 | comments, \
21 | created_at, \
22 | updated_at \
23 | FROM idea_reviews \
24 | WHERE idea_id = ${ideaId}`,
25 | { type: models.sequelize.QueryTypes.SELECT});
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/client/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | // This is the webpack config used for unit tests.
2 |
3 | var utils = require('./utils')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseConfig = require('./webpack.base.conf')
7 |
8 | var webpackConfig = merge(baseConfig, {
9 | // use inline sourcemap for karma-sourcemap-loader
10 | module: {
11 | rules: utils.styleLoaders()
12 | },
13 | devtool: '#inline-source-map',
14 | resolveLoader: {
15 | alias: {
16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
18 | 'scss-loader': 'sass-loader'
19 | }
20 | },
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': require('../config/test.env')
24 | })
25 | ]
26 | })
27 |
28 | // no need for app entry during tests
29 | delete webpackConfig.entry
30 |
31 | module.exports = webpackConfig
32 |
--------------------------------------------------------------------------------
/client/test/unit/specs/shared/MainNavbar.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'avoriaz';
2 | import MainNavbar from '@/components/shared/MainNavbar';
3 |
4 | describe('MainNavbar.vue', () => {
5 | it('has a nav as wrapper', () => {
6 | const wrapper = mount(MainNavbar);
7 | expect(wrapper.is('nav')).to.equal(true);
8 | });
9 |
10 | it('has a wrapper with class nav', () => {
11 | const wrapper = mount(MainNavbar);
12 | expect(wrapper.hasClass('nav')).to.equal(true);
13 | });
14 |
15 | it('has a handle login method', () => {
16 | const wrapper = mount(MainNavbar);
17 | expect(typeof wrapper.methods().handleLogin).to.equal('function');
18 | });
19 |
20 | it('has a handle logout method', () => {
21 | const wrapper = mount(MainNavbar);
22 | expect(typeof wrapper.methods().handleLogout).to.equal('function');
23 | });
24 |
25 | it('has a is logged in method', () => {
26 | const wrapper = mount(MainNavbar);
27 | expect(typeof wrapper.methods().isLoggedIn).to.equal('function');
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/server/db/models/document.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var Document = sequelize.define('Document', {
4 | id: {
5 | type: DataTypes.INTEGER,
6 | primaryKey: true,
7 | autoIncrement: true,
8 | allowNull: false,
9 | },
10 |
11 | idea_id: {
12 | type: DataTypes.INTEGER,
13 | onDelete: 'CASCADE',
14 | references: {
15 | model: 'ideas',
16 | key: 'id',
17 | },
18 | allowNull: false,
19 | },
20 |
21 | url: {
22 | type: DataTypes.TEXT,
23 | allowNull: false,
24 | },
25 |
26 | description: {
27 | type: DataTypes.TEXT,
28 | allowNull: true,
29 | },
30 |
31 | }, {
32 | underscored: true,
33 | classMethods: {
34 | associate: function (models) {
35 | // associations can be defined here
36 | Document.belongsTo(models.Profile);
37 | Document.belongsTo(models.Idea);
38 | }
39 | }
40 | });
41 | return Document;
42 | };
43 |
--------------------------------------------------------------------------------
/server/db/models/agreement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var Agreement = sequelize.define('Agreement', {
4 | id: {
5 | allowNull: false,
6 | autoIncrement: true,
7 | primaryKey: true,
8 | type: DataTypes.INTEGER,
9 | },
10 |
11 | idea_id: {
12 | type: DataTypes.INTEGER,
13 | onDelete: 'CASCADE',
14 | references: {
15 | model: 'ideas',
16 | key: 'id',
17 | },
18 | allowNull: false,
19 | },
20 |
21 | agreement: {
22 | type: DataTypes.STRING,
23 | allowNull: false,
24 | },
25 |
26 | version: {
27 | type: DataTypes.INTEGER,
28 | defaultValue: 1,
29 | allowNull: false,
30 | },
31 | }, {
32 | underscored: true,
33 | classMethods: {
34 | associate: function (models) {
35 | // associations can be defined here
36 | Agreement.belongsTo(models.Profile);
37 | Agreement.belongsTo(models.Idea);
38 | }
39 | }
40 | });
41 | return Agreement;
42 | };
43 |
--------------------------------------------------------------------------------
/client/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module',
8 | esversion: 6
9 | },
10 | env: {
11 | browser: true,
12 | },
13 | extends: 'airbnb-base',
14 | // required to lint *.vue files
15 | plugins: [
16 | 'html'
17 | ],
18 | // check if imports actually resolve
19 | 'settings': {
20 | 'import/resolver': {
21 | 'webpack': {
22 | 'config': 'build/webpack.base.conf.js'
23 | }
24 | }
25 | },
26 | // add your custom rules here
27 | 'rules': {
28 | // don't require .vue extension when importing
29 | 'import/extensions': ['error', 'always', {
30 | 'js': 'never',
31 | 'vue': 'never'
32 | }],
33 | // allow optionalDependencies
34 | 'import/no-extraneous-dependencies': ['error', {
35 | 'optionalDependencies': ['test/unit/index.js']
36 | }],
37 | // allow debugger during development
38 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/server/db/migrations/20171219011522-create-profile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('profiles', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | user_id: {
12 | allowNull: false,
13 | type: Sequelize.STRING
14 | },
15 | username: {
16 | allowNull: false,
17 | type: Sequelize.STRING
18 | },
19 | name: {
20 | type: Sequelize.STRING
21 | },
22 | avatar_url: {
23 | type: Sequelize.STRING
24 | },
25 | qualifications: {
26 | type: Sequelize.STRING
27 | },
28 | created_at: {
29 | allowNull: false,
30 | type: Sequelize.DATE
31 | },
32 | updated_at: {
33 | allowNull: false,
34 | type: Sequelize.DATE
35 | }
36 | });
37 | },
38 | down: (queryInterface, Sequelize) => {
39 | return queryInterface.dropTable('profiles');
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/server/db/models/profile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var Profile = sequelize.define('Profile', {
4 | id: {
5 | type: DataTypes.INTEGER,
6 | primaryKey: true,
7 | autoIncrement: true,
8 | allowNull: false,
9 | },
10 |
11 | user_id: {
12 | type: DataTypes.STRING,
13 | allowNull: false,
14 | unique: true,
15 | primaryKey: true,
16 | },
17 |
18 | username: {
19 | type: DataTypes.STRING,
20 | allowNull: false,
21 | unique: true,
22 | },
23 |
24 | name: DataTypes.STRING,
25 |
26 | avatar_url: DataTypes.STRING,
27 |
28 | qualifications: DataTypes.STRING
29 | }, {
30 | underscored: true,
31 | classMethods: {
32 | associate: function (models) {
33 | // associations can be defined here
34 | Profile.hasMany(models.Idea);
35 | Profile.hasMany(models.Agreement);
36 | Profile.hasMany(models.Review);
37 | Profile.hasMany(models.Document);
38 | }
39 | }
40 | });
41 | return Profile;
42 | };
43 |
--------------------------------------------------------------------------------
/server/db/migrations/2017122012080-create-document.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('documents', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | idea_id: {
12 | type: Sequelize.INTEGER,
13 | onDelete: 'CASCADE',
14 | references: {
15 | model: 'ideas',
16 | key: 'id',
17 | },
18 | allowNull: false,
19 | },
20 | url: {
21 | type: Sequelize.TEXT,
22 | allowNull: false,
23 | },
24 | description: {
25 | type: Sequelize.TEXT,
26 | allowNull: false,
27 | },
28 | created_at: {
29 | allowNull: false,
30 | type: Sequelize.DATE
31 | },
32 | updated_at: {
33 | allowNull: false,
34 | type: Sequelize.DATE
35 | }
36 | });
37 | },
38 | down: (queryInterface, Sequelize) => {
39 | return queryInterface.dropTable('documents');
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/server/db/models/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var Sequelize = require('sequelize');
6 | var basename = path.basename(__filename);
7 | var env = process.env.NODE_ENV || 'development';
8 | var config = require(__dirname + '/../config.js')[env];
9 | var db = {};
10 |
11 | if (config.use_env_variable) {
12 | var sequelize = new Sequelize(process.env[config.use_env_variable], config);
13 | } else {
14 | var sequelize = new Sequelize(config.database, config.username, config.password, config);
15 | }
16 |
17 | fs
18 | .readdirSync(__dirname)
19 | .filter(file => {
20 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
21 | })
22 | .forEach(file => {
23 | var model = sequelize['import'](path.join(__dirname, file));
24 | db[model.name] = model;
25 | });
26 |
27 | Object.keys(db).forEach(modelName => {
28 | if (db[modelName].associate) {
29 | db[modelName].associate(db);
30 | }
31 | });
32 |
33 | db.sequelize = sequelize;
34 | db.Sequelize = Sequelize;
35 |
36 | module.exports = db;
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Chingu Coders
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/db/models/review.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var Review = sequelize.define('Review', {
4 | id: {
5 | type: DataTypes.INTEGER,
6 | primaryKey: true,
7 | autoIncrement: true,
8 | allowNull: false,
9 | },
10 |
11 | idea_id: {
12 | type: DataTypes.INTEGER,
13 | onDelete: 'CASCADE',
14 | references: {
15 | model: 'ideas',
16 | key: 'id',
17 | },
18 | allowNull: false,
19 | },
20 |
21 | reviewer_id: {
22 | type: DataTypes.INTEGER,
23 | onDelete: 'CASCADE',
24 | references: {
25 | model: 'profiles',
26 | key: 'id',
27 | },
28 | allowNull: false,
29 | },
30 |
31 | comments: {
32 | type: DataTypes.TEXT,
33 | allowNull: false,
34 | },
35 | }, {
36 | underscored: true,
37 | classMethods: {
38 | associate: function (models) {
39 | // associations can be defined here
40 | Review.belongsTo(models.Profile);
41 | Review.belongsTo(models.Idea);
42 | }
43 | }
44 | });
45 | return Review;
46 | };
47 |
--------------------------------------------------------------------------------
/client/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf');
7 |
8 | module.exports = function (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true,
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' },
30 | ]
31 | },
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/server/db/migrations/20171219140454-create-agreement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('agreements', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | idea_id: {
12 | type: Sequelize.INTEGER,
13 | onDelete: 'CASCADE',
14 | references: {
15 | model: 'ideas',
16 | key: 'id',
17 | },
18 | allowNull: false,
19 | },
20 | agreement: {
21 | type: Sequelize.STRING,
22 | allowNull: false,
23 | },
24 | version: {
25 | type: Sequelize.INTEGER,
26 | defaultValue: 1,
27 | allowNull: false,
28 | },
29 | created_at: {
30 | allowNull: false,
31 | type: Sequelize.DATE
32 | },
33 | updated_at: {
34 | allowNull: false,
35 | type: Sequelize.DATE
36 | }
37 | });
38 | },
39 | down: (queryInterface, Sequelize) => {
40 | return queryInterface.dropTable('agreements');
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/client/test/unit/specs/Home.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'avoriaz';
2 | import Home from '@/components/Home';
3 |
4 | describe('Home.vue', () => {
5 | it('has a div as wrapper', () => {
6 | const wrapper = mount(Home);
7 | expect(wrapper.is('div')).to.equal(true);
8 | });
9 |
10 | it('has a wrapper with class container', () => {
11 | const wrapper = mount(Home);
12 | expect(wrapper.hasClass('container')).to.equal(true);
13 | });
14 |
15 | it('has a wrapper with class splash', () => {
16 | const wrapper = mount(Home);
17 | expect(wrapper.hasClass('splash')).to.equal(true);
18 | });
19 |
20 | it('has a header for idea nebulae', () => {
21 | const wrapper = mount(Home);
22 | const expectedMessage = 'Idea Nebulae';
23 | expect(wrapper.find('.sr-only')[0].text()).to.equal(expectedMessage);
24 | });
25 |
26 | it('has a flip method', () => {
27 | const wrapper = mount(Home);
28 | expect(typeof wrapper.methods().flip).to.equal('function');
29 | });
30 |
31 | it('has an unflip method', () => {
32 | const wrapper = mount(Home);
33 | expect(typeof wrapper.methods().unflip).to.equal('function');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/server/services/config.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv');
2 |
3 | dotenv.config();
4 |
5 | let config = {};
6 | config.app = {};
7 | config.db = {};
8 |
9 | config.app.debuglog = process.env.DEBUGLOG === undefined
10 | ? false
11 | : process.env.DEBUGLOG.toUpperCase() === 'TRUE' ? true : false;
12 |
13 | config.db.dbuserid = `${process.env.DBUSERID}`;
14 | config.db.dbpasswd = `${process.env.DBPASSWD}`;
15 | config.db.dbdomain = `${process.env.DBDOMAIN}`;
16 | config.db.dbhost = `${process.env.DBHOST}`;
17 | config.db.dbport = `${process.env.DBPORT}`;
18 | config.db.dbname = `${process.env.DBNAME}`;
19 | config.db.dburl = `${process.env.DBURL}`;
20 |
21 | if (config.app.debuglog) {
22 | console.log(`config.app.debuglog = ${process.env.DEBUGLOG}`);
23 | console.log(`config.db.dbuserid = ${process.env.DBUSERID}`);
24 | console.log(`config.db.dbpasswd = ${process.env.DBPASSWD}`);
25 | console.log(`config.db.dbdomain = ${process.env.DBDOMAIN}`);
26 | console.log(`config.db.dbhost = ${process.env.DBHOST}`);
27 | console.log(`config.db.dbport = ${process.env.DBPORT}`);
28 | console.log(`config.db.dbname = ${process.env.DBNAME}`);
29 | console.log(`config.db.dburl = ${process.env.DBURL}`);
30 | }
31 |
32 | module.exports = config;
33 |
--------------------------------------------------------------------------------
/client/test/e2e/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 | var config = require('../../config')
3 |
4 | // http://nightwatchjs.org/gettingstarted#settings-file
5 | module.exports = {
6 | src_folders: ['test/e2e/specs'],
7 | output_folder: 'test/e2e/reports',
8 | custom_assertions_path: ['test/e2e/custom-assertions'],
9 |
10 | selenium: {
11 | start_process: true,
12 | server_path: require('selenium-server').path,
13 | host: '127.0.0.1',
14 | port: 4444,
15 | cli_args: {
16 | 'webdriver.chrome.driver': require('chromedriver').path
17 | }
18 | },
19 |
20 | test_settings: {
21 | default: {
22 | selenium_port: 4444,
23 | selenium_host: 'localhost',
24 | silent: true,
25 | globals: {
26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
27 | }
28 | },
29 |
30 | chrome: {
31 | desiredCapabilities: {
32 | browserName: 'chrome',
33 | javascriptEnabled: true,
34 | acceptSslCerts: true
35 | }
36 | },
37 |
38 | firefox: {
39 | desiredCapabilities: {
40 | browserName: 'firefox',
41 | javascriptEnabled: true,
42 | acceptSslCerts: true
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/client/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | // 1. start the dev server using production config
2 | process.env.NODE_ENV = 'testing';
3 | var server = require('../../build/dev-server.js');
4 |
5 | server.ready.then(() => {
6 | // 2. run the nightwatch test suite against it
7 | // to run in additional browsers:
8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
9 | // 2. add it to the --env flag below
10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
11 | // For more information on Nightwatch's config file, see
12 | // http://nightwatchjs.org/guide#settings-file
13 | var opts = process.argv.slice(2);
14 | if (opts.indexOf('--config') === -1) {
15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']);
16 | }
17 | if (opts.indexOf('--env') === -1) {
18 | opts = opts.concat(['--env', 'chrome']);
19 | }
20 |
21 | var spawn = require('cross-spawn');
22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' });
23 |
24 | runner.on('exit', function (code) {
25 | server.close();
26 | process.exit(code);
27 | });
28 |
29 | runner.on('error', function (err) {
30 | server.close();
31 | throw err;
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/client/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | var ora = require('ora')
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var webpack = require('webpack')
10 | var config = require('../config')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | if (stats.hasErrors()) {
30 | console.log(chalk.red(' Build failed with errors.\n'))
31 | process.exit(1)
32 | }
33 |
34 | console.log(chalk.cyan(' Build complete.\n'))
35 | console.log(chalk.yellow(
36 | ' Tip: built files are meant to be served over an HTTP server.\n' +
37 | ' Opening index.html over file:// won\'t work.\n'
38 | ))
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/server/db/migrations/20171219142211-create-review.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('reviews', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | idea_id: {
12 | type: Sequelize.INTEGER,
13 | onDelete: 'CASCADE',
14 | references: {
15 | model: 'ideas',
16 | key: 'id',
17 | },
18 | allowNull: false,
19 | },
20 | reviewer_id: {
21 | type: Sequelize.INTEGER,
22 | onDelete: 'CASCADE',
23 | references: {
24 | model: 'profiles',
25 | key: 'id',
26 | },
27 | allowNull: false,
28 | },
29 | comments: {
30 | type: Sequelize.TEXT,
31 | allowNull: false,
32 | },
33 | created_at: {
34 | allowNull: false,
35 | type: Sequelize.DATE
36 | },
37 | updated_at: {
38 | allowNull: false,
39 | type: Sequelize.DATE
40 | }
41 | });
42 | },
43 | down: (queryInterface, Sequelize) => {
44 | return queryInterface.dropTable('reviews');
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | dist/
61 | package-lock.json
62 |
63 | # Yarn and NPM locks
64 | package-lock.json
65 | yarn.lock
66 |
--------------------------------------------------------------------------------
/server/db/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | development: {
3 | username: process.env.DBUSERID || 'postgres',
4 | password: process.env.DBPASSWD || 'postgres',
5 | database: process.env.DBNAME || 'ideanebulae_dev',
6 | host: process.env.DBHOST || 'localhost',
7 | port: process.env.DBPORT || '5432',
8 | dialect: 'postgres',
9 | logging: console.log,
10 | define: {
11 | underscored: true, // snake_case timestamps
12 | underscoredAll: true, // Converts camelCase model names to snake_case
13 | },
14 | },
15 | test: {
16 | username: process.env.DBUSERID,
17 | password: process.env.DBPASSWD,
18 | database: process.env.DBNAME,
19 | host: process.env.DBHOST,
20 | port: process.env.DBPORT,
21 | dialect: 'postgres',
22 | define: {
23 | underscored: true,
24 | underscoredAll: true,
25 | },
26 | },
27 | production: {
28 | username: process.env.DBUSERID,
29 | password: process.env.DBPASSWD,
30 | database: process.env.DBNAME,
31 | host: process.env.DBHOST,
32 | port: process.env.DBPORT,
33 | use_env_variable: 'DATABASE_URL',
34 | dialect: 'postgres',
35 | define: {
36 | underscored: true,
37 | underscoredAll: true,
38 | },
39 | },
40 | }
41 |
--------------------------------------------------------------------------------
/client/src/assets/shield_w.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
--------------------------------------------------------------------------------
/client/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var webpack = require('webpack')
3 | var config = require('../config')
4 | var merge = require('webpack-merge')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 |
9 | // add hot-reload related code to entry chunks
10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
12 | })
13 |
14 | module.exports = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
17 | },
18 | // cheap-module-eval-source-map is faster for development
19 | devtool: '#cheap-module-eval-source-map',
20 | plugins: [
21 | new webpack.DefinePlugin({
22 | 'process.env': config.dev.env
23 | }),
24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
25 | new webpack.HotModuleReplacementPlugin(),
26 | new webpack.NoEmitOnErrorsPlugin(),
27 | // https://github.com/ampedandwired/html-webpack-plugin
28 | new HtmlWebpackPlugin({
29 | filename: 'index.html',
30 | template: 'index.html',
31 | inject: true
32 | }),
33 | new FriendlyErrorsPlugin()
34 | ]
35 | })
36 |
--------------------------------------------------------------------------------
/server/db/migrations/20171219012348-create-idea.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('ideas', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER,
10 | },
11 | title: {
12 | type: Sequelize.STRING,
13 | allowNull: false,
14 | },
15 | description: {
16 | type: Sequelize.TEXT,
17 | allowNull: false,
18 | },
19 | idea_type: {
20 | type: Sequelize.ENUM,
21 | values: ['public', 'private', 'commercial'],
22 | defaultValue: 'public',
23 | allowNull: false,
24 | },
25 | profile_id: {
26 | type: Sequelize.INTEGER,
27 | onDelete: 'CASCADE',
28 | references: {
29 | model: 'profiles',
30 | key: 'id',
31 | },
32 | allowNull: false,
33 | },
34 | tags: {
35 | type: Sequelize.ARRAY(Sequelize.TEXT),
36 | allowNull: true,
37 | },
38 | created_at: {
39 | allowNull: false,
40 | type: Sequelize.DATE,
41 | },
42 | updated_at: {
43 | allowNull: false,
44 | type: Sequelize.DATE,
45 | }
46 | });
47 | },
48 | down: (queryInterface, Sequelize) => {
49 | return queryInterface.dropTable('ideas');
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/client/test/unit/specs/users/UserProfile.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from 'avoriaz';
2 | import UserProfile from '@/components/users/UserProfile';
3 |
4 | describe('UserProfile.vue', () => {
5 | it('has a div as wrapper', () => {
6 | const wrapper = mount(UserProfile);
7 | expect(wrapper.is('div')).to.equal(true);
8 | });
9 |
10 | it('has a wrapper with class container', () => {
11 | const wrapper = mount(UserProfile);
12 | expect(wrapper.hasClass('container')).to.equal(true);
13 | });
14 |
15 | it('has a wrapper with profile container', () => {
16 | const wrapper = mount(UserProfile);
17 | expect(wrapper.hasClass('profile')).to.equal(true);
18 | });
19 |
20 | it('has a save qualifications method', () => {
21 | const wrapper = mount(UserProfile);
22 | expect(typeof wrapper.methods().saveQualifications).to.equal('function');
23 | });
24 |
25 | it('has a cancel qualifications method', () => {
26 | const wrapper = mount(UserProfile);
27 | expect(typeof wrapper.methods().cancelQualifications).to.equal('function');
28 | });
29 |
30 | it('has an adjust text area method', () => {
31 | const wrapper = mount(UserProfile);
32 | expect(typeof wrapper.methods().adjustTextArea).to.equal('function');
33 | });
34 |
35 | it('has a toggle edit method', () => {
36 | const wrapper = mount(UserProfile);
37 | expect(typeof wrapper.methods().toggleEdit).to.equal('function');
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | test/unit/coverage
8 | test/e2e/reports
9 | selenium-debug.log
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 | *.pid.lock
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # nyc test coverage
24 | .nyc_output
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # Bower dependency directory (https://bower.io/)
30 | bower_components
31 |
32 | # node-waf configuration
33 | .lock-wscript
34 |
35 | # Compiled binary addons (http://nodejs.org/api/addons.html)
36 | build/Release
37 |
38 | # Dependency directories
39 | node_modules/
40 | jspm_packages/
41 |
42 | # Typescript v1 declaration files
43 | typings/
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional eslint cache
49 | .eslintcache
50 |
51 | # Optional REPL history
52 | .node_repl_history
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 |
60 | # dotenv environment variables file
61 | .env
62 |
63 | # Distribution
64 | dist/
65 |
66 | # Mac stuff
67 | .DS_Store
68 |
69 | # Editor directories and files
70 | .idea
71 | *.suo
72 | *.ntvs*
73 | *.njsproj
74 | *.sln
75 |
76 | # Yarn and NPM locks
77 | package-lock.json
78 | yarn.lock
79 |
--------------------------------------------------------------------------------
/client/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | }
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/db/seeders/profile_seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert("profiles", [
6 | {
7 | user_id: "github|1287072",
8 | username: "jdmedlock",
9 | name: "Jim Medlock",
10 | avatar_url: "https://avatars3.githubusercontent.com/u/1287072?v=4",
11 | qualifications: "I love empanadas!",
12 | created_at: '2017-12-19 00:00:00',
13 | updated_at: '2017-12-19 00:00:00'
14 | }, {
15 | user_id: "github|8445249",
16 | username: "Parminder Singh",
17 | name: "null",
18 | avatar_url: "null",
19 | qualifications: "null",
20 | created_at: '2017-12-19 00:00:00',
21 | updated_at: '2017-12-19 00:00:00'
22 | }, {
23 | user_id: "github|11398826",
24 | username: "oxyrus",
25 | name: "Andrés Pérez",
26 | avatar_url: "TBD",
27 | qualifications: "TBD",
28 | created_at: '2017-12-19 00:00:00',
29 | updated_at: '2017-12-19 00:00:00'
30 | }, {
31 | user_id: "github|24995773",
32 | username: "rifkegribenes",
33 | name: "Sarah Schneider",
34 | avatar_url: "https://avatars2.githubusercontent.com/u/24995773?v=4",
35 | qualifications: "Designer extrordinaire",
36 | created_at: '2017-12-19 00:00:00',
37 | updated_at: '2017-12-19 00:00:00'
38 | }
39 | ], {});
40 | },
41 | down: (queryInterface, Sequelize) => {
42 | return queryInterface.bulkDelete('profiles', null, {});
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/client/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css'],
18 | // Run the build command with an extra argument to
19 | // View the bundle analyzer report after build finishes:
20 | // `npm run build --report`
21 | // Set to `true` or `false` to always turn it on or off
22 | bundleAnalyzerReport: process.env.npm_config_report
23 | },
24 | dev: {
25 | env: require('./dev.env'),
26 | port: 8080,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {},
31 | // CSS Sourcemaps off by default because relative paths are "buggy"
32 | // with this option, according to the CSS-Loader README
33 | // (https://github.com/webpack/css-loader#sourcemaps)
34 | // In our experience, they generally work as expected,
35 | // just be aware of this issue when enabling this option.
36 | cssSourceMap: false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/db/models/idea.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var Idea = sequelize.define('Idea', {
4 | id: {
5 | type: DataTypes.INTEGER,
6 | primaryKey: true,
7 | autoIncrement: true,
8 | allowNull: false,
9 | },
10 |
11 | title: {
12 | type: DataTypes.STRING,
13 | allowNull: false,
14 | },
15 |
16 | description: {
17 | type: DataTypes.TEXT,
18 | allowNull: false,
19 | },
20 |
21 | idea_type: {
22 | type: DataTypes.ENUM,
23 | values: ['public', 'private', 'commercial'],
24 | defaultValue: 'public',
25 | allowNull: false,
26 | },
27 |
28 | profile_id: {
29 | type: DataTypes.INTEGER,
30 | onDelete: 'CASCADE',
31 | references: {
32 | model: 'Profiles',
33 | key: 'id',
34 | },
35 | allowNull: false,
36 | },
37 |
38 | tags: {
39 | type: DataTypes.ARRAY(DataTypes.TEXT),
40 | allowNull: true,
41 | },
42 |
43 | }, {
44 | underscored: true,
45 | classMethods: {
46 | associate: function (models) {
47 | // associations can be defined here
48 | Idea.belongsTo(models.Profile);
49 | Idea.hasOne(models.Agreement);
50 | Idea.hasMany(models.Review);
51 | Idea.hasMany(models.Document);
52 | }
53 | },
54 | indexes: [
55 | // Create a unique index on id
56 | {
57 | unique: true,
58 | fields: ['id']
59 | },
60 |
61 | // Creates a gin index on the tags array
62 | {
63 | unique: false,
64 | fields: ['tags'],
65 | using: 'gin',
66 | },
67 | ]
68 | });
69 | return Idea;
70 | };
71 |
--------------------------------------------------------------------------------
/client/static/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
31 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ideanebulae-api",
3 | "version": "0.0.2",
4 | "description": "The API powering IdeaNebulae",
5 | "main": "server.js",
6 | "scripts": {
7 | "build": "rimraf dist/ && babel ./ --out-dir dist/ --ignore ./node_modules,./.babelrc,./package.json,./npm-debug.log --copy-files",
8 | "createdb": "sequelize db:drop && sequelize db:create && sequelize db:migrate",
9 | "loadprof": "sequelize db:seed --seed profile_seed",
10 | "loadidea": "sequelize db:seed --seed idea_seed",
11 | "loadagre": "sequelize db:seed --seed agreement_seed",
12 | "loaddoc": "sequelize db:seed --seed document_seed",
13 | "loadrev": "sequelize db:seed --seed review_seed",
14 | "createviews": "psql -U postgres -d ideanebulae_dev -f ./db/models/postgres_views.sql -h localhost -p 5432",
15 | "initdb": "npm run createdb && npm run loadprof && npm run loadidea && npm run loadagre && npm run loaddoc && npm run loadrev && npm run createviews",
16 | "deploy": "git subtree push — prefix dist heroku master",
17 | "start": "npm run build && node dist/server.js",
18 | "test": "echo \"Error: no test specified\" && exit 1"
19 | },
20 | "keywords": [
21 | "ideanebulae",
22 | "express",
23 | "api",
24 | "auth0",
25 | "vue"
26 | ],
27 | "author": "Bears-27",
28 | "license": "MIT",
29 | "dependencies": {
30 | "babel": "^6.23.0",
31 | "babel-cli": "^6.26.0",
32 | "babel-preset-es2015": "^6.24.1",
33 | "body-parser": "^1.18.2",
34 | "cors": "^2.8.4",
35 | "dotenv": "^4.0.0",
36 | "express": "^4.16.2",
37 | "express-jwt": "^5.3.0",
38 | "jwks-rsa": "^1.2.0",
39 | "jwt-decode": "^2.2.0",
40 | "mongodb": "^2.2.33",
41 | "mongoose": "^4.12.3",
42 | "pg": "^6.4.2",
43 | "pg-hstore": "^2.3.2",
44 | "rimraf": "^2.6.2",
45 | "sequelize": "^4.27.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/client/src/assets/rocket_w.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
30 |
--------------------------------------------------------------------------------
/server/router/reviews.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const models = require('../db/models');
3 |
4 | const router = express.Router();
5 |
6 | /**
7 | * @description Add a new reviewer to an idea document identified by the specified
8 | * creator, title, and type.
9 | * @param {Integer} req.query.ideaid - The idea's unique identier
10 | * @param {String} req.query.reviewername - The reviewers user name
11 | * @param {Integer} req.body.idea_profile_id - Profile id of the idea creator
12 | * @param {String} req.body.idea_type - Type of the idea
13 | * @param {String} req.body.idea_title - The title of the idea
14 | * @param {String} req.body.comments - Review comments
15 | * @returns {Object} If successfully updated the new row is returned.
16 | */
17 | router.post(':ideaid(*):reviewername(*)', (req,res) => {
18 | models.Profile.findOne(
19 | {
20 | where: {user_id: req.query.reviewername},
21 | }
22 | )
23 | .then((profile) => {
24 | models.Review.create({
25 | idea_id: req.query.ideaid,
26 | reviewer_id: profile.id,
27 | comments: req.body.comment,
28 | })
29 | .then(review => {
30 | res.json(review);
31 | })
32 | .catch(err => {
33 | res.send(err);
34 | });
35 | })
36 | .catch(err => {
37 | res.send(err);
38 | });
39 | });
40 |
41 | /**
42 | * @description Update an existing review.
43 | * @param {Integer} req.query.ideaid - The idea's unique identier
44 | * @param {Integer} req.query.reviewerid - The reviewers profile id
45 | * @param {String} req.body.comments - Review comments
46 | * @returns {Object} If successfully updated the new row is returned.
47 | */
48 | router.put('/review/:ideaid(*):reviewerid(*)', (req, res) => {
49 | models.Review.update({
50 | comments: req.body.comment,
51 | },
52 | {
53 | where: {
54 | idea_id: req.query.ideaid,
55 | reviewer_id: req.query.reviewerid,
56 | },
57 | })
58 | .then(result => {
59 | res.json(result);
60 | })
61 | .catch(err => {
62 | res.send(err)
63 | });
64 | })
65 |
66 | module.exports = router;
67 |
--------------------------------------------------------------------------------
/client/src/assets/shield.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | // import { requireAuth } from '@/auth';
4 | import Home from '@/components/Home';
5 | import Dashboard from '@/components/ideas/Dashboard';
6 | import CreateIdea from '@/components/ideas/CreateIdea';
7 | import EditIdea from '@/components/ideas/EditIdea';
8 | import ReviewIdea from '@/components/ideas/ReviewIdea';
9 | import IdeaDetails from '@/components/ideas/IdeaDetails';
10 | import ExploreIdeas from '@/components/ideas/ExploreIdeas';
11 | import Agreement from '@/components/misc/Agreement';
12 | import UserProfile from '@/components/users/UserProfile';
13 | import Callback from '@/components/Callback';
14 |
15 | Vue.use(Router);
16 |
17 | // requireAuth can be used like this:
18 | // {
19 | // path: '/ideas',
20 | // name: 'IdeasList',
21 | // beforeEnter: requireAuth,
22 | // }
23 |
24 | export default new Router({
25 | routes: [
26 | {
27 | path: '/',
28 | name: 'Home',
29 | component: Home,
30 | },
31 | {
32 | path: '/dashboard',
33 | name: 'Dashboard',
34 | component: Dashboard,
35 | },
36 | {
37 | path: '/create',
38 | name: 'CreateIdea',
39 | component: CreateIdea,
40 | },
41 | {
42 | path: '/edit/:creatorId/:title/:type',
43 | name: 'EditIdea',
44 | component: EditIdea,
45 | },
46 | {
47 | path: '/ideas/:creatorId/:title/:type',
48 | name: 'IdeaDetails',
49 | component: IdeaDetails,
50 | },
51 | {
52 | path: '/explore',
53 | name: 'ExploreIdeas',
54 | component: ExploreIdeas,
55 | },
56 | {
57 | path: '/profile/:username',
58 | name: 'UserProfile',
59 | component: UserProfile,
60 | },
61 | {
62 | path: '/agreement',
63 | name: 'Agreement',
64 | component: Agreement,
65 | },
66 | {
67 | path: '/review/:creatorId/:title/:type',
68 | name: 'ReviewIdea',
69 | component: ReviewIdea,
70 | },
71 | {
72 | path: '/callback',
73 | name: 'Callback',
74 | component: Callback,
75 | },
76 | ],
77 | mode: 'history',
78 | });
79 |
--------------------------------------------------------------------------------
/server/db/seeders/agreement_seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | queryInterface.bulkInsert("agreements", [
6 | {
7 | idea_id: 4,
8 | version: 12,
9 | agreement: "This is my own little agreement. Please honor it or there will be no empanadas for you!",
10 | created_at: '2017-12-19 00:00:00',
11 | updated_at: '2017-12-19 00:00:00'
12 | }, {
13 | idea_id: 1,
14 | version: 9,
15 | agreement: "Agree or it will be no empanadas for you!",
16 | created_at: '2017-12-19 00:00:00',
17 | updated_at: '2017-12-19 00:00:00'
18 | }, {
19 | idea_id: 9,
20 | version: 11,
21 | agreement: "This agreement binds you to Promise to worship Trion",
22 | created_at: '2017-12-19 00:00:00',
23 | updated_at: '2017-12-19 00:00:00'
24 | }, {
25 | idea_id: 10,
26 | version: 10,
27 | agreement: "Acceptance of this agreement certifies that you acknowledge Trion as the Supreme Overlord AI and you promise to hold empanadas created by him in reverence for all perpetuity.",
28 | created_at: '2017-12-19 00:00:00',
29 | updated_at: '2017-12-19 00:00:00'
30 | }, {
31 | idea_id: 8,
32 | version: 1,
33 | agreement: "You agree to 1) Eat more empanadas, 2) Refrain from eating, touching, or looking at any other batter coated food product, 3) Spread the good word about the beauty, quiet dignity, and general awesomeness of empanadas to your fellow Earthlings.",
34 | created_at: '2017-12-19 00:00:00',
35 | updated_at: '2017-12-19 00:00:00'
36 | }, {
37 | idea_id: 2,
38 | version: 8,
39 | agreement: "Agree or else!",
40 | created_at: '2017-12-19 00:00:00',
41 | updated_at: '2017-12-19 00:00:00'
42 | }, {
43 | idea_id: 5,
44 | version: 1,
45 | agreement: "This agreement is for the birds!",
46 | created_at: '2017-12-19 00:00:00',
47 | updated_at: '2017-12-19 00:00:00'
48 | }
49 | ], {});
50 | },
51 |
52 | down: (queryInterface, Sequelize) => {
53 | return queryInterface.bulkDelete('agreements', null, {});
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/client/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 |
15 | var cssLoader = {
16 | loader: 'css-loader',
17 | options: {
18 | minimize: process.env.NODE_ENV === 'production',
19 | sourceMap: options.sourceMap
20 | }
21 | }
22 |
23 | // generate loader string to be used with extract text plugin
24 | function generateLoaders (loader, loaderOptions) {
25 | var loaders = [cssLoader]
26 | if (loader) {
27 | loaders.push({
28 | loader: loader + '-loader',
29 | options: Object.assign({}, loaderOptions, {
30 | sourceMap: options.sourceMap
31 | })
32 | })
33 | }
34 |
35 | // Extract CSS when that option is specified
36 | // (which is the case during production build)
37 | if (options.extract) {
38 | return ExtractTextPlugin.extract({
39 | use: loaders,
40 | fallback: 'vue-style-loader'
41 | })
42 | } else {
43 | return ['vue-style-loader'].concat(loaders)
44 | }
45 | }
46 |
47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
48 | return {
49 | css: generateLoaders(),
50 | postcss: generateLoaders(),
51 | less: generateLoaders('less'),
52 | sass: generateLoaders('sass', { indentedSyntax: true }),
53 | scss: generateLoaders('sass'),
54 | stylus: generateLoaders('stylus'),
55 | styl: generateLoaders('stylus')
56 | }
57 | }
58 |
59 | // Generate loaders for standalone style files (outside of .vue)
60 | exports.styleLoaders = function (options) {
61 | var output = []
62 | var loaders = exports.cssLoaders(options)
63 | for (var extension in loaders) {
64 | var loader = loaders[extension]
65 | output.push({
66 | test: new RegExp('\\.' + extension + '$'),
67 | use: loader
68 | })
69 | }
70 | return output
71 | }
72 |
--------------------------------------------------------------------------------
/server/db/seeders/document_seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | queryInterface.bulkInsert("documents", [
6 | {
7 | idea_id: 7,
8 | url: "https://google.com",
9 | description: "https://google.com",
10 | created_at: '2017-12-19 00:00:00',
11 | updated_at: '2017-12-19 00:00:00'
12 | }, {
13 | idea_id: 9,
14 | url: "https://youtu.be/hjJOAFAriHQ",
15 | description: "https://youtu.be/hjJOAFAriHQ",
16 | created_at: '2017-12-19 00:00:00',
17 | updated_at: '2017-12-19 00:00:00'
18 | }, {
19 | idea_id: 8,
20 | url: "https://google.com",
21 | description: "The search engine of the web",
22 | created_at: '2017-12-19 00:00:00',
23 | updated_at: '2017-12-19 00:00:00'
24 | }, {
25 | idea_id: 6,
26 | url: "haha.com",
27 | description: "https://haha.com",
28 | created_at: '2017-12-19 00:00:00',
29 | updated_at: '2017-12-19 00:00:00'
30 | }, {
31 | idea_id: 2,
32 | url: "https://google.com",
33 | description: "The search engine of the web",
34 | created_at: '2017-12-19 00:00:00',
35 | updated_at: '2017-12-19 00:00:00'
36 | }, {
37 | idea_id: 6,
38 | url: "lol.com",
39 | description: "https://lol.com",
40 | created_at: '2017-12-19 00:00:00',
41 | updated_at: '2017-12-19 00:00:00'
42 | }, {
43 | idea_id: 10,
44 | url: "https://google.com",
45 | description: "The search engine of the web",
46 | created_at: '2017-12-19 00:00:00',
47 | updated_at: '2017-12-19 00:00:00'
48 | }, {
49 | idea_id: 6,
50 | url: "goooogle.com",
51 | description: "https://goooogle.com",
52 | created_at: '2017-12-19 00:00:00',
53 | updated_at: '2017-12-19 00:00:00'
54 | }, {
55 | idea_id: 1,
56 | url: "https://google.com",
57 | description: "The search engine of the web",
58 | created_at: '2017-12-19 00:00:00',
59 | updated_at: '2017-12-19 00:00:00'
60 | }
61 | ], {});
62 | },
63 |
64 | down: (queryInterface, Sequelize) => {
65 | return queryInterface.bulkDelete('documents', null, {});
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/server/router/profiles.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const models = require('../db/models');
3 |
4 | const router = express.Router();
5 |
6 | /**
7 | * List all profiles
8 | * @param {object} req - The request object
9 | * @param {object} res - The response object to write to
10 | * @return {object[]} The array of profiles
11 | */
12 | router.get('/profiles', async (req, res) => {
13 | const profiles = await models.Profile.findAll();
14 | res.json(profiles);
15 | });
16 |
17 | /**
18 | * Return one profile based on the username
19 | * @param {object} req - The request object
20 | * @param {object} res - The response object
21 | * @return {object} The profile found in the database
22 | */
23 | router.get('/profile/:username(*)', async (req, res) => {
24 | const { username } = req.query;
25 | const profile = await models.Profile.findOne({ where: { username } });
26 | res.json(profile);
27 | });
28 |
29 | /**
30 | * Create/Update the user profile for the specified user
31 | * @param {object} req - The request object
32 | * @param {object} res - The response object
33 | * @return {object} The created/updated profile
34 | */
35 | router.put('/profile/:userId(*)', async (req, res) => {
36 | const { user_id, username, name, avatar_url, qualifications } = req.body;
37 | /**
38 | * First we need to check if there's a profile for the given user_id
39 | * if that's the case, we only want to update the rest of the fields
40 | * however if it doesn't exist, we want to create one
41 | */
42 |
43 | const profile = await models.Profile.findOne({ where: { user_id } });
44 |
45 | // We found a profile, therefore we need to update it
46 | if (profile) {
47 | profile.update({
48 | username,
49 | name,
50 | avatar_url,
51 | qualifications,
52 | })
53 | .then((updatedProfile) => {
54 | res.json(updatedProfile);
55 | })
56 | .catch((err) => {
57 | res.json(err);
58 | });
59 | }
60 | // We didn't find a profile, so it's time to create one
61 | else {
62 | models.Profile.create({
63 | user_id,
64 | username,
65 | name,
66 | avatar_url,
67 | qualifications,
68 | })
69 | .then((createdProfile) => {
70 | res.json(createdProfile);
71 | })
72 | .catch((err) => {
73 | res.json(err);
74 | });
75 | }
76 | });
77 |
78 | module.exports = router;
79 |
--------------------------------------------------------------------------------
/client/src/assets/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/server/db/methods/agreementMethods.js:
--------------------------------------------------------------------------------
1 | const models = require('../models');
2 | import Idea from '../models/idea';
3 |
4 | export default class agreementMethods {
5 |
6 | /**
7 | * @description Delete an agreement document. Note that it is the responsibility of the
8 | * caller to clear the agreement id from the owning idea document.
9 | * @static
10 | * @param {String} creator Creator id of the owning idea
11 | * @param {String} title Title of the owning idea
12 | * @param {String} type Type of the owning idea
13 | * @returns {Promise} A WriteOpResult object containing the status of the operation
14 | * @memberof agreementMethods
15 | */
16 | static async deleteAgreement(creator, title, type) {
17 | return await this.deleteOne({
18 | creator: creator,
19 | title: title,
20 | type: type
21 | });
22 | }
23 |
24 | /**
25 | * @description Retrieve an agreement document based on its '_id' field
26 | * @param {any} agreementID The '_id' value of the agreement document to be located
27 | * @returns {Promise} A WriteResult object containing the status of the operation
28 | * @memberof agreementMethods
29 | */
30 | static async findAgreement(agreementID) {
31 | return await this.findById(agreementID);
32 | }
33 |
34 | /**
35 | * @description Retrieve all agreements associated with a given idea.
36 | * @param {any} ideaId The id value of the owning idea
37 | * @returns {Object} An object containing all agreements realated to the idea id
38 | * @memberof agreementMethods
39 | */
40 | static async findByIdea(ideaId) {
41 | return await models.sequelize.query(
42 | `SELECT id, \
43 | username, \
44 | idea_id, \
45 | title, \
46 | idea_type, \
47 | agreement, \
48 | version \
49 | FROM idea_agreements \
50 | WHERE idea_id = ${ideaId}`,
51 | { type: models.sequelize.QueryTypes.SELECT}
52 | );
53 | }
54 |
55 | /**
56 | * @description Add an agreement document to the database. It is expected that the
57 | * parent idea document will already exist in the database.
58 | * @param {Object} body An object containing all agreement fields.
59 | * @returns {Promise} A WriteResult object containing the status of the operation
60 | * @memberof agreementMethods
61 | */
62 | static async saveAgreement(body) {
63 | let { creator, title, type, agreement, agreement_version} = body;
64 | let newAgreement = new this();
65 |
66 | newAgreement.creator = creator;
67 | newAgreement.title = title;
68 | newAgreement.type = type;
69 | newAgreement.agreement = agreement;
70 | newAgreement.agreement_version = agreement_version;
71 | return await newAgreement.save();
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/client/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('../config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 | var webpack = require('webpack')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 | const stylus_var = resolve('./styles/var.styl')
12 |
13 | module.exports = {
14 | entry: {
15 | app: './src/main.js'
16 | },
17 | output: {
18 | path: config.build.assetsRoot,
19 | filename: '[name].js',
20 | publicPath: process.env.NODE_ENV === 'production'
21 | ? config.build.assetsPublicPath
22 | : config.dev.assetsPublicPath
23 | },
24 | resolve: {
25 | extensions: ['.js', '.vue', '.json'],
26 | alias: {
27 | 'vue$': 'vue/dist/vue.esm.js',
28 | '@': resolve('src'),
29 | stylus_var,
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.(js|vue)$/,
36 | loader: 'eslint-loader',
37 | enforce: 'pre',
38 | include: [resolve('src'), resolve('test')],
39 | options: {
40 | formatter: require('eslint-friendly-formatter')
41 | }
42 | },
43 | {
44 | test: /\.vue$/,
45 | loader: 'vue-loader',
46 | options: vueLoaderConfig
47 | },
48 | {
49 | test: /\.js$/,
50 | loader: 'babel-loader',
51 | include: [resolve('src'), resolve('test')]
52 | },
53 | {
54 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
55 | loader: 'url-loader',
56 | options: {
57 | limit: 10000,
58 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
59 | }
60 | },
61 | {
62 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
63 | loader: 'url-loader',
64 | options: {
65 | limit: 10000,
66 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
67 | }
68 | },
69 | {
70 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
71 | loader: 'url-loader',
72 | options: {
73 | limit: 10000,
74 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
75 | }
76 | }
77 | ]
78 | },
79 | plugins: [
80 | new webpack.DefinePlugin( {
81 | 'process.env': {
82 | // Heroku environment variables
83 | API_HOST: JSON.stringify(process.env.API_HOST),
84 | // Auth0 environment variables
85 | AUDIENCE: JSON.stringify(process.env.AUDIENCE),
86 | CLIENT_ID: JSON.stringify(process.env.CLIENT_ID),
87 | CLIENT_DOMAIN: JSON.stringify(process.env.CLIENT_DOMAIN),
88 | REDIRECT: JSON.stringify(process.env.REDIRECT),
89 | // MLab environment variables
90 | DBUSERID: JSON.stringify(process.env.DBUSERID),
91 | DBPASSWD: JSON.stringify(process.env.DBPASSWD),
92 | },
93 | }),
94 | ],
95 | };
96 |
--------------------------------------------------------------------------------
/client/src/assets/feedback_w.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/client/src/assets/rocket.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/server/db/seeders/review_seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | queryInterface.bulkInsert("reviews", [
6 | {
7 | idea_id: "6",
8 | reviewer_id: 1,
9 | comments: "Woot woot",
10 | created_at: "2017-11-20 15:36:45.373",
11 | updated_at: "2017-11-21 17:03:27.119",
12 | }, {
13 | idea_id: "2",
14 | reviewer_id: 3,
15 | comments: "This idea has merit, but more details are needed to fully understand the associated benefits and costs.",
16 | created_at: "2017-11-06 10:30:00",
17 | updated_at: "2017-11-06 18:00:00",
18 | }, {
19 | idea_id: "8",
20 | reviewer_id: 2,
21 | comments: "This is an awesome idea, but there may be issues with the time necessary to acquire a liquor license.",
22 | created_at: "2017-11-06 10:30:00",
23 | updated_at: "2017-11-06 18:00:00",
24 | }, {
25 | idea_id: "2",
26 | reviewer_id: 1,
27 | comments: "Have you considered rainbow colored empanadas? If not you really should. \n\nWhat is the frequency Kenneth? 42 or 43?",
28 | created_at: "2017-11-06 10:30:00",
29 | updated_at: "2017-11-22 14:41:33.385",
30 | }, {
31 | idea_id: "1",
32 | reviewer_id: 1,
33 | comments: "Can an empanada be stringified?",
34 | created_at: "2017-11-21 16:50:23.279",
35 | updated_at: "2017-11-21 16:50:23.279",
36 | }, {
37 | idea_id: "9",
38 | reviewer_id: 4,
39 | comments: "What about after effects of longterm exposure to gamma ray irradiated empanadas?",
40 | created_at: "2017-12-05 04:26:56.665",
41 | updated_at: "2017-12-05 04:26:56.665",
42 | }, {
43 | idea_id: "6",
44 | reviewer_id: 4,
45 | comments: "Here is another review, will this add???",
46 | created_at: "2017-12-10 22:42:25.356",
47 | updated_at: "2017-12-10 22:50:56.247",
48 | }, {
49 | idea_id: "10",
50 | reviewer_id: 3,
51 | comments: "Will you use a 2-cycle or 4-cycle engine?",
52 | created_at: "2017-11-06 10:30:00",
53 | updated_at: "2017-11-06 18:00:00",
54 | }, {
55 | idea_id: "6",
56 | reviewer_id: 4,
57 | comments: "Test review",
58 | created_at: "2017-12-10 22:42:30.468",
59 | updated_at: "2017-12-10 22:42:30.468",
60 | }, {
61 | idea_id: "1",
62 | reviewer_id: 2,
63 | comments: "How are you going to preserve an empanada forever?",
64 | created_at: "2017-11-06 10:30:00",
65 | updated_at: "2017-11-06 18:00:00",
66 | }, {
67 | idea_id: "3",
68 | reviewer_id: 4,
69 | comments: "It needs more dachshunds",
70 | created_at: "2017-12-04 02:38:09.023",
71 | updated_at: "2017-12-04 02:38:09.023",
72 | }, {
73 | idea_id: "2",
74 | reviewer_id: 4,
75 | comments: "Will this work if i submit a new review???",
76 | created_at: "2017-12-10 23:31:11.392",
77 | updated_at: "2017-12-10 23:32:14.873",
78 | }
79 | ], {});
80 | },
81 |
82 | down: (queryInterface, Sequelize) => {
83 | return queryInterface.bulkDelete('reviews', null, {});
84 | }
85 | };
86 |
--------------------------------------------------------------------------------
/client/src/components/shared/MainNavbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
38 |
39 |
40 |
71 |
72 |
130 |
--------------------------------------------------------------------------------
/client/src/components/shared/ModalDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 | default body
16 |
17 |
18 |
19 |
24 |
25 |
26 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
58 |
59 |
--------------------------------------------------------------------------------
/client/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | var config = require('../config')
4 |
5 | // Evaluate environment variables and assign development system default values
6 | // if their values have not been defined.
7 | if (!process.env.API_HOST) {
8 | process.env.API_HOST = 'http://localhost:7000/api';
9 | }
10 | if (!process.env.AUDIENCE) {
11 | process.env.AUDIENCE = 'https://ideanebulae.auth0.com/api/v2/';
12 | }
13 | if (!process.env.CLIENT_ID) {
14 | process.env.CLIENT_ID = '54kq1Kx1717k52deTJ55CUHzaq77fJQy';
15 | }
16 | if (!process.env.CLIENT_DOMAIN) {
17 | process.env.CLIENT_DOMAIN = 'ideanebulae.auth0.com';
18 | }
19 | if (!process.env.NODE_ENV) {
20 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV);
21 | }
22 | if (process.env.NODE_ENV !== 'production') {
23 | process.env.REDIRECT = 'http://localhost:8080/callback';
24 | }
25 |
26 | var opn = require('opn')
27 | var path = require('path')
28 | var express = require('express')
29 | var webpack = require('webpack')
30 | var proxyMiddleware = require('http-proxy-middleware')
31 | var webpackConfig = (process.env.NODE_ENV === 'testing' || process.env.NODE_ENV === 'production')
32 | ? require('./webpack.prod.conf')
33 | : require('./webpack.dev.conf')
34 |
35 | // default port where dev server listens for incoming traffic
36 | var port = process.env.PORT || config.dev.port
37 | // automatically open browser, if not set will be false
38 | var autoOpenBrowser = !!config.dev.autoOpenBrowser
39 | // Define HTTP proxies to your custom API backend
40 | // https://github.com/chimurai/http-proxy-middleware
41 | var proxyTable = config.dev.proxyTable
42 |
43 | var app = express()
44 | var compiler = webpack(webpackConfig)
45 |
46 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
47 | publicPath: webpackConfig.output.publicPath,
48 | quiet: true
49 | })
50 |
51 | var hotMiddleware = require('webpack-hot-middleware')(compiler, {
52 | log: false,
53 | heartbeat: 2000
54 | })
55 | // force page reload when html-webpack-plugin template changes
56 | compiler.plugin('compilation', function (compilation) {
57 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
58 | hotMiddleware.publish({ action: 'reload' })
59 | cb()
60 | })
61 | })
62 |
63 | // proxy api requests
64 | Object.keys(proxyTable).forEach(function (context) {
65 | var options = proxyTable[context]
66 | if (typeof options === 'string') {
67 | options = { target: options }
68 | }
69 | app.use(proxyMiddleware(options.filter || context, options))
70 | })
71 |
72 | // handle fallback for HTML5 history API
73 | app.use(require('connect-history-api-fallback')())
74 |
75 | // serve webpack bundle output
76 | app.use(devMiddleware)
77 |
78 | // enable hot-reload and state-preserving
79 | // compilation error display
80 | app.use(hotMiddleware)
81 |
82 | // serve pure static assets
83 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
84 | app.use(staticPath, express.static('./static'))
85 |
86 | var uri = 'http://localhost:' + port
87 |
88 | var _resolve
89 | var readyPromise = new Promise(resolve => {
90 | _resolve = resolve
91 | })
92 |
93 | console.log('> Starting dev server...')
94 | devMiddleware.waitUntilValid(() => {
95 | console.log('> Listening at ' + uri + '\n')
96 | // when env is testing, don't need open it
97 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
98 | opn(uri)
99 | }
100 | _resolve()
101 | })
102 |
103 | var server = app.listen(port)
104 |
105 | module.exports = {
106 | ready: readyPromise,
107 | close: () => {
108 | server.close()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/server/db/models/postgres_views.sql:
--------------------------------------------------------------------------------
1 | -- These views must be manually defined in Postgres since Sequelize does not
2 | -- support view creation.
3 |
4 | -- View: public.idea_agreements
5 | --
6 | -- This view produces a list of all agreements for an idea
7 | --
8 | --DROP VIEW public.idea_agreements;
9 | CREATE OR REPLACE VIEW public.idea_agreements AS
10 | SELECT agreements.id,
11 | profiles.username,
12 | agreements.idea_id,
13 | ideas.title,
14 | ideas.idea_type,
15 | agreements.agreement,
16 | agreements.version
17 | FROM agreements,
18 | ideas,
19 | profiles
20 | WHERE ideas.id = agreements.idea_id
21 | AND ideas.profile_id = profiles.id
22 | ORDER BY profiles.username;
23 |
24 | ALTER TABLE public.idea_agreements
25 | OWNER TO postgres;
26 |
27 | -- View: public.idea_documents
28 | --
29 | -- This view produces a list of all documents for an idea
30 | --
31 | --DROP VIEW public.idea_documents;
32 | CREATE OR REPLACE VIEW public.idea_documents AS
33 | SELECT documents.id,
34 | profiles.username,
35 | documents.idea_id,
36 | ideas.title,
37 | ideas.idea_type,
38 | documents.url,
39 | documents.description
40 | FROM ideas,
41 | documents,
42 | profiles
43 | WHERE ideas.id = documents.idea_id
44 | AND ideas.profile_id = profiles.id
45 | ORDER BY profiles.username;
46 |
47 | ALTER TABLE public.idea_documents
48 | OWNER TO postgres;
49 |
50 | -- View: public.idea_reviews
51 | --
52 | -- This view produces a list of all reviews of an idea
53 | --
54 | --DROP VIEW public.idea_reviews;
55 | CREATE OR REPLACE VIEW public.idea_reviews AS
56 | SELECT reviews.id,
57 | reviews.idea_id,
58 | ideaowners.username AS idea_creator,
59 | ideas.title,
60 | ideas.idea_type,
61 | reviews.reviewer_id,
62 | reviewers.user_id AS reviewer,
63 | reviews.comments,
64 | reviews.created_at,
65 | reviews.updated_at
66 | FROM ideas,
67 | reviews,
68 | profiles ideaowners,
69 | profiles reviewers
70 | WHERE ideas.id = reviews.idea_id
71 | AND ideas.profile_id = ideaowners.id
72 | AND reviews.reviewer_id = reviewers.id
73 | ORDER BY reviews.idea_id, reviews.id, reviewers.username;
74 |
75 | ALTER TABLE public.idea_reviews
76 | OWNER TO postgres;
77 |
78 |
79 |
80 | -- View: public.review_status
81 | --
82 | -- This view results in a single row for each idea containing its status and
83 | -- status date, as well as its most recent review creation date, and update
84 | --
85 | --DROP VIEW public.review_status;
86 | CREATE OR REPLACE VIEW public.review_status AS
87 | SELECT DISTINCT ON (all_reviews.idea_id) all_reviews.idea_id,
88 | dated_reviews.maxcreated,
89 | dated_reviews.maxupdated,
90 | CASE
91 | WHEN count(all_reviews.*) > 0 THEN 'Assigned'::text
92 | ELSE 'Created'::text
93 | END AS status,
94 | CASE
95 | WHEN dated_reviews.maxupdated = NULL::timestamp with time zone THEN dated_reviews.maxcreated
96 | ELSE dated_reviews.maxupdated
97 | END AS status_dt
98 | FROM reviews all_reviews,
99 | ( SELECT this_review.idea_id,
100 | MAX(this_review.created_at) AS maxcreated,
101 | MAX(this_review.updated_at) AS maxupdated
102 | FROM reviews this_review
103 | WHERE this_review.idea_id = this_review.idea_id
104 | GROUP BY this_review.idea_id) dated_reviews
105 | GROUP BY all_reviews.idea_id, dated_reviews.maxcreated, dated_reviews.maxupdated
106 | ORDER BY all_reviews.idea_id, dated_reviews.maxupdated DESC;
107 |
108 | ALTER TABLE public.review_status
109 | OWNER TO postgres;
110 |
--------------------------------------------------------------------------------
/client/src/auth/index.js:
--------------------------------------------------------------------------------
1 | // import axios from 'axios';
2 | import auth0 from 'auth0-js';
3 | import Router from 'vue-router';
4 | // import Auth0Lock from 'auth0-lock';
5 |
6 | import { isTokenExpired } from './utils';
7 |
8 | const ACCESS_TOKEN_KEY = 'access_token';
9 | const AUDIENCE = process.env.AUDIENCE;
10 | const CLIENT_ID = process.env.CLIENT_ID;
11 | const CLIENT_DOMAIN = process.env.CLIENT_DOMAIN;
12 | const ID_TOKEN_KEY = 'id_token';
13 | const REDIRECT = (process.env.NODE_ENV === 'production')
14 | ? `${window.location.protocol}//${window.location.hostname}/callback`
15 | : 'http://localhost:8080/callback';
16 | const SCOPE = 'openid profile';
17 |
18 | const auth = new auth0.WebAuth({
19 | clientID: CLIENT_ID,
20 | domain: CLIENT_DOMAIN,
21 | });
22 |
23 | const router = new Router({
24 | mode: 'history',
25 | });
26 |
27 | export function login() {
28 | auth.authorize({
29 | responseType: 'token id_token',
30 | redirectUri: REDIRECT,
31 | audience: AUDIENCE,
32 | scope: SCOPE,
33 | });
34 | }
35 |
36 | // Helper to extract the access token and id token
37 | function getParameterByName(name) {
38 | // eslint-disable-next-line
39 | let match = RegExp('[#&]' + name + '=([^&]*)').exec(window.location.hash);
40 | // const match = RegExp(`[#&] ${name} =([^&]*)`).exec(window.location.hash);
41 | return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
42 | }
43 |
44 | // Get the id token from the localStorage
45 | export function getIdToken() {
46 | return window.localStorage.getItem(ID_TOKEN_KEY);
47 | }
48 |
49 | // Get the access token from the localStorage
50 | export function getAccessToken() {
51 | return window.localStorage.getItem(ACCESS_TOKEN_KEY);
52 | }
53 |
54 | export function setAccessToken() {
55 | const accessToken = getParameterByName('access_token');
56 | window.localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
57 | }
58 |
59 | export function setIdToken() {
60 | const idToken = getParameterByName('id_token');
61 | window.localStorage.setItem(ID_TOKEN_KEY, idToken);
62 | }
63 |
64 | // Remove the id token from the localStorage
65 | export function clearIdToken() {
66 | window.localStorage.removeItem(ID_TOKEN_KEY);
67 | }
68 |
69 | // Remove the access token from the localStorage
70 | export function clearAccessToken() {
71 | window.localStorage.removeItem(ACCESS_TOKEN_KEY);
72 | }
73 |
74 | // Check if there's a token and it's not expired
75 | export function isLoggedIn() {
76 | const idToken = getIdToken();
77 | return !!idToken && !isTokenExpired(idToken);
78 | }
79 |
80 | // Logout the current user by deleting the tokens
81 | export function logout() {
82 | clearIdToken();
83 | clearAccessToken();
84 | router.go('/');
85 | }
86 |
87 | // Middleware to check if the user is logged in
88 | export function requireAuth(to, from, next) {
89 | if (!isLoggedIn()) {
90 | next({
91 | path: '/',
92 | query: { redirect: to.fullPath },
93 | });
94 | } else {
95 | next();
96 | }
97 | }
98 |
99 | // eslint-disable-next-line
100 | export function getUserProfile() {
101 | const accessToken = getAccessToken();
102 |
103 | if (accessToken) {
104 | return new Promise((resolve, reject) => {
105 | auth.client.userInfo(accessToken, (err, profile) => {
106 | // There was an error fetching the user profile
107 | // reject with the error
108 | if (err) {
109 | reject(err);
110 | }
111 | // We were able to fetch the user profile, resolve using it
112 | resolve(profile);
113 | });
114 | });
115 | }
116 |
117 | // There's no access token therefore we can't fetch the user
118 | // profile, return null then
119 | return null;
120 | }
121 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ideabox",
3 | "version": "1.0.1",
4 | "description": "A webapp to post and get feedback on your ideas",
5 | "author": "Parminder Singh ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "node build/dev-server.js",
10 | "build": "node build/build.js",
11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
12 | "e2e": "node test/e2e/runner.js",
13 | "test": "npm run unit && npm run e2e",
14 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
15 | },
16 | "dependencies": {
17 | "auth0-js": "^8.10.1",
18 | "autosize": "^4.0.0",
19 | "axios": "^0.16.2",
20 | "json-stable-stringify": "^1.0.1",
21 | "jwt-decode": "^2.2.0",
22 | "lodash.debounce": "^4.0.8",
23 | "marked": "^0.3.6",
24 | "postcss": "^6.0.13",
25 | "stylelint": "^8.2.0",
26 | "stylus": "^0.54.5",
27 | "stylus-loader": "^3.0.1",
28 | "vee-validate": "^2.0.0-rc.19",
29 | "vue": "^2.4.2",
30 | "vue-router": "^2.7.0",
31 | "vuex": "^2.4.1",
32 | "autoprefixer": "^7.1.2",
33 | "avoriaz": "^6.0.1",
34 | "babel-core": "^6.22.1",
35 | "babel-eslint": "^7.1.1",
36 | "babel-loader": "^7.1.1",
37 | "babel-plugin-istanbul": "^4.1.1",
38 | "babel-plugin-transform-runtime": "^6.22.0",
39 | "babel-polyfill": "^6.26.0",
40 | "babel-preset-env": "^1.3.2",
41 | "babel-preset-stage-2": "^6.22.0",
42 | "babel-register": "^6.22.0",
43 | "chai": "^3.5.0",
44 | "chalk": "^2.1.0",
45 | "chromedriver": "^2.27.2",
46 | "connect-history-api-fallback": "^1.3.0",
47 | "copy-webpack-plugin": "^4.0.1",
48 | "cross-env": "^5.0.1",
49 | "cross-spawn": "^5.0.1",
50 | "css-loader": "^0.28.0",
51 | "cssnano": "^3.10.0",
52 | "eslint": "^3.19.0",
53 | "eslint-config-airbnb-base": "^11.1.3",
54 | "eslint-friendly-formatter": "^3.0.0",
55 | "eslint-import-resolver-webpack": "^0.8.1",
56 | "eslint-loader": "^1.7.1",
57 | "eslint-plugin-html": "^3.0.0",
58 | "eslint-plugin-import": "^2.2.0",
59 | "eventsource-polyfill": "^0.9.6",
60 | "express": "^4.14.1",
61 | "extract-text-webpack-plugin": "^2.0.0",
62 | "file-loader": "^0.11.1",
63 | "friendly-errors-webpack-plugin": "^1.1.3",
64 | "html-webpack-plugin": "^2.28.0",
65 | "http-proxy-middleware": "^0.17.3",
66 | "inject-loader": "^3.0.0",
67 | "karma": "^1.4.1",
68 | "karma-coverage": "^1.1.1",
69 | "karma-mocha": "^1.3.0",
70 | "karma-phantomjs-launcher": "^1.0.2",
71 | "karma-phantomjs-shim": "^1.4.0",
72 | "karma-sinon-chai": "^1.3.1",
73 | "karma-sourcemap-loader": "^0.3.7",
74 | "karma-spec-reporter": "0.0.31",
75 | "karma-webpack": "^2.0.2",
76 | "mocha": "^3.2.0",
77 | "nightwatch": "^0.9.12",
78 | "opn": "^5.1.0",
79 | "optimize-css-assets-webpack-plugin": "^2.0.0",
80 | "ora": "^1.2.0",
81 | "phantomjs-prebuilt": "^2.1.14",
82 | "rimraf": "^2.6.0",
83 | "selenium-server": "^3.0.1",
84 | "semver": "^5.3.0",
85 | "shelljs": "^0.7.8",
86 | "sinon": "^2.1.0",
87 | "sinon-chai": "^2.8.0",
88 | "url-loader": "^0.5.8",
89 | "vue-loader": "^13.0.4",
90 | "vue-style-loader": "^3.0.1",
91 | "vue-template-compiler": "^2.4.2",
92 | "webpack": "^2.6.1",
93 | "webpack-bundle-analyzer": "^2.2.1",
94 | "webpack-dev-middleware": "^1.10.0",
95 | "webpack-hot-middleware": "^2.18.0",
96 | "webpack-merge": "^4.1.0"
97 | },
98 | "engines": {
99 | "node": "^8.0.0",
100 | "npm": "^5.0.0"
101 | },
102 | "browserslist": [
103 | "> 1%",
104 | "last 2 versions",
105 | "not ie <= 8"
106 | ]
107 | }
108 |
--------------------------------------------------------------------------------
/client/src/assets/idea-nebulae-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var CopyWebpackPlugin = require('copy-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11 |
12 | var env = process.env.NODE_ENV === 'testing'
13 | ? require('../config/test.env')
14 | : config.build.env
15 |
16 | var webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true
21 | })
22 | },
23 | devtool: config.build.productionSourceMap ? '#source-map' : false,
24 | output: {
25 | path: config.build.assetsRoot,
26 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
28 | },
29 | plugins: [
30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
31 | new webpack.DefinePlugin({
32 | 'process.env': env
33 | }),
34 | new webpack.optimize.UglifyJsPlugin({
35 | compress: {
36 | warnings: false
37 | },
38 | sourceMap: true
39 | }),
40 | // extract css into its own file
41 | new ExtractTextPlugin({
42 | filename: utils.assetsPath('css/[name].[contenthash].css')
43 | }),
44 | // Compress extracted CSS. We are using this plugin so that possible
45 | // duplicated CSS from different components can be deduped.
46 | new OptimizeCSSPlugin({
47 | cssProcessorOptions: {
48 | safe: true
49 | }
50 | }),
51 | // generate dist index.html with correct asset hash for caching.
52 | // you can customize output by editing /index.html
53 | // see https://github.com/ampedandwired/html-webpack-plugin
54 | new HtmlWebpackPlugin({
55 | filename: process.env.NODE_ENV === 'testing'
56 | ? 'index.html'
57 | : config.build.index,
58 | template: 'index.html',
59 | inject: true,
60 | minify: {
61 | removeComments: true,
62 | collapseWhitespace: true,
63 | removeAttributeQuotes: true
64 | // more options:
65 | // https://github.com/kangax/html-minifier#options-quick-reference
66 | },
67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
68 | chunksSortMode: 'dependency'
69 | }),
70 | // keep module.id stable when vender modules does not change
71 | new webpack.HashedModuleIdsPlugin(),
72 | // split vendor js into its own file
73 | new webpack.optimize.CommonsChunkPlugin({
74 | name: 'vendor',
75 | minChunks: function (module, count) {
76 | // any required modules inside node_modules are extracted to vendor
77 | return (
78 | module.resource &&
79 | /\.js$/.test(module.resource) &&
80 | module.resource.indexOf(
81 | path.join(__dirname, '../node_modules')
82 | ) === 0
83 | )
84 | }
85 | }),
86 | // extract webpack runtime and module manifest to its own file in order to
87 | // prevent vendor hash from being updated whenever app bundle is updated
88 | new webpack.optimize.CommonsChunkPlugin({
89 | name: 'manifest',
90 | chunks: ['vendor']
91 | }),
92 | // copy custom static assets
93 | new CopyWebpackPlugin([
94 | {
95 | from: path.resolve(__dirname, '../static'),
96 | to: config.build.assetsSubDirectory,
97 | ignore: ['.*']
98 | }
99 | ])
100 | ]
101 | })
102 |
103 | if (config.build.productionGzip) {
104 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
105 |
106 | webpackConfig.plugins.push(
107 | new CompressionWebpackPlugin({
108 | asset: '[path].gz[query]',
109 | algorithm: 'gzip',
110 | test: new RegExp(
111 | '\\.(' +
112 | config.build.productionGzipExtensions.join('|') +
113 | ')$'
114 | ),
115 | threshold: 10240,
116 | minRatio: 0.8
117 | })
118 | )
119 | }
120 |
121 | if (config.build.bundleAnalyzerReport) {
122 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
123 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
124 | }
125 |
126 | module.exports = webpackConfig
127 |
--------------------------------------------------------------------------------
/server/db/seeders/idea_seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | queryInterface.bulkInsert("ideas", [
6 | {
7 | profile_id: 3,
8 | title: "Empanadas 4 ever",
9 | idea_type: "public",
10 | description: "Empanadas will conquer the world",
11 | created_at: "2017-10-15 00:00:00",
12 | updated_at: "2017-10-15 00:00:00",
13 | tags: ["domination ","empanada ","eternal "]
14 | }, {
15 | profile_id: 2,
16 | title: "Empanadas",
17 | idea_type: "public",
18 | description: "Empanadas",
19 | created_at: "2017-10-15 00:00:00",
20 | updated_at: "2017-10-15 00:00:00",
21 | tags: ["empanada "]
22 | }, {
23 | profile_id: 3,
24 | title: "Dachshund Powered Empanada Factory",
25 | idea_type: "private",
26 | description: "In order to keep the cost of empanadas low so they can be enjoyed by everyone, regardless of their economic means its important to keep production costs low. To achieve this it is proposed to convert our empanada factory to operate on electricity generated via a dachsund-powered treadmill instead of electricity purchased from a public utility.",
27 | created_at: '2017-11-19 16:39:56.195',
28 | updated_at: '2017-11-19 16:39:56.195',
29 | tags: ["empanada ","factory ","dachshund "]
30 | }, {
31 | profile_id: 1,
32 | title: "Interstellar Empanadas",
33 | idea_type: "private",
34 | description: "It is time to share the beauty of the empanada with other extraterrestrial cultures by launching empanadas randomly into space.",
35 | created_at: "2017-11-29 19:00:02.291",
36 | updated_at: "2017-11-29 19:00:02.291",
37 | tags: ["empanada ", "interstellar ", "extraterrestrial"]
38 | }, {
39 | profile_id: 1,
40 | title: "Fortune Empanadas",
41 | idea_type: "private",
42 | description: "They say that imitation is the height of flattery. Chinese Fortune Cookies are such a good idea (and tasty too I might add) so why not Fortune Empanadas",
43 | created_at: "2017-12-06 01:17:02.978",
44 | updated_at: "2017-12-06 01:17:02.978",
45 | tags: ["empanada ","fortune "]
46 | }, {
47 | profile_id: 2,
48 | title: "Evolution Strategies",
49 | idea_type: "public",
50 | description: "It would be awesome if bots can learn to dance",
51 | created_at: "2017-11-18 10:46:42.484",
52 | updated_at: "2017-11-18 10:46:42.484",
53 | tags: ["lol ", "haha ", "funny ", "ha "]
54 | }, {
55 | profile_id: 1,
56 | title: "Turducken Empanada",
57 | idea_type: "public",
58 | description: "What could be better than an empanada stuffed with a duck, stuffed inside another empanada that is stuffed with a turkey?",
59 | created_at: "2017-11-22 15:06:57.504",
60 | updated_at: "2017-11-22 15:06:57.504",
61 | tags: ["turducken ","empanada "]
62 | }, {
63 | profile_id: 1,
64 | title: "Pumpkin Ale Empanadas",
65 | idea_type: "private",
66 | description: "Produce pumpkin ale flavored empanadas in two varieties - 1) a non-alcoholic version and 2) a version with between 5% and 6% ABV. Dietary information will be imprinted on the golden brown covering of each and every empanada.",
67 | created_at: "2017-11-03 00:00:00",
68 | updated_at: "2017-11-03 00:00:00",
69 | tags: ["pumpkin ","empanada "]
70 | }, {
71 | profile_id: 1,
72 | title: "Cold Fusion Powered Empanadas",
73 | idea_type: "private",
74 | description: "In order to boost the already awesome power of the empanada an even more powerful energy source is required. Hence, the joining of the empanada with the cold fusion reactor powered in turn by the home version of the Large Hadron Collider.",
75 | created_at: "2017-11-15 15:49:29.296",
76 | updated_at: "2017-11-15 15:49:29.296",
77 | tags: ["empanada ", "cold fusion ", "high power "]
78 | }, {
79 | profile_id: 2,
80 | title: "Gas Operated Empanadas",
81 | idea_type: "private",
82 | description: "Produce gas operated empanadas so the empanadas come to you rather than you having to go to the empanadas.",
83 | created_at: "2017-11-03 00:00:00",
84 | updated_at: "2017-11-03 00:00:00",
85 | tags: ["gas ","empanada "]
86 | }
87 | ], {});
88 | },
89 |
90 | down: (queryInterface, Sequelize) => {
91 | return queryInterface.bulkDelete('ideas', null, {});
92 | }
93 | };
94 |
--------------------------------------------------------------------------------
/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
47 |
48 |
188 |
--------------------------------------------------------------------------------
/client/src/assets/feedback.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/components/users/UserProfile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
21 |
22 |
23 | {{userName}}
24 |
25 | {{name}}
26 |
27 |
28 |
29 | {{userQualifications}}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
135 |
136 |
256 |
--------------------------------------------------------------------------------
/client/src/components/shared/IdeaTags.vue:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
72 |
73 |
320 |
--------------------------------------------------------------------------------
/client/src/components/shared/IdeaLinks.vue:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 |
78 |
79 |
331 |
--------------------------------------------------------------------------------
/client/src/components/ideas/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 | | Idea |
10 | Type |
11 | Status |
12 | Reviews |
13 |
14 |
15 | |
16 |
17 | {{ idea.idea.title }}
18 |
19 | |
20 | {{ idea.idea.idea_type }} |
21 | {{ idea.idea.status }} |
22 | {{ idea.idea.reviews.length }} |
23 |
24 |
25 |
33 |
34 |
35 |
36 |
37 |
74 |
75 |
329 |
--------------------------------------------------------------------------------
/client/src/components/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
22 |
23 |
24 |
{flip(e)}"
26 | @mouseleave="(e)=>{unflip(e)}">
27 |
28 |
Get
Feedback
29 |

30 |
31 |
32 |
Get Feedback
33 |

34 |
Refine your idea with the help of a supportive community
35 |
36 |
37 |
{flip(e)}"
39 | @mouseleave="(e)=>{unflip(e)}">
40 |
41 |
Share
Securely
42 |

43 |
44 |
45 |
Share Securely
46 |

47 |
Preserve your intellectual property rights when sharing
48 |
49 |
50 |
{flip(e)}"
52 | @mouseleave="(e)=>{unflip(e)}">
53 |
54 |
Collaborate
& Launch
55 |

56 |
57 |
58 |
Collaborate & Launch
59 |

60 |
Release your idea for development or recruit your dream team
61 |
62 |
63 |
64 |
73 |
74 |
75 |
76 |
77 |
140,257
78 |
Ideas born so far
79 |
80 | What's your idea?
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
107 |
108 |
345 |
--------------------------------------------------------------------------------
/client/src/components/shared/IdeaType.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
73 |
74 |
426 |
--------------------------------------------------------------------------------
/client/src/components/ideas/CreateIdea.vue:
--------------------------------------------------------------------------------
1 |
2 |
35 |
36 |
37 |
129 |
130 |
490 |
--------------------------------------------------------------------------------