├── .bowerrc ├── client ├── app │ ├── app.js │ ├── constants │ │ ├── RoomConstants.js │ │ ├── PageConstants.js │ │ ├── IdeaConstants.js │ │ ├── InterestConstants.js │ │ └── CommentConstants.js │ ├── actions │ │ ├── RoomActions.js │ │ ├── PageActions.js │ │ ├── InterestActions.js │ │ ├── IdeaActions.js │ │ └── CommentActions.js │ ├── dispatcher │ │ └── AppDispatcher.js │ ├── starter.js │ └── stores │ │ ├── UserStore.js │ │ ├── RoomStore.js │ │ ├── PageStore.js │ │ ├── InterestStore.js │ │ ├── IdeaStore.js │ │ └── CommentStore.js ├── react │ ├── User.js │ ├── CreateRoom.js │ ├── CreateIdea.js │ ├── CreateComment.js │ ├── RoomNavModal.js │ ├── Room.js │ ├── PageNav.js │ ├── Rooms.js │ ├── RoomCreateForm.js │ ├── Ideas.js │ ├── PageView.js │ ├── UserAuth.js │ ├── RoomTitle.js │ ├── CommentForm.js │ ├── IdeaForm.js │ ├── Comment.js │ ├── Interest.js │ ├── Comments.js │ ├── BrainstormApp.js │ └── Idea.js ├── styles │ └── app.css └── index.html ├── .gitignore ├── index.js ├── productionIndex.js ├── specs ├── client │ ├── AppDispatcherSpec.js │ ├── IdeaSpec.js │ ├── IdeaFormSpec.js │ ├── PageNavSpec.js │ ├── IdeasSpec.js │ ├── PageViewSpec.js │ ├── CommentFormSpec.js │ ├── RoomNavModalSpec.js │ ├── PageStoreSpec.js │ ├── CommentsSpec.js │ └── CommentStoreSpec.js └── server │ └── ServerSpec.js ├── server ├── users │ ├── userRoutes.js │ ├── userController.js │ ├── user.server.model.js │ └── userAuthController.js ├── rooms │ ├── roomRoutes.js │ ├── room.server.model.js │ └── roomController.js ├── interests │ ├── interestRoutes.js │ ├── interest.server.model.js │ └── interestController.js ├── ideas │ ├── idea.server.model.js │ ├── ideaRoutes.js │ └── ideaController.js ├── comments │ ├── commentRoutes.js │ ├── comment.server.model.js │ └── commentController.js ├── db.js ├── productionServer.js ├── server.js └── config │ ├── middleware.js │ └── productionMiddleware.js ├── bower.json ├── PRESS-RELEASE.md ├── README.md ├── LICENSE ├── package.json ├── karma.conf.js ├── Gulpfile.js ├── CONTRIBUTING.md └── STYLE-GUIDE.md /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/lib" 3 | } 4 | -------------------------------------------------------------------------------- /client/app/app.js: -------------------------------------------------------------------------------- 1 | var app = {}; 2 | var CHANGE_EVENT = 'change'; 3 | -------------------------------------------------------------------------------- /client/app/constants/RoomConstants.js: -------------------------------------------------------------------------------- 1 | app.RoomConstants = { 2 | ROOM_CREATE: 'ROOM_CREATE' 3 | }; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | client/lib/ 3 | client/app/react/ 4 | production/ 5 | npm-debug.log 6 | .DS_Store 7 | .floo 8 | .flooignore 9 | -------------------------------------------------------------------------------- /client/app/constants/PageConstants.js: -------------------------------------------------------------------------------- 1 | app.PageConstants = { 2 | NAVIGATE: 'PAGE_NAVIGATE', 3 | GETROOMDATA: 'PAGE_GETROOMDATA' 4 | }; 5 | -------------------------------------------------------------------------------- /client/app/constants/IdeaConstants.js: -------------------------------------------------------------------------------- 1 | app.IdeaConstants = { 2 | IDEA_CREATE: 'IDEA_CREATE', 3 | IDEA_EDIT: 'IDEA_EDIT', 4 | IDEA_DELETE: 'IDEA_DELETE' 5 | }; 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var app = require('./server/server.js'); 2 | 3 | var port = 8000; 4 | app.listen(port, function() { 5 | console.log('Server is listening on ' + port); 6 | }); -------------------------------------------------------------------------------- /client/app/constants/InterestConstants.js: -------------------------------------------------------------------------------- 1 | app.InterestConstants = { 2 | INTEREST_GET: 'INTEREST_GET', 3 | INTEREST_CREATE: 'INTEREST_CREATE', 4 | INTEREST_DELETE: 'INTEREST_DELETE' 5 | }; 6 | -------------------------------------------------------------------------------- /client/react/User.js: -------------------------------------------------------------------------------- 1 | app.User = React.createClass({ 2 | render: function(){ 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /productionIndex.js: -------------------------------------------------------------------------------- 1 | var app = require('./server/productionServer.js'); 2 | 3 | var port = 8000; 4 | app.listen(port, function() { 5 | console.log('Server is listening on ' + port); 6 | }); 7 | -------------------------------------------------------------------------------- /client/react/CreateRoom.js: -------------------------------------------------------------------------------- 1 | app.CreateRoom = React.createClass({ 2 | render: function(){ 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /client/app/constants/CommentConstants.js: -------------------------------------------------------------------------------- 1 | app.CommentConstants = { 2 | COMMENT_GET: 'COMMENT_GET', 3 | COMMENT_CREATE: 'COMMENT_CREATE', 4 | COMMENT_EDIT: 'COMMENT_EDIT', 5 | COMMENT_DELETE: 'COMMENT_DELETE' 6 | }; 7 | -------------------------------------------------------------------------------- /client/app/actions/RoomActions.js: -------------------------------------------------------------------------------- 1 | app.RoomActions = { 2 | create: function(name) { 3 | app.AppDispatcher.handleViewAction({ 4 | actionType: app.RoomConstants.ROOM_CREATE, 5 | name: name 6 | }); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /client/react/CreateIdea.js: -------------------------------------------------------------------------------- 1 | app.CreateIdea = React.createClass({ 2 | render: function(){ 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /specs/client/AppDispatcherSpec.js: -------------------------------------------------------------------------------- 1 | describe('AppDispatcher',function(){ 2 | 3 | it('should be an instance of the Dispatcher',function(done){ 4 | expect(app.AppDispatcher instanceof Flux.Dispatcher).to.equal(true); 5 | done(); 6 | }); 7 | 8 | }); 9 | -------------------------------------------------------------------------------- /client/react/CreateComment.js: -------------------------------------------------------------------------------- 1 | app.CreateComment = React.createClass({ 2 | render: function(){ 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /server/users/userRoutes.js: -------------------------------------------------------------------------------- 1 | var userController = require('./userController.js'); 2 | 3 | module.exports = function(app) { 4 | // app === userRouter injected from middlware.js 5 | app.route('/') 6 | .delete(userController.logout) 7 | .get(userController.user); 8 | }; -------------------------------------------------------------------------------- /server/rooms/roomRoutes.js: -------------------------------------------------------------------------------- 1 | var roomController = require('./roomController.js'); 2 | 3 | module.exports = function (app) { 4 | // app === userRouter injected from middlware.js 5 | app.route('/') 6 | .post(roomController.newRoom) 7 | .get(roomController.allRooms); 8 | }; -------------------------------------------------------------------------------- /server/rooms/room.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var RoomSchema = new Schema({ 7 | name: { 8 | type: String 9 | } 10 | }); 11 | 12 | module.exports = mongoose.model('Room', RoomSchema); -------------------------------------------------------------------------------- /client/app/dispatcher/AppDispatcher.js: -------------------------------------------------------------------------------- 1 | //require app 2 | //require Flux.Dispatcher 3 | 4 | app.AppDispatcher = _.extend(new Flux.Dispatcher(), { 5 | handleViewAction: function(action) { 6 | this.dispatch({ 7 | source: 'VIEW_ACTION', 8 | action: action 9 | }); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /server/users/userController.js: -------------------------------------------------------------------------------- 1 | var User = require('./user.server.model.js'); 2 | 3 | module.exports = { 4 | user: function (req, res, next) { 5 | var user = req.user || null; 6 | res.json(user); 7 | }, 8 | 9 | logout: function (req, res, next) { 10 | req.logout(); 11 | res.json(null); 12 | } 13 | }; -------------------------------------------------------------------------------- /client/react/RoomNavModal.js: -------------------------------------------------------------------------------- 1 | app.RoomNavModal = React.createClass({ 2 | 3 | render:function(){ 4 | return ( 5 |
6 |

{this.props.name}

7 | http://localhost/_/rooms/{this.props.roomId} 8 |
9 | ); 10 | } 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /server/interests/interestRoutes.js: -------------------------------------------------------------------------------- 1 | var interestController = require('./interestController.js'); 2 | 3 | module.exports = function (app) { 4 | app.route('/:idea_id') 5 | .post(interestController.newInterest) 6 | .get(interestController.allInterests); 7 | 8 | app.route('/:interest_id') 9 | .delete(interestController.deleteInterest); 10 | }; 11 | -------------------------------------------------------------------------------- /server/interests/interest.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var InterestSchema = new Schema({ 7 | idea: { 8 | type: Schema.ObjectId, 9 | ref: 'Idea' 10 | }, 11 | owner: { 12 | type: Schema.ObjectId, 13 | ref: 'User' 14 | } 15 | }); 16 | 17 | module.exports = mongoose.model('Interest', InterestSchema); 18 | -------------------------------------------------------------------------------- /client/app/actions/PageActions.js: -------------------------------------------------------------------------------- 1 | app.PageActions = { 2 | navigate: function(body) { 3 | app.AppDispatcher.handleViewAction({ 4 | actionType: app.PageConstants.NAVIGATE, 5 | body: body 6 | }); 7 | }, 8 | getRoomData: function(room_id) { 9 | app.AppDispatcher.handleViewAction({ 10 | actionType: app.PageConstants.GETROOMDATA, 11 | room_id: room_id 12 | }); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /specs/client/IdeaSpec.js: -------------------------------------------------------------------------------- 1 | describe('Ideas Component', function(){ 2 | 3 | var TestUtils, idea; 4 | 5 | beforeEach(function(){ 6 | TestUtils = React.addons.TestUtils; 7 | idea = React.createElement(app.Idea, null); 8 | 9 | idea = TestUtils.renderIntoDocument(idea); 10 | }); 11 | 12 | it('should render the body', function(){ 13 | expect(idea.refs.body).to.be.ok(); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /client/app/starter.js: -------------------------------------------------------------------------------- 1 | React.render( 2 | React.createElement(app.BrainstormApp, null), 3 | document.getElementById('main') 4 | ); 5 | 6 | if (document.location.hash.substr(3, 5) === 'rooms'){ 7 | app.PageActions.navigate({ 8 | 9 | dest: 'rooms', 10 | props: document.location.hash.substr(9) 11 | 12 | }); 13 | } else { 14 | app.PageActions.navigate({ 15 | 16 | dest: 'welcome' 17 | 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /client/react/Room.js: -------------------------------------------------------------------------------- 1 | app.Room = React.createClass({ 2 | 3 | gotoRoom: function(e){ 4 | e.preventDefault(); 5 | app.PageActions.navigate({ 6 | dest: 'rooms', 7 | props: this.props._id 8 | }); 9 | }, 10 | 11 | render: function() { 12 | return ( 13 |
14 | {this.props.name} 15 |
16 | ); 17 | } 18 | }); -------------------------------------------------------------------------------- /server/ideas/idea.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var IdeaSchema = new Schema({ 7 | name: { 8 | type: String 9 | }, 10 | room: { 11 | type: Schema.ObjectId, 12 | ref: 'Room' 13 | }, 14 | owner: { 15 | type: Schema.ObjectId, 16 | ref: 'User' 17 | } 18 | }); 19 | 20 | module.exports = mongoose.model('Idea', IdeaSchema); 21 | -------------------------------------------------------------------------------- /server/users/user.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var UserSchema = new Schema({ 7 | username: { 8 | type: String 9 | }, 10 | socialData: { 11 | type: Object 12 | }, 13 | created: { 14 | type: Date, 15 | default: Date.now 16 | }, 17 | updated: { 18 | type: Date 19 | } 20 | }); 21 | 22 | mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /server/comments/commentRoutes.js: -------------------------------------------------------------------------------- 1 | var commentController = require('./commentController.js'); 2 | 3 | module.exports = function (app) { 4 | // app === userRouter injected from middlware.js 5 | app.route('/:idea_id') 6 | .post(commentController.newComment) 7 | .get(commentController.allComments); 8 | 9 | app.route('/:comment_id') 10 | .put(commentController.editComment) 11 | .delete(commentController.deleteComment); 12 | }; 13 | -------------------------------------------------------------------------------- /server/comments/comment.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var CommentSchema = new Schema({ 7 | name: { 8 | type: String 9 | }, 10 | idea: { 11 | type: Schema.ObjectId, 12 | ref: 'Idea' 13 | }, 14 | owner: { 15 | type: Schema.ObjectId, 16 | ref: 'User' 17 | } 18 | }); 19 | 20 | module.exports = mongoose.model('Comment', CommentSchema); 21 | -------------------------------------------------------------------------------- /server/ideas/ideaRoutes.js: -------------------------------------------------------------------------------- 1 | var ideaController = require('./ideaController.js'); 2 | 3 | module.exports = function (app) { 4 | // app === ideaRouter injected from middlware.js 5 | // the app has the '/ideas' path mounted for the ideaRouter 6 | // thus the root route here is actually '/ideas' 7 | app.route('/:room_id') 8 | .post(ideaController.newIdea) 9 | .get(ideaController.allIdeas); 10 | 11 | app.route('/:idea_id') 12 | .put(ideaController.updateIdea) 13 | .delete(ideaController.deleteIdea); 14 | }; 15 | -------------------------------------------------------------------------------- /client/app/actions/InterestActions.js: -------------------------------------------------------------------------------- 1 | app.InterestActions = { 2 | get: function () { 3 | app.AppDispatcher.handleViewAction({ 4 | actionType: app.InterestConstants.INTEREST_GET 5 | }); 6 | }, 7 | create: function (idea_id) { 8 | app.AppDispatcher.handleViewAction({ 9 | actionType: app.InterestConstants.INTEREST_CREATE, 10 | idea_id: idea_id 11 | }); 12 | }, 13 | delete: function (_id) { 14 | app.AppDispatcher.handleViewAction({ 15 | actionType: app.InterestConstants.INTEREST_DELETE, 16 | _id: _id 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /client/app/actions/IdeaActions.js: -------------------------------------------------------------------------------- 1 | app.IdeaActions = { 2 | create: function(room_id, name) { 3 | app.AppDispatcher.handleViewAction({ 4 | actionType: app.IdeaConstants.IDEA_CREATE, 5 | room_id: room_id, 6 | name: name 7 | }); 8 | }, 9 | 10 | edit: function(idea) { 11 | app.AppDispatcher.handleViewAction({ 12 | actionType: app.IdeaConstants.IDEA_EDIT, 13 | idea: idea 14 | }); 15 | }, 16 | 17 | delete: function(idea) { 18 | app.AppDispatcher.handleViewAction({ 19 | actionType: app.IdeaConstants.IDEA_DELETE, 20 | idea: idea 21 | }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /client/react/PageNav.js: -------------------------------------------------------------------------------- 1 | app.PageNav = React.createClass({ 2 | 3 | handleWelcome:function(){ 4 | //dispatch a navigate to welcome on click 5 | app.PageActions.navigate({ 6 | dest: 'welcome' 7 | }); 8 | }, 9 | 10 | render:function(){ 11 | return ( 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | ); 21 | } 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /server/db.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var db = {}; 3 | 4 | mongoose.connect('mongodb://localhost/brainstormer'); 5 | var userModel = require('./users/user.server.model.js'); 6 | var commentModel = require('./comments/comment.server.model.js'); 7 | var ideaModel = require('./ideas/idea.server.model.js'); 8 | var roomModel = require('./rooms/room.server.model.js'); 9 | var interestModel = require('./interests/interest.server.model.js'); 10 | db.User = mongoose.model('User'); 11 | db.Comment = mongoose.model('Comment'); 12 | db.Idea = mongoose.model('Idea'); 13 | db.Room = mongoose.model('Room'); 14 | db.Interest = mongoose.model('Interest'); 15 | 16 | module.exports = db; 17 | -------------------------------------------------------------------------------- /client/react/Rooms.js: -------------------------------------------------------------------------------- 1 | app.Rooms = React.createClass({ 2 | getInitialState: function() { 3 | return { 4 | rooms: app.RoomStore.getAll() 5 | }; 6 | }, 7 | 8 | componentDidMount: function() { 9 | app.RoomStore.addChangeListener(function() { 10 | if(this.isMounted()) { 11 | this.setState({ rooms: app.RoomStore.getAll() }); 12 | } 13 | }.bind(this)); 14 | app.RoomStore.all(); 15 | }, 16 | 17 | render: function() { 18 | var rooms = []; 19 | this.state.rooms.forEach(function(room) { 20 | rooms.push(); 21 | }); 22 | return ( 23 |
24 | { rooms } 25 |
26 | ); 27 | } 28 | }) -------------------------------------------------------------------------------- /client/react/RoomCreateForm.js: -------------------------------------------------------------------------------- 1 | app.RoomCreateForm = React.createClass({ 2 | handleSubmit: function(e) { 3 | e.preventDefault(); 4 | 5 | var roomName = this.refs.name.getDOMNode(); 6 | 7 | app.RoomActions.create(roomName.value.trim()); 8 | roomName.value = ''; 9 | return; 10 | }, 11 | 12 | render: function(){ 13 | return ( 14 |
15 | 16 | 17 |
18 | ); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Brainstorm", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/HRR2-Brainstorm/Brainstorm", 5 | "authors": [ 6 | "Brett", 7 | "Dmitri", 8 | "Gunnari", 9 | "Jason" 10 | ], 11 | "description": "Brainstorming app", 12 | "keywords": [ 13 | "Brainstorm" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "flux": "~2.0.2", 25 | "react": "~0.12.1", 26 | "page": "visionmedia/page.js#~1.5.0", 27 | "eventEmitter": "~4.2.10", 28 | "underscore": "~1.7.0", 29 | "jquery": "~2.1.1", 30 | "pure": "~0.5.0", 31 | "socket.io-client": "~1.2.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/react/Ideas.js: -------------------------------------------------------------------------------- 1 | app.Ideas = React.createClass({ 2 | getInitialState: function () { 3 | return { 4 | ideas: app.IdeaStore.getAll() 5 | }; 6 | }, 7 | 8 | componentDidMount: function () { 9 | app.IdeaStore.addChangeListener(function() { 10 | if(this.isMounted()) { 11 | this.setState({ ideas: app.IdeaStore.getAll() }); 12 | } 13 | }.bind(this)); 14 | // get all ideas from db 15 | }, 16 | 17 | render: function() { 18 | var ideas = []; 19 | // create all idea components 20 | this.state.ideas.forEach(function(idea) { 21 | ideas.push(); 22 | }); 23 | return ( 24 |
25 | { ideas } 26 |
27 | ); 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /specs/client/IdeaFormSpec.js: -------------------------------------------------------------------------------- 1 | describe('Idea Form', function(){ 2 | 3 | var TestUtils, ideaForm, ideaMockAction; 4 | 5 | beforeEach(function(){ 6 | TestUtils = React.addons.TestUtils; 7 | ideaForm = React.createElement(app.IdeaForm, null); 8 | app.IdeaActions = { 9 | create:function(text){ 10 | ideaMockAction = text; 11 | } 12 | }; 13 | ideaForm = TestUtils.renderIntoDocument(ideaForm); 14 | }); 15 | 16 | it('should render the body', function(){ 17 | expect(ideaForm.refs.name).to.be.ok(); 18 | }); 19 | 20 | it('should handle a click', function(){ 21 | ideaForm.refs.name.getDOMNode().value = 'test'; 22 | TestUtils.Simulate.submit(ideaForm.refs.form.getDOMNode()); 23 | expect(ideaMockAction).to.equal('test'); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /client/app/actions/CommentActions.js: -------------------------------------------------------------------------------- 1 | app.CommentActions = { 2 | get: function () { 3 | app.AppDispatcher.handleViewAction({ 4 | actionType: app.CommentConstants.COMMENT_GET 5 | }); 6 | }, 7 | create: function (idea_id, name) { 8 | app.AppDispatcher.handleViewAction({ 9 | actionType: app.CommentConstants.COMMENT_CREATE, 10 | idea_id: idea_id, 11 | name: name 12 | }); 13 | }, 14 | edit: function (_id, name) { 15 | app.AppDispatcher.handleViewAction({ 16 | actionType: app.CommentConstants.COMMENT_EDIT, 17 | _id: _id, 18 | name: name 19 | }); 20 | }, 21 | delete: function (_id) { 22 | app.AppDispatcher.handleViewAction({ 23 | actionType: app.CommentConstants.COMMENT_DELETE, 24 | _id: _id 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /client/react/PageView.js: -------------------------------------------------------------------------------- 1 | app.PageView = React.createClass({ 2 | 3 | getInitialState: function(){ 4 | return app.PageStore.currentRoute || {dest: 'welcome'}; 5 | }, 6 | 7 | componentDidMount: function(){ 8 | //add a change listener for the page store on routing 9 | app.PageStore.addChangeListener(function(){ 10 | 11 | //get state from the PageStore.currentRoute 12 | var state = app.PageStore.currentRoute; 13 | 14 | //if props is undefined set it to empty string 15 | state.props = state.props || ''; 16 | this.setState(state); 17 | }.bind(this)); 18 | }, 19 | 20 | render: function(){ 21 | return ( 22 |
23 | You are in {this.state.dest + (this.state.props || '')} 24 |
25 | ); 26 | } 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /server/rooms/roomController.js: -------------------------------------------------------------------------------- 1 | var Room = require('./room.server.model.js'); 2 | var Q = require('q'); 3 | 4 | module.exports = { 5 | newRoom: function (req, res, next) { 6 | var room = {}; 7 | 8 | room.name = req.body.name; 9 | 10 | var createRoom = Q.nbind(Room.create, Room); 11 | 12 | createRoom(room) 13 | .then(function (createdRoom) { 14 | if (createdRoom) { 15 | res.json(createdRoom); 16 | } 17 | }) 18 | .fail(function (error) { 19 | next(error); 20 | }); 21 | }, 22 | 23 | allRooms: function(req, res, next) { 24 | var getRooms = Q.nbind(Room.find, Room); 25 | getRooms({}) 26 | .then(function(allRooms) { 27 | if(allRooms) { 28 | res.json(allRooms); 29 | } 30 | }) 31 | .fail(function(error) { 32 | next(error); 33 | }); 34 | } 35 | }; -------------------------------------------------------------------------------- /specs/client/PageNavSpec.js: -------------------------------------------------------------------------------- 1 | describe('Page Nav', function(){ 2 | 3 | var TestUtils, pageNav, pageMockAction, holder; 4 | 5 | beforeEach(function(){ 6 | TestUtils = React.addons.TestUtils; 7 | pageNav = React.createElement(app.PageNav, null); 8 | holder = app.PageActions; 9 | app.PageActions = { 10 | navigate: function(props){ 11 | pageMockAction = props; 12 | } 13 | }; 14 | pageNav = TestUtils.renderIntoDocument(pageNav); 15 | }); 16 | 17 | afterEach(function(){ 18 | app.PageActions = holder; 19 | }); 20 | 21 | it('should render the body', function(){ 22 | expect(pageNav.refs.body).to.be.ok(); 23 | }); 24 | 25 | it('should handle a click', function(){ 26 | TestUtils.Simulate.click(pageNav.refs.welcome.getDOMNode()); 27 | expect(pageMockAction.dest).to.equal('welcome'); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /client/react/UserAuth.js: -------------------------------------------------------------------------------- 1 | app.UserAuth = React.createClass({ 2 | getInitialState: function() { 3 | return { currentUser: app.UserStore.get() }; 4 | }, 5 | 6 | handleClick: function(e) { 7 | if(this.state.currentUser) { 8 | e.preventDefault(); 9 | app.UserStore.logout(); 10 | } 11 | }, 12 | 13 | render: function(){ 14 | var text = this.state.currentUser ? 'Logout' : 'Login'; 15 | return ( 16 |
17 | {text} 18 |
19 | ); 20 | }, 21 | 22 | componentDidMount: function() { 23 | app.UserStore.addChangeListener(function() { 24 | if(this.isMounted()) { 25 | this.setState({ currentUser: app.UserStore.get() }); 26 | } 27 | }.bind(this)); 28 | app.UserStore.getCurrentUser(); 29 | } 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /server/productionServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var mongoose = require('./db.js'); 5 | var app = express(); 6 | var server = require('http').Server(app); 7 | var io = require('socket.io')(server); 8 | 9 | io.on('connection', function(client) { 10 | client.on('comment-change', function(currentComments) { 11 | client.broadcast.emit('comment-change', currentComments); 12 | }); 13 | 14 | client.on('idea-change', function(currentIdeas) { 15 | client.broadcast.emit('idea-change', currentIdeas); 16 | }); 17 | 18 | client.on('interest-change', function(currentInterests) { 19 | client.broadcast.emit('interest-change', currentInterests); 20 | }); 21 | 22 | client.on('room-change', function(currentRooms) { 23 | client.broadcast.emit('room-change', currentRooms); 24 | }); 25 | }); 26 | 27 | require('./config/productionMiddleware')(app, express); 28 | 29 | module.exports = server; 30 | -------------------------------------------------------------------------------- /specs/client/IdeasSpec.js: -------------------------------------------------------------------------------- 1 | describe('Ideas Component', function(){ 2 | 3 | var TestUtils, ideas, holder, CHANGE_EVENT; 4 | 5 | beforeEach(function(){ 6 | CHANGE_EVENT = 'change'; 7 | TestUtils = React.addons.TestUtils; 8 | ideas = React.createElement(app.Ideas, null); 9 | holder = app.IdeaStore; 10 | app.IdeaStore = _.extend({}, EventEmitter.prototype, { 11 | _ideas: [], 12 | create: function(text) { 13 | this._ideas.push(text); 14 | }, 15 | getAll: function() { 16 | return this._ideas; 17 | }, 18 | all: function() { 19 | return this._ideas; 20 | }, 21 | addChangeListener: function(callback) { 22 | this.on(CHANGE_EVENT, callback); 23 | } 24 | }); 25 | 26 | ideas = TestUtils.renderIntoDocument(ideas); 27 | }); 28 | 29 | it('should render the body', function(){ 30 | expect(ideas.refs.body).to.be.ok(); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /client/react/RoomTitle.js: -------------------------------------------------------------------------------- 1 | app.RoomTitle = React.createClass({ 2 | getInitialState: function() { 3 | return { 4 | room: 5 | _(app.RoomStore.getAll()).filter(function (room) { 6 | return room._id === this.props.room_id; 7 | },this)[0] 8 | }; 9 | }, 10 | 11 | componentDidMount: function() { 12 | app.RoomStore.addChangeListener(function() { 13 | if(this.isMounted()) { 14 | this.setState({ room: 15 | _(app.RoomStore.getAll()).filter(function (room) { 16 | return room._id === this.props.room_id; 17 | },this)[0] 18 | }); 19 | } 20 | }.bind(this)); 21 | app.RoomStore.all(); 22 | }, 23 | 24 | render: function() { 25 | var title; 26 | if (this.state.room){ 27 | title = (

{ this.state.room.name }

) 28 | } 29 | 30 | return ( 31 |
32 | {title} 33 |
34 | ); 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /specs/client/PageViewSpec.js: -------------------------------------------------------------------------------- 1 | describe('PageView', function () { 2 | 3 | var TestUtils, pageView; 4 | 5 | beforeEach(function () { 6 | TestUtils = React.addons.TestUtils; 7 | pageView = React.createElement(app.PageView, null); 8 | pageView = TestUtils.renderIntoDocument(pageView); 9 | }); 10 | 11 | it('should render the body', function () { 12 | expect(pageView.refs.body).to.be.ok(); 13 | }); 14 | 15 | it('should update its state when the PageStore changes', function () { 16 | 17 | app.PageActions.navigate({ 18 | dest: 'welcome' 19 | }); 20 | 21 | expect(pageView.refs.body.getDOMNode().textContent).to.contain('welcome'); 22 | 23 | app.PageActions.navigate({ 24 | dest: 'rooms', 25 | props: '0' 26 | }); 27 | 28 | expect(pageView.refs.body.getDOMNode().textContent).to.contain('room'); 29 | expect(pageView.refs.body.getDOMNode().textContent).to.contain('0'); 30 | 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /client/app/stores/UserStore.js: -------------------------------------------------------------------------------- 1 | app.UserStore = _.extend({}, EventEmitter.prototype, { 2 | _user: null, 3 | 4 | get: function() { 5 | return this._user; 6 | }, 7 | 8 | getCurrentUser: function() { 9 | $.ajax({ 10 | url: '/users', 11 | type: 'GET' 12 | }) 13 | .done(function(user) { 14 | this._user = user; 15 | this.emitChange(); 16 | }.bind(this)) 17 | .fail(function(err) { 18 | console.log(err); 19 | }); 20 | }, 21 | 22 | logout: function() { 23 | $.ajax({ 24 | url: '/users', 25 | type: 'DELETE' 26 | }) 27 | .done(function(user) { 28 | this._user = user; 29 | this.emitChange(); 30 | }.bind(this)) 31 | .fail(function(err) { 32 | console.log(err); 33 | }); 34 | }, 35 | 36 | emitChange: function() { 37 | this.emit(CHANGE_EVENT); 38 | }, 39 | 40 | addChangeListener:function(callback){ 41 | this.on(CHANGE_EVENT, callback); 42 | }, 43 | }); -------------------------------------------------------------------------------- /PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | # HRR2-Brainstorm # 2 | 3 | ## Brainstormer ## 4 | 5 | ## Brainstorming app for Hack Reactor project forming ## 6 | 7 | ## Summary ## 8 | Share ideas and get feedback quickly and easily. Find ideas Create groups with interested hackers. Stop PMing people and get the @!#* to work! 9 | 10 | ## Problem ## 11 | With existing tools, it is hard to discuss ideas in an organized fashion, it's almost impossible to provide substantive feedback on those ideas, and it's difficult to see who is working with whom. 12 | 13 | ## Solution ## 14 | A dedicated application focused on quickly presenting new ideas to users so that they can give and get feedback and pick and choose the ideas they are most interested in. 15 | 16 | ### Brainstormer is the first app to tackle real ad-hoc idea sharing ### 17 | 18 | 19 | ## How to Get Started ## 20 | Getting started with Brainstomer is as easy as heading over to URL, signing in with Github, and creating a room. 21 | 22 | ## Visit our app today! ## 23 | -------------------------------------------------------------------------------- /specs/client/CommentFormSpec.js: -------------------------------------------------------------------------------- 1 | describe('Comment Form', function () { 2 | 3 | var TestUtils, commentForm, commentMockAction, commentActionHolder; 4 | 5 | beforeEach(function () { 6 | TestUtils = React.addons.TestUtils; 7 | commentForm = React.createElement(app.CommentForm, null); 8 | commentActionHolder = app.CommentActions; 9 | app.CommentActions = { 10 | create:function (idea_id, text) { 11 | commentMockAction = text; 12 | } 13 | }; 14 | commentForm = TestUtils.renderIntoDocument(commentForm); 15 | }); 16 | 17 | afterEach(function () { 18 | app.CommentActions = commentActionHolder; 19 | }); 20 | 21 | it('should render the body', function () { 22 | expect(commentForm.refs.body).to.be.ok(); 23 | }); 24 | 25 | it('should handle a click', function () { 26 | commentForm.refs.input.getDOMNode().value = 'test'; 27 | TestUtils.Simulate.submit(commentForm.refs.body.getDOMNode()); 28 | expect(commentMockAction).to.equal('test'); 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var mongoose = require('./db.js'); 5 | var app = express(); 6 | var server = require('http').Server(app); 7 | var io = require('socket.io')(server); 8 | 9 | io.on('connection', function(client) { 10 | client.on('join', function(room) { 11 | client.join(room); 12 | }); 13 | 14 | client.on('comment-change', function(currentComments, room) { 15 | client.broadcast.in(room).emit('comment-change', currentComments); 16 | }); 17 | 18 | client.on('idea-change', function(currentIdeas, room) { 19 | client.broadcast.in(room).emit('idea-change', currentIdeas); 20 | }); 21 | 22 | client.on('interest-change', function(currentInterests, room) { 23 | client.broadcast.in(room).emit('interest-change', currentInterests); 24 | }); 25 | 26 | client.on('room-change', function(currentRooms) { 27 | client.broadcast.emit('room-change', currentRooms); 28 | }); 29 | }); 30 | 31 | require('./config/middleware')(app, express); 32 | 33 | module.exports = server; 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stories in Ready](https://badge.waffle.io/HRR2-Brainstorm/Brainstorm.png?label=ready&title=Ready)](https://waffle.io/HRR2-Brainstorm/Brainstorm) 2 | Brainstorm 3 | ========== 4 | 5 | HR-Brainstorming app 6 | 7 | This app is built to make the process of brainstorming and idea sharing within Hack Reactor easier 8 | 9 | It leverages React, Flux, and Socket.io to create a truly seamless user and responsive user experience 10 | 11 | ##Interested in Contributing? 12 | 13 | Please review [CONTRIBUTING.md](CONTRIBUTING.md) 14 | 15 | ###App Gulp Tasks 16 | 17 | serve index.js with nodemon (page will reload on server and client files changes) 18 | 19 | `gulp` 20 | 21 | run *client side* tests automatically whenever files change 22 | 23 | `gulp karma-auto` 24 | 25 | run server side tests one time 26 | 27 | `npm test` 28 | 29 | set up automatic jsx compiling on save: 30 | 31 | first install react tools if you have not already (may need to run as sudo) 32 | 33 | `npm install -g react-tools` 34 | 35 | then run from the root of the application 36 | 37 | `gulp jsx-auto` 38 | -------------------------------------------------------------------------------- /client/react/CommentForm.js: -------------------------------------------------------------------------------- 1 | app.CommentForm = React.createClass({ 2 | 3 | handleSubmit: function (e) { 4 | e.preventDefault(); 5 | 6 | var commentBody = this.refs.input.getDOMNode(); 7 | var comment = commentBody.value.trim(); 8 | 9 | //if editing dispatch to editing 10 | if (this.props.editing) { 11 | app.CommentActions.edit(this.props._id, comment); 12 | } else { //otherwise dispatch to create 13 | app.CommentActions.create(this.props.idea_id, comment); 14 | } 15 | 16 | //dispatch an event with the comment text 17 | commentBody.value = ''; 18 | return; 19 | }, 20 | 21 | render: function () { 22 | return ( 23 |
24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /specs/client/RoomNavModalSpec.js: -------------------------------------------------------------------------------- 1 | describe('RoomNavModal', function(){ 2 | 3 | var TestUtils, roomNavModal, roomMockAction, holder; 4 | 5 | beforeEach(function(){ 6 | TestUtils = React.addons.TestUtils; 7 | holder = app.PageActions; 8 | app.PageActions = { 9 | navigate: function(body){ 10 | roomMockAction = body; 11 | } 12 | }; 13 | roomNavModal = React.createElement(app.RoomNavModal, { 14 | roomId: '0', 15 | handleClick: function () { 16 | app.PageActions.navigate({ 17 | dest: 'rooms', 18 | props: '0' 19 | }); 20 | } 21 | }); 22 | roomNavModal = TestUtils.renderIntoDocument(roomNavModal); 23 | }); 24 | 25 | afterEach(function(){ 26 | app.PageActions = holder; 27 | }); 28 | 29 | it('should render the body', function(){ 30 | expect(roomNavModal.refs.body).to.be.ok(); 31 | }); 32 | 33 | it('should handle a click', function(){ 34 | TestUtils.Simulate.click(roomNavModal.refs.room.getDOMNode()); 35 | expect(roomMockAction.dest).to.equal('rooms'); 36 | expect(roomMockAction.props).to.equal('0'); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 HRR2-Brainstorm 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 | 23 | -------------------------------------------------------------------------------- /client/react/IdeaForm.js: -------------------------------------------------------------------------------- 1 | app.IdeaForm = React.createClass({ 2 | handleSubmit: function(e) { 3 | e.preventDefault(); 4 | // get the value out of the input with ref="name" 5 | var name = this.refs.name.getDOMNode(); 6 | 7 | // if editing send info to edit method in IdeaActions 8 | if (this.props.editing) { 9 | var idea = {id: this.props._id}; 10 | idea.name = name.value.trim(); 11 | app.IdeaActions.edit(idea); 12 | } else { // else an idea is being created 13 | app.IdeaActions.create(this.props.room_id, name.value.trim()); 14 | } 15 | // clear the value in the input 16 | name.value = ''; 17 | return; 18 | }, 19 | 20 | render: function(){ 21 | // if editing the defaultValue will be the idea name 22 | // if editing an "Edit" button will show otherwise a "Create" 23 | return ( 24 |
25 | 26 | 27 |
28 | ); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /specs/server/ServerSpec.js: -------------------------------------------------------------------------------- 1 | /* global require, describe, it */ 2 | 'use strict'; 3 | 4 | var expect = require('chai').expect; 5 | var request = require('request'); 6 | 7 | var url = function(path) { 8 | return 'http://localhost:8000' + path; 9 | }; 10 | 11 | describe("MongoDB", function() { 12 | it("is there a server running", function(next) { 13 | var MongoClient = require('mongodb').MongoClient; 14 | MongoClient.connect('mongodb://127.0.0.1:27017/brainstormer', function(err, db) { 15 | expect(err).to.equal(null); 16 | next(); 17 | }); 18 | }); 19 | }); 20 | 21 | describe('GET /', function() { 22 | it('responds', function(done){ 23 | request(url('/'), function(error, res) { 24 | expect(res.statusCode).to.equal(200); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | 30 | describe('GET /index.html', function() { 31 | it('responds', function(done){ 32 | request(url('/index.html'), function(error, res) { 33 | expect(res.headers['content-type'].indexOf('html')).to.not.equal(-1); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('GET /no-such-file.html', function() { 40 | it('responds', function(done){ 41 | request(url('/no-such-file.html'), function(error, res) { 42 | expect(res.statusCode).to.equal(404); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /server/config/middleware.js: -------------------------------------------------------------------------------- 1 | var morgan = require('morgan'); 2 | var bodyParser = require('body-parser'); 3 | 4 | module.exports = function(app, express) { 5 | var commentRouter = express.Router(); 6 | var ideaRouter = express.Router(); 7 | var roomRouter = express.Router(); 8 | var userRouter = express.Router(); 9 | var interestRouter = express.Router(); 10 | 11 | app.use(morgan('dev')); 12 | // Returns middleware that only parses urlencoded bodies 13 | // and parses extended syntax with qs module 14 | app.use(bodyParser.urlencoded({extended: true})); 15 | // bodyParser.json() returns middleware that only parses json 16 | app.use(bodyParser.json()); 17 | // use express to serve statis assets 18 | app.use(express.static(__dirname + '/../../client')); 19 | 20 | //auth controller must be first to attach user to request 21 | require('../users/userAuthController.js')(app); 22 | app.use('/comments', commentRouter); 23 | app.use('/ideas', ideaRouter); 24 | app.use('/rooms', roomRouter); 25 | app.use('/users', userRouter); 26 | app.use('/interest', interestRouter); 27 | 28 | require('../comments/commentRoutes.js')(commentRouter); 29 | require('../ideas/ideaRoutes.js')(ideaRouter); 30 | require('../rooms/roomRoutes.js')(roomRouter); 31 | require('../users/userRoutes.js')(userRouter); 32 | require('../interests/interestRoutes.js')(interestRouter); 33 | }; 34 | -------------------------------------------------------------------------------- /server/config/productionMiddleware.js: -------------------------------------------------------------------------------- 1 | var morgan = require('morgan'); 2 | var bodyParser = require('body-parser'); 3 | 4 | module.exports = function(app, express) { 5 | var commentRouter = express.Router(); 6 | var ideaRouter = express.Router(); 7 | var roomRouter = express.Router(); 8 | var userRouter = express.Router(); 9 | var interestRouter = express.Router(); 10 | 11 | app.use(morgan('dev')); 12 | // Returns middleware that only parses urlencoded bodies 13 | // and parses extended syntax with qs module 14 | app.use(bodyParser.urlencoded({extended: true})); 15 | // bodyParser.json() returns middleware that only parses json 16 | app.use(bodyParser.json()); 17 | // use express to serve statis assets 18 | app.use(express.static(__dirname + '/../../production')); 19 | 20 | //auth controller must be first to attach user to request 21 | require('../users/userAuthController.js')(app); 22 | app.use('/comments', commentRouter); 23 | app.use('/ideas', ideaRouter); 24 | app.use('/rooms', roomRouter); 25 | app.use('/users', userRouter); 26 | app.use('/interest', interestRouter); 27 | 28 | require('../comments/commentRoutes.js')(commentRouter); 29 | require('../ideas/ideaRoutes.js')(ideaRouter); 30 | require('../rooms/roomRoutes.js')(roomRouter); 31 | require('../users/userRoutes.js')(userRouter); 32 | require('../interests/interestRoutes.js')(interestRouter); 33 | }; 34 | -------------------------------------------------------------------------------- /specs/client/PageStoreSpec.js: -------------------------------------------------------------------------------- 1 | describe('paging', function(){ 2 | 3 | var welcomeHolder = app.PageStore.welcome; 4 | var roomsHolder = app.PageStore.rooms; 5 | 6 | beforeEach(function(){ 7 | app.PageStore.welcome = welcomeHolder; 8 | app.PageStore.rooms = roomsHolder; 9 | }); 10 | 11 | it('should have a welcome route that invokes a callback', function(){ 12 | var counter = 0; 13 | app.PageStore.welcome = function(){counter++;}; 14 | app.PageActions.navigate({ 15 | dest: 'welcome' 16 | }); 17 | expect(counter).to.equal(1); 18 | }); 19 | 20 | it('should have a rooms route that passes the roomId to a callback', function(){ 21 | var id; 22 | app.PageStore.rooms = function(roomId){id = roomId;}; 23 | app.PageActions.navigate({ 24 | dest: 'rooms', 25 | props: '0' 26 | }); 27 | expect(id).to.equal('0'); 28 | }); 29 | 30 | it('should emit events when routing', function(){ 31 | var callcount = 0; 32 | var callback = function(){callcount++;}; 33 | 34 | app.PageStore.addChangeListener(callback); 35 | 36 | app.PageActions.navigate({ 37 | dest: 'welcome' 38 | }); 39 | 40 | expect(callcount).to.equal(1); 41 | 42 | app.PageActions.navigate({ 43 | dest: 'rooms' 44 | }); 45 | 46 | expect(callcount).to.equal(2); 47 | 48 | app.PageStore.removeChangeListener(callback); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /client/react/Comment.js: -------------------------------------------------------------------------------- 1 | app.Comment = React.createClass({ 2 | getInitialState: function() { 3 | // set initial editing state to false 4 | return { 5 | editing: false 6 | }; 7 | }, 8 | 9 | componentWillReceiveProps: function() { 10 | // remove the editing parameter when the view updates 11 | this.setState({editing: false}); 12 | }, 13 | 14 | render: function() { 15 | var editForm; 16 | // if editing render edit form otherwise render "Edit Idea" button 17 | if (this.state.editing) { 18 | editForm = 19 | } 20 | 21 | return ( 22 |
23 |

{this.props.name}

24 |
25 | {editForm} 26 | 27 | 28 |
29 |
30 | ); 31 | }, 32 | 33 | edit: function(e) { 34 | e.preventDefault(); 35 | if (this.isMounted()) { 36 | this.setState({editing: !this.state.editing}); 37 | } 38 | }, 39 | 40 | delete: function(e) { 41 | e.preventDefault(); 42 | if(this.isMounted()) { 43 | app.CommentActions.delete(this.props._id); 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /specs/client/CommentsSpec.js: -------------------------------------------------------------------------------- 1 | describe('Comments', function () { 2 | 3 | var TestUtils, Comments, CommentStoreHolder, mockComments; 4 | 5 | beforeEach(function () { 6 | mockComments = [ {}, {}, {} ]; 7 | TestUtils = React.addons.TestUtils; 8 | 9 | CommentStoreHolder = {}; 10 | CommentStoreHolder.all = app.CommentStore.all; 11 | CommentStoreHolder.create = app.CommentStore.create; 12 | app.CommentStore._comments = mockComments, 13 | 14 | app.CommentStore.all = function () { 15 | this.emitChange(); 16 | }; 17 | 18 | app.CommentStore.create = function (idea_id, name) { 19 | this._comments.push(name); 20 | }; 21 | 22 | comments = React.createElement(app.Comments, null); 23 | comments = TestUtils.renderIntoDocument(comments); 24 | 25 | }); 26 | 27 | afterEach(function () { 28 | app.CommentStore._comments = []; 29 | app.CommentStore.all = CommentStoreHolder.all; 30 | app.CommentStore.create = CommentStoreHolder.create; 31 | }); 32 | 33 | it('should render the body', function () { 34 | expect(comments.refs.body).to.be.ok(); 35 | }); 36 | 37 | xit('should get loaded comments', function () { 38 | expect(comments.state.comments).to.equal(mockComments); 39 | }); 40 | 41 | xit('should update its state when the CommentStore changes', function () { 42 | 43 | app.CommentActions.create(0, 'test'); 44 | var stateComments = comments.state.comments; 45 | expect(stateComments[stateComments.length - 1]).to.equal('test'); 46 | 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /client/react/Interest.js: -------------------------------------------------------------------------------- 1 | app.Interest = React.createClass({ 2 | _liked: false, 3 | 4 | checkLiked: function (interests) { 5 | this._liked = false; 6 | var currentUser = app.UserStore.get(); 7 | if (currentUser){ 8 | interests.forEach(function (interest) { 9 | if (interest.owner === currentUser._id){ 10 | this._liked = interest._id; 11 | } 12 | }.bind(this)); 13 | } 14 | }, 15 | 16 | getInitialState: function () { 17 | var interests = app.InterestStore.getAll(this.props.idea_id); 18 | this.checkLiked(interests); 19 | return { 20 | interests: interests 21 | }; 22 | }, 23 | 24 | handleClick: function () { 25 | if (this._liked){ 26 | app.InterestActions.delete(this._liked); 27 | } else { 28 | app.InterestActions.create(this.props.idea_id); 29 | } 30 | }, 31 | 32 | componentDidMount: function () { 33 | app.InterestStore.addChangeListener(function () { 34 | if(this.isMounted()) { 35 | var interests = app.InterestStore.getAll(this.props.idea_id); 36 | this.checkLiked(interests); 37 | this.setState({ interests: interests }); 38 | } 39 | }.bind(this)); 40 | }, 41 | 42 | render: function () { 43 | var interestCount = this.state.interests.length; 44 | return ( 45 |
46 | 47 |  {interestCount} Likes 48 |
49 | ); 50 | } 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /server/users/userAuthController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var User = require('../db.js').User; 4 | var passport = require('passport'); 5 | var session = require('express-session'); 6 | 7 | module.exports = function(app) { 8 | app.use(session({ 9 | secret: 'hackReactorStudentsAreAwesome', 10 | resave: false, 11 | saveUninitialized: true 12 | })); 13 | 14 | app.use(passport.initialize()); 15 | app.use(passport.session()); 16 | 17 | 18 | passport.serializeUser(function(user, done) { 19 | done(null, user.username); 20 | }); 21 | 22 | passport.deserializeUser(function(username, done) { 23 | User.findOne({username: username}, function(err, user) { 24 | if (user) { 25 | done(null, user); 26 | } else { 27 | done(err, false); 28 | } 29 | }); 30 | }); 31 | 32 | 33 | app.get('/auth', passport.authenticate('github')); 34 | app.get('/auth/callback', 35 | passport.authenticate('github', { successRedirect: '/', failureRedirect: '/' })); 36 | 37 | 38 | var GitHubStrategy = require('passport-github').Strategy; 39 | passport.use(new GitHubStrategy({ 40 | clientID: '4e0e24f94e07e2e2d1c9', 41 | clientSecret: 'c5a5d8a6c39396e0292e21267e4b8fc7aebf3bfe', 42 | callbackURL: 'http://localhost:3000/auth/callback' 43 | }, 44 | function(accessToken, refreshToken, profile, done) { 45 | User.findOne({username: profile.username}, function(err, user) { 46 | if(err) { 47 | console.log(err); 48 | } 49 | 50 | if (!user) { 51 | user = new User({ username: profile.username, socialData: profile._json }); 52 | user.save(); 53 | } 54 | done(null, user); 55 | }); 56 | } 57 | )); 58 | }; 59 | -------------------------------------------------------------------------------- /client/react/Comments.js: -------------------------------------------------------------------------------- 1 | app.Comments = React.createClass({ 2 | //get all loaded comments 3 | getInitialState: function () { 4 | return { 5 | displaying: false, 6 | comments: app.CommentStore.getAll(this.props.idea_id) 7 | }; 8 | }, 9 | 10 | //when we mount the view setup event listener for store changes 11 | componentDidMount: function () { 12 | app.CommentStore.addChangeListener(function () { 13 | if (this.isMounted()) { 14 | this.setState({ comments: app.CommentStore.getAll(this.props.idea_id) }); 15 | } 16 | }.bind(this)); 17 | }, 18 | 19 | show: function (e) { 20 | e.preventDefault(); 21 | 22 | if (this.isMounted()) { 23 | this.setState({ displaying: !this.state.displaying }); 24 | } 25 | }, 26 | 27 | //render a comment component for each comment 28 | render: function () { 29 | var comments; 30 | var commentForm; 31 | var showCommentsButton; 32 | //display comments if we are displaying, otherwise show buttons 33 | if (this.state.displaying){ 34 | commentForm = 35 | comments = []; 36 | //render a comment component for each comment 37 | this.state.comments.forEach(function (comment) { 38 | comments.push( 39 | 40 | ); 41 | }); 42 | } 43 | 44 | showCommentsButton = 45 | 46 | return ( 47 |
48 | { showCommentsButton } 49 | { comments } 50 | { commentForm } 51 |
52 | ); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /client/react/BrainstormApp.js: -------------------------------------------------------------------------------- 1 | app.BrainstormApp = React.createClass({ 2 | getInitialState: function() { 3 | return { 4 | indexView: true, 5 | currentUser: app.UserStore.get() 6 | }; 7 | }, 8 | 9 | componentDidMount: function () { 10 | app.UserStore.addChangeListener(function() { 11 | if(this.isMounted()) { 12 | this.setState({ currentUser: app.UserStore.get() }); 13 | } 14 | }.bind(this)); 15 | 16 | app.PageStore.addChangeListener(function(){ 17 | 18 | //get state from the PageStore.currentRoute 19 | var state = app.PageStore.currentRoute; 20 | 21 | //if props is undefined set it to empty string 22 | state.props = state.props || ''; 23 | state.indexView = (state.dest === 'welcome' ? true : false); 24 | if (state.dest === 'rooms'){ 25 | setTimeout(function () { 26 | app.PageActions.getRoomData(state.props); 27 | }, 0); 28 | } 29 | this.setState(state); 30 | 31 | if(!state.indexView) { 32 | socket.emit('join', state.props); 33 | } 34 | }.bind(this)); 35 | }, 36 | 37 | render: function(){ 38 | var currentView; 39 | if(this.state.indexView) { //thisIsHomePage 40 | currentView = ( 41 |
42 | 43 | 44 |
45 | ); 46 | } else { // must be a room 47 | currentView = ( 48 |
49 | 50 | 51 | 52 |
53 | ); 54 | } 55 | 56 | return ( 57 |
58 | 59 | { currentView } 60 |
61 | ); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /client/app/stores/RoomStore.js: -------------------------------------------------------------------------------- 1 | app.RoomStore = _.extend({}, EventEmitter.prototype, { 2 | _rooms: [], 3 | 4 | getAll: function() { 5 | return this._rooms; 6 | }, 7 | 8 | all: function() { 9 | $.ajax({ 10 | type: 'GET', 11 | url: '/rooms' 12 | }) 13 | .done(function(rooms) { 14 | this._rooms = rooms; 15 | this.emitChange(); 16 | }.bind(this)) 17 | .fail(function(error) { 18 | console.log(error); 19 | }); 20 | 21 | socket.on('room-change', function(currentRooms) { 22 | this._rooms = currentRooms; 23 | this.emitChange(); 24 | }.bind(this)); 25 | }, 26 | 27 | create: function(name) { 28 | $.ajax({ 29 | type: 'POST', 30 | url: '/rooms', 31 | data: {name: name} 32 | }) 33 | .done(function(room) { 34 | this._rooms.push(room); 35 | 36 | // broadcast that _rooms has changed 37 | socket.emit('room-change', this._rooms); 38 | this.emitChange(); 39 | 40 | app.PageActions.navigate({ 41 | dest: 'rooms', 42 | props: room._id 43 | }); 44 | }.bind(this)) 45 | .fail(function(error) { 46 | console.log(error); 47 | }); 48 | }, 49 | 50 | emitChange: function() { 51 | this.emit(CHANGE_EVENT); 52 | }, 53 | 54 | addChangeListener: function(callback) { 55 | this.on(CHANGE_EVENT, callback); 56 | }, 57 | 58 | removeChangeListener: function(callback) { 59 | this.removeListener(CHANGE_EVENT, callback); 60 | } 61 | }); 62 | 63 | app.AppDispatcher.register(function(payload) { 64 | var action = payload.action; 65 | var name; 66 | 67 | switch(action.actionType) { 68 | case app.RoomConstants.ROOM_CREATE: 69 | name = action.name.trim(); 70 | 71 | if (name !== '') { 72 | app.RoomStore.create(name); 73 | } 74 | break; 75 | 76 | default: 77 | return true; 78 | } 79 | }); 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Brainstorm", 3 | "version": "1.0.0", 4 | "description": "Brainstorming app", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node index.js& ./node_modules/.bin/mocha --bail --reporter nyan specs/server/ServerSpec.js; pkill -n node;" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/HRR2-Brainstorm/Brainstorm" 12 | }, 13 | "keywords": [ 14 | "Brainstorm" 15 | ], 16 | "author": "Brett, Dmitri, Gunnari, Jason", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/HRR2-Brainstorm/Brainstorm/issues" 20 | }, 21 | "homepage": "https://github.com/HRR2-Brainstorm/Brainstorm", 22 | "devDependencies": { 23 | "browser-sync": "^1.7.2", 24 | "chai": "^1.10.0", 25 | "expect.js": "^0.3.1", 26 | "gulp": "^3.8.10", 27 | "gulp-minify-css": "^0.3.11", 28 | "gulp-minify-html": "^0.1.7", 29 | "gulp-nodemon": "^1.0.4", 30 | "gulp-rev": "^2.0.1", 31 | "gulp-shell": "^0.2.11", 32 | "gulp-uglify": "^1.0.2", 33 | "gulp-usemin": "^0.3.8", 34 | "gulp-watch": "^3.0.0", 35 | "jasmine-core": "^2.1.2", 36 | "karma": "^0.12.28", 37 | "karma-chrome-launcher": "^0.1.5", 38 | "karma-cli": "0.0.4", 39 | "karma-jasmine": "^0.3.2", 40 | "karma-mocha": "^0.1.9", 41 | "karma-nested-reporter": "^0.1.3", 42 | "karma-nyan-reporter": "0.0.50", 43 | "karma-phantomjs-launcher": "^0.1.4", 44 | "karma-unicorn-reporter": "^0.1.4", 45 | "mocha": "^2.0.1", 46 | "request": "^2.49.0" 47 | }, 48 | "dependencies": { 49 | "body-parser": "^1.10.0", 50 | "express": "^4.10.4", 51 | "express-session": "^1.9.3", 52 | "mongodb": "^1.4.23", 53 | "mongoose": "^3.8.20", 54 | "morgan": "^1.5.0", 55 | "passport": "^0.2.1", 56 | "passport-github": "^0.1.5", 57 | "q": "^1.1.2", 58 | "socket.io": "^1.2.1", 59 | "socket.io-client": "^1.2.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/app/stores/PageStore.js: -------------------------------------------------------------------------------- 1 | app.PageStore = _.extend({}, EventEmitter.prototype, { 2 | 3 | //define routes for PageStore 4 | routes: { 5 | 6 | welcome: function () { 7 | return '/welcome'; 8 | }, 9 | 10 | //rooms route needs the roomId to route to 11 | rooms: function (roomId) { 12 | return '/rooms/'+roomId; 13 | } 14 | 15 | }, 16 | 17 | //dispatch event to render welcome 18 | welcome: function () { 19 | this.emitChange('welcome'); 20 | }, 21 | 22 | //dispatch event to render rooms 23 | rooms: function (roomId) { 24 | this.emitChange('room', roomId); 25 | }, 26 | 27 | emitChange: function(){ 28 | this.emit(CHANGE_EVENT); 29 | }, 30 | 31 | addChangeListener: function (callback) { 32 | this.on(CHANGE_EVENT, callback); 33 | }, 34 | 35 | removeChangeListener: function (callback) { 36 | this.removeListener(CHANGE_EVENT, callback); 37 | } 38 | 39 | }); 40 | 41 | app.AppDispatcher.register(function (payload) { 42 | var action = payload.action; 43 | var body; 44 | 45 | //listen for navigate action 46 | switch (action.actionType) { 47 | case app.PageConstants.NAVIGATE: 48 | 49 | //get destination and properties from action 50 | body = action.body; 51 | 52 | //set currentRoute in store for views to access 53 | app.PageStore.currentRoute = body; 54 | 55 | //route application 56 | page(app.PageStore.routes[body.dest](body.props)); 57 | break; 58 | 59 | default: 60 | return true; 61 | } 62 | 63 | }); 64 | 65 | //page options 66 | page({ 67 | 68 | //prefix urls with #! 69 | hashbang: true, 70 | 71 | //prevent page dispatching 72 | dispatch: false 73 | }); 74 | 75 | //on welcome route: call welcome route 76 | page('/welcome', function(){ 77 | app.PageStore.welcome(); 78 | }); 79 | 80 | //on rooms route: call rooms route and pass it room id 81 | page('/rooms/:roomId', function(ctx){ 82 | app.PageStore.rooms(ctx.params.roomId); 83 | }); 84 | -------------------------------------------------------------------------------- /specs/client/CommentStoreSpec.js: -------------------------------------------------------------------------------- 1 | describe('CommentStore', function(){ 2 | 3 | var mockResult, mockIdea_id, mockComment; 4 | var ajaxHolder = $.ajax; 5 | 6 | beforeEach(function () { 7 | mockComment = undefined; 8 | mockResult = undefined; 9 | $.ajax = function (props) { 10 | mockResult = props; 11 | return { 12 | done: function (cb) { 13 | cb([]); 14 | return { fail: function () {} }; 15 | } 16 | }; 17 | }; 18 | }); 19 | 20 | afterEach(function () { 21 | $.ajax = ajaxHolder; 22 | }); 23 | 24 | it('should perform an ajax request to get comments', function () { 25 | app.CommentActions.get(); 26 | expect(mockResult.type).to.equal('GET'); 27 | expect(mockResult.url).to.equal('/comments'); 28 | }); 29 | 30 | xit('should perform an ajax request to post a comment', function () { 31 | mockIdea_id = 0; 32 | mockComment = 'testcomment'; 33 | app.CommentActions.create(mockIdea_id, mockComment); 34 | expect(mockResult.type).to.equal('POST'); 35 | expect(mockResult.url).to.equal('/comments'); 36 | expect(mockResult.data.name).to.equal(mockComment); 37 | }); 38 | 39 | xit('should perform an ajax request to edit a comment', function () { 40 | mockComment = 'updatedcomment'; 41 | var _id = 0; 42 | app.CommentActions.edit(_id, mockComment); 43 | expect(mockResult.type).to.equal('PUT'); 44 | expect(mockResult.url).to.equal('/comments/0'); 45 | expect(mockResult.data.name).to.equal(mockComment); 46 | }); 47 | 48 | xit('should perform an ajax request to delete a comment', function () { 49 | var _id = 0; 50 | app.CommentActions.delete(_id); 51 | expect(mockResult.type).to.equal('DELETE'); 52 | expect(mockResult.url).to.equal('/comments/0'); 53 | }); 54 | 55 | xit('should emit events after comment AJAX requests complete', function(){ 56 | var callcount = 0; 57 | var callback = function(){callcount++;}; 58 | 59 | app.CommentStore.addChangeListener(callback); 60 | 61 | app.CommentActions.get(); 62 | 63 | expect(callcount).to.equal(1); 64 | 65 | app.CommentActions.create(0, 'test'); 66 | 67 | expect(callcount).to.equal(2); 68 | 69 | app.CommentStore.removeChangeListener(callback); 70 | 71 | }); 72 | 73 | }); 74 | -------------------------------------------------------------------------------- /server/interests/interestController.js: -------------------------------------------------------------------------------- 1 | var Interest = require('./interest.server.model.js'); 2 | var Idea = require('../ideas/idea.server.model.js'); 3 | var Q = require('q'); 4 | 5 | module.exports = { 6 | newInterest: function (req, res, next) { 7 | var interest = {}; 8 | 9 | //get interest parameters from request 10 | interest.idea = req.params.idea_id; 11 | interest.owner = req.user._id; 12 | // interest.idea = req.body.ideaId; 13 | // interest.owner = req.body.userId; 14 | 15 | var createInterest = Q.nbind(Interest.create, Interest); 16 | 17 | createInterest(interest) 18 | .then(function (createdInterest) { 19 | if (createdInterest) { 20 | res.json(createdInterest); 21 | } 22 | }) 23 | .fail(function (error) { 24 | next(error); 25 | }); 26 | }, 27 | 28 | allInterests: function (req, res, next) { 29 | var getIdeas = Q.nbind(Idea.find, Idea); 30 | var query = req.params.room_id ? { room: req.params.room_id } : {}; 31 | 32 | // get all ideas 33 | getIdeas(query) 34 | .then(function(allIdeas) { 35 | // if there are ideas send them in response 36 | if(allIdeas) { 37 | var getInterests = Q.nbind(Interest.find, Interest); 38 | var query = []; 39 | allIdeas.forEach(function (idea) { 40 | query.push({ idea: idea._id }); 41 | }); 42 | getInterests(query) 43 | .then(function (allInterests) { 44 | if (allInterests) { 45 | res.json(allInterests); 46 | } 47 | }) 48 | .fail(function (error) { 49 | next(error); 50 | }); 51 | } 52 | }) 53 | .fail(function(error) { 54 | next(error); 55 | }); 56 | }, 57 | 58 | deleteInterest: function (req, res, next) { 59 | var deleteInterest = Q.nbind(Interest.findOneAndRemove, Interest); 60 | deleteInterest({ 61 | _id: req.params.interest_id 62 | }).then(function (interest) { 63 | if (interest) { 64 | res.json({ 65 | message: 'Successfully deleted', 66 | _id: req.params.interest_id 67 | }); 68 | } 69 | }) 70 | .fail(function (error) { 71 | next(error); 72 | }); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Dec 05 2014 20:16:53 GMT-0500 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | //lib files 19 | 'client/lib/flux/dist/Flux.js', 20 | 'client/lib/react/react-with-addons.js', 21 | 'client/lib/eventEmitter/EventEmitter.js', 22 | 'client/lib/underscore/underscore.js', 23 | 'client/lib/jquery/dist/jquery.js', 24 | 'client/lib/page/page.js', 25 | 'client/lib/socket.io-client/socket.io.js', 26 | 27 | //app code 28 | 'client/app/app.js', 29 | 'client/app/**/*.js', 30 | 31 | //spec files 32 | 'node_modules/expect.js/index.js', 33 | 'specs/client/**/*.js' 34 | ], 35 | 36 | 37 | // list of files to exclude 38 | exclude: [ 39 | 'karma.conf.js', 40 | 'client/app/starter.js' 41 | ], 42 | 43 | 44 | // preprocess matching files before serving them to the browser 45 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 46 | preprocessors: { 47 | }, 48 | 49 | 50 | // test results reporter to use 51 | // possible values: 'dots', 'progress' 52 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 53 | reporters: ['nyan'], 54 | 55 | 56 | // web server port 57 | port: 9876, 58 | 59 | 60 | // enable / disable colors in the output (reporters and logs) 61 | colors: true, 62 | 63 | 64 | // level of logging 65 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 66 | logLevel: config.LOG_INFO, 67 | 68 | 69 | // enable / disable watching file and executing tests whenever any file changes 70 | autoWatch: false, 71 | 72 | 73 | // start these browsers 74 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 75 | browsers: ['Chrome'], 76 | 77 | 78 | // Continuous Integration mode 79 | // if true, Karma captures browsers, runs the tests and exits 80 | singleRun: true 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | watch = require('gulp-watch'), 5 | nodemon = require('gulp-nodemon'), 6 | bs = require('browser-sync'), 7 | reload = bs.reload, 8 | karma = require('karma').server, 9 | shell = require('gulp-shell'), 10 | usemin = require('gulp-usemin'), 11 | uglify = require('gulp-uglify'), 12 | minifyHtml = require('gulp-minify-html'), 13 | minifyCss = require('gulp-minify-css'), 14 | rev = require('gulp-rev'); 15 | 16 | var paths = { 17 | scripts: ['client/app/**/*.js'], 18 | html: ['client/app/**/*.html', 'client/index.html'], 19 | styles: ['client/styles/style.css'], 20 | test: ['specs/**/*.js'] 21 | }; 22 | 23 | gulp.task('start', ['serve'], function() { 24 | bs({ 25 | notify: true, 26 | injectChanges: true, 27 | files: paths.scripts.concat(paths.html, paths.styles), 28 | proxy: 'localhost:8000' 29 | }); 30 | }); 31 | 32 | gulp.task('jsx', shell.task([ 33 | 'jsx ' + __dirname + '/client/react ' + __dirname + '/client/app/react', 34 | 'rm -r ' + __dirname + '/client/app/react/.module-cache' 35 | ])); 36 | 37 | gulp.task('jsx-auto', ['jsx'], function () { 38 | watch(['client/react/**/*.js'], function () { 39 | gulp.start('jsx'); 40 | }); 41 | }); 42 | 43 | gulp.task('karma', shell.task([ 44 | 'karma start' 45 | ])); 46 | 47 | gulp.task('karma-auto', function (done) { 48 | karma.start({ 49 | configFile: __dirname + '/karma.conf.js', 50 | autoWatch: true, 51 | singleRun: false 52 | },done); 53 | }); 54 | 55 | gulp.task('selenium', shell.task([ 56 | 'webdriver-manager start' 57 | ])); 58 | 59 | gulp.task('e2e', shell.task([ 60 | 'protractor e2e/conf.js' 61 | ])); 62 | 63 | gulp.task('serve', function () { 64 | nodemon({script: 'index.js', ignore: 'node_modules/**/*.js'}); 65 | }); 66 | 67 | gulp.task('clearProd', shell.task([ 68 | 'rm -r production/' 69 | ])); 70 | 71 | gulp.task('usemin', ['jsx', 'clearProd'], function () { 72 | gulp.src('./client/index.html') 73 | .pipe(usemin({ 74 | css: [minifyCss(), 'concat'], 75 | html: [minifyHtml({empty: true})], 76 | js: [uglify(), rev()] 77 | })) 78 | .pipe(gulp.dest('production/')); 79 | }); 80 | 81 | gulp.task('production', ['usemin'], function () { 82 | nodemon({script: 'productionIndex.js', ignore: 'node_modules/**/*.js'}); 83 | }); 84 | 85 | gulp.task('default', ['start']); 86 | -------------------------------------------------------------------------------- /client/react/Idea.js: -------------------------------------------------------------------------------- 1 | app.Idea = React.createClass({ 2 | 3 | getInitialState: function() { 4 | // set initial editing state to false 5 | return { 6 | displaying: true, 7 | editing: false 8 | }; 9 | }, 10 | 11 | componentDidMount: function() { 12 | // add a change listener on the IdeaStore 13 | // this is needed when the edit comes back and emits a change 14 | // that will force the component to re-render 15 | app.IdeaStore.addChangeListener(function() { 16 | if(this.isMounted()) { 17 | this.setState({editing: false}); 18 | } 19 | }.bind(this)); 20 | }, 21 | 22 | show: function () { 23 | if (this.isMounted()) { 24 | this.setState({ displaying: !this.state.displaying }); 25 | } 26 | }, 27 | 28 | render: function() { 29 | var ideaContent; 30 | var editForm; 31 | // if editing render edit form otherwise render "Edit Idea" button 32 | if (this.state.editing) { 33 | editForm = 34 | } 35 | 36 | if (this.state.displaying) { 37 | ideaContent = ( 38 |
39 | 40 |
41 |
42 |

{this.props.name}

43 | {editForm} 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | ); 62 | } 63 | 64 | return ( 65 |
66 | {ideaContent} 67 |
68 | ); 69 | }, 70 | 71 | edit: function(e) { 72 | e.preventDefault(); 73 | if (this.isMounted()) { 74 | this.setState({ editing: !this.state.editing }); 75 | } 76 | }, 77 | 78 | delete: function(e) { 79 | e.preventDefault(); 80 | if (this.isMounted()) { 81 | app.IdeaActions.delete({ id: this.props._id }); 82 | } 83 | } 84 | }); 85 | -------------------------------------------------------------------------------- /server/ideas/ideaController.js: -------------------------------------------------------------------------------- 1 | var Idea = require('./idea.server.model.js'); 2 | var Q = require('q'); 3 | 4 | module.exports = { 5 | newIdea: function (req, res, next) { 6 | var idea = {}; 7 | 8 | idea.name = req.body.name; 9 | idea.room = req.params.room_id; 10 | idea.owner = req.user._id; 11 | 12 | // create promise for Idea.create method 13 | var createIdea = Q.nbind(Idea.create, Idea); 14 | 15 | // attempt to create the idea 16 | createIdea(idea) 17 | .then(function (createdIdea) { 18 | // if the idea is created send that object back 19 | if (createdIdea) { 20 | res.json(createdIdea); 21 | } 22 | }) 23 | .fail(function (error) { 24 | next(error); 25 | }); 26 | }, 27 | 28 | allIdeas: function(req, res, next) { 29 | // create promise for Idea.find 30 | var getIdeas = Q.nbind(Idea.find, Idea); 31 | var query = req.params.room_id ? { room: req.params.room_id } : {}; 32 | // get all ideas 33 | getIdeas(query) 34 | .then(function(allIdeas) { 35 | // if there are ideas send them in response 36 | if(allIdeas) { 37 | res.json(allIdeas); 38 | } 39 | }) 40 | .fail(function(error) { 41 | next(error); 42 | }); 43 | }, 44 | 45 | updateIdea: function(req, res, next) { 46 | // create promise for Idea.findById 47 | var findIdeaById = Q.nbind(Idea.findById, Idea); 48 | 49 | // attempt to find the idea by the id passed in 50 | findIdeaById(req.params.idea_id) 51 | .then(function(foundIdea) { 52 | // if the idea is found update the name and save 53 | if (foundIdea) { 54 | foundIdea.name = req.body.name; 55 | foundIdea.save(function(err) { 56 | if (err) { 57 | res.send(err); 58 | } 59 | res.json(foundIdea); 60 | }); 61 | } 62 | }) 63 | .fail(function(error) { 64 | next(error); 65 | }); 66 | }, 67 | 68 | deleteIdea: function(req, res, next) { 69 | // create promise for Idea.remove method 70 | var removeIdea = Q.nbind(Idea.remove, Idea); 71 | // delete idea based on id passed in 72 | removeIdea({_id: req.params.idea_id}) 73 | .then(function(removedIdea) { 74 | // if the idea has been removed, send success 75 | // and id to remove from IdeaStore 76 | if(removedIdea[1].ok) { 77 | res.json({ 78 | message: 'Successfully deleted.', 79 | _id: req.params.idea_id 80 | }); 81 | } 82 | }) 83 | .fail(function(error) { 84 | next(error); 85 | }); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /server/comments/commentController.js: -------------------------------------------------------------------------------- 1 | var Comment = require('./comment.server.model.js'); 2 | var Idea = require('../ideas/idea.server.model.js'); 3 | var Q = require('q'); 4 | 5 | module.exports = { 6 | newComment: function (req, res, next) { 7 | var comment = {}; 8 | 9 | //get comment parameters from request 10 | comment.name = req.body.name; 11 | comment.idea = req.params.idea_id; 12 | comment.owner = req.user._id; 13 | // comment.idea = req.body.ideaId; 14 | // comment.owner = req.body.userId; 15 | 16 | var createComment = Q.nbind(Comment.create, Comment); 17 | 18 | createComment(comment) 19 | .then(function (createdComment) { 20 | if (createdComment) { 21 | res.json(createdComment); 22 | } 23 | }) 24 | .fail(function (error) { 25 | next(error); 26 | }); 27 | }, 28 | 29 | allComments: function (req, res, next) { 30 | var getIdeas = Q.nbind(Idea.find, Idea); 31 | var query = req.params.room_id ? { room: req.params.room_id } : {}; 32 | 33 | // get all ideas 34 | getIdeas(query) 35 | .then(function(allIdeas) { 36 | // if there are ideas send them in response 37 | if(allIdeas) { 38 | var getComments = Q.nbind(Comment.find, Comment); 39 | var query = []; 40 | allIdeas.forEach(function (idea) { 41 | query.push({ idea: idea._id}); 42 | }); 43 | getComments(query) 44 | .then(function (allComments) { 45 | if (allComments) { 46 | res.json(allComments); 47 | } 48 | }) 49 | .fail(function (error) { 50 | next(error); 51 | }); 52 | } 53 | }) 54 | .fail(function(error) { 55 | next(error); 56 | }); 57 | }, 58 | 59 | editComment: function (req, res, next) { 60 | var editComment = Q.nbind(Comment.findOneAndUpdate, Comment); 61 | editComment({ 62 | _id: req.params.comment_id 63 | },{ 64 | name: req.body.name 65 | }).then(function (comment) { 66 | if (comment) { 67 | res.json(comment); 68 | } 69 | }) 70 | .fail(function (error) { 71 | next(error); 72 | }); 73 | }, 74 | 75 | deleteComment: function (req, res, next) { 76 | var deleteComment = Q.nbind(Comment.findOneAndRemove, Comment); 77 | deleteComment({ 78 | _id: req.params.comment_id 79 | }).then(function (comment) { 80 | if (comment) { 81 | res.json({ 82 | message: 'Successfully deleted', 83 | _id: req.params.comment_id 84 | }); 85 | } 86 | }) 87 | .fail(function (error) { 88 | next(error); 89 | }); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /client/styles/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | -webkit-font-smoothing: antialiased; 4 | } 5 | 6 | body { 7 | padding:2em 1em; 8 | } 9 | 10 | #main { 11 | max-width:1060px; 12 | margin:0 auto; 13 | } 14 | 15 | header { 16 | margin:0 0 2em 0; 17 | } 18 | 19 | .user-false .auth-check { 20 | display: none; 21 | } 22 | 23 | div.login { 24 | text-align:right; 25 | } 26 | 27 | form .pure-button:not(:first-of-type) { 28 | margin-left:1em; 29 | } 30 | 31 | form .pure-button:last-of-type { 32 | margin-left:1em; 33 | } 34 | 35 | input.postfix { 36 | margin-bottom:0.5em; 37 | } 38 | 39 | .pure-button.no-margin { 40 | margin-left:0 !important; 41 | } 42 | 43 | .rooms { 44 | margin-top:2em; 45 | } 46 | 47 | .room { 48 | margin:0 0 0.5em 0; 49 | } 50 | 51 | .room a { 52 | display:block; 53 | line-height:1.2em; 54 | padding:1em 0.3em 1em 1em; 55 | background-color: #f2f2f2; 56 | text-align:left; 57 | height:100%; 58 | 59 | text-decoration:none; 60 | } 61 | 62 | .room a:hover { 63 | background-color:#f2f2f2; 64 | text-decoration:underline; 65 | } 66 | 67 | .idea { 68 | margin-top:1.5em; 69 | border-bottom:1px solid #f2f2f2; 70 | padding-bottom:1.5em; 71 | } 72 | 73 | .controls { 74 | margin-bottom:1em; 75 | } 76 | 77 | .controls .pure-button { 78 | margin-right:0.5em; 79 | font-size:85%; 80 | } 81 | 82 | .watch { 83 | text-align:right; 84 | } 85 | 86 | h2 { 87 | margin-top:0; 88 | } 89 | 90 | .likes { 91 | margin-top:0.5em; 92 | } 93 | 94 | .likes .pure-button { 95 | font-size:75%; 96 | } 97 | 98 | .comments { 99 | margin-top:0.5em; 100 | } 101 | 102 | .comments > div > .pure-button { 103 | margin-left:0 !important; 104 | } 105 | 106 | .formComment { 107 | margin-top:2em; 108 | } 109 | 110 | .formComment .pure-button { 111 | 112 | } 113 | 114 | .formEditComment { 115 | font-size:85%; 116 | } 117 | 118 | @media screen and (min-width: 35.5em) { 119 | 120 | input.postfix { 121 | margin-bottom:0; 122 | -webkit-border-top-right-radius: 0px !important; 123 | -webkit-border-bottom-right-radius: 0px !important; 124 | -moz-border-radius-topright: 0px !important; 125 | -moz-border-radius-bottomright: 0px !important; 126 | border-top-right-radius: 0px !important; 127 | border-bottom-right-radius: 0px !important; 128 | } 129 | 130 | .pure-button.no-margin { 131 | -webkit-border-top-left-radius: 0px !important; 132 | -webkit-border-bottom-left-radius: 0px !important; 133 | -moz-border-radius-topleft: 0px !important; 134 | -moz-border-radius-bottomleft: 0px !important; 135 | border-top-left-radius: 0px !important; 136 | border-bottom-left-radius: 0px !important; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Brainstormer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | Future Site of Brainstormer 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /client/app/stores/InterestStore.js: -------------------------------------------------------------------------------- 1 | app.InterestStore = _.extend({}, EventEmitter.prototype, { 2 | _interests: [], 3 | 4 | _room: function() { 5 | return app.PageStore.currentRoute.props; 6 | }, 7 | 8 | getAll: function (idea_id) { 9 | if (!idea_id) return this._interests; 10 | return _(this._interests).filter(function (interest) { 11 | return interest.idea === idea_id; 12 | }); 13 | }, 14 | 15 | get: function (room_id) { 16 | $.ajax({ 17 | type: 'GET', 18 | url: '/interest/' + room_id, 19 | }) 20 | .done(function (interests) { 21 | this._interests = interests; 22 | // broadcast that _ideas has changed 23 | this.emitChange(); 24 | }.bind(this)) 25 | .fail(function(error) { 26 | console.error(error); 27 | }); 28 | 29 | socket.on('interest-change', function(currentInterests) { 30 | this._interests = currentInterests; 31 | this.emitChange(); 32 | }.bind(this)); 33 | }, 34 | 35 | create: function(idea_id) { 36 | $.ajax({ 37 | type: 'POST', 38 | url: '/interest/' + idea_id 39 | }) 40 | .done(function(interest) { 41 | this._interests.push(interest); 42 | 43 | // broadcast that _interests has changed 44 | socket.emit('interest-change', this._interests, this._room()); 45 | this.emitChange(); 46 | }.bind(this)) 47 | .fail(function(error) { 48 | console.log(error); 49 | }); 50 | }, 51 | 52 | delete: function (_id) { 53 | $.ajax({ 54 | type: 'DELETE', 55 | url: '/interest/' + _id 56 | }) 57 | .done(function (oldInterest) { 58 | //look through comments and splice out comment 59 | this._interests.forEach(function (interest, i) { 60 | if (interest._id === oldInterest._id) { 61 | this._interests.splice(i, 1); 62 | } 63 | }.bind(this)); 64 | 65 | // broadcast that _comments has changed 66 | socket.emit('interest-change', this._interests, this._room()); 67 | this.emitChange(); 68 | }.bind(this)) 69 | .fail(function (error) { 70 | console.log(error); 71 | }); 72 | }, 73 | 74 | emitChange: function() { 75 | this.emit(CHANGE_EVENT); 76 | }, 77 | 78 | addChangeListener: function(callback) { 79 | this.on(CHANGE_EVENT, callback); 80 | }, 81 | 82 | removeChangeListener: function(callback) { 83 | this.removeListener(CHANGE_EVENT, callback); 84 | } 85 | }); 86 | 87 | app.AppDispatcher.register(function(payload) { 88 | var action = payload.action; 89 | var idea_id = action.idea_id; 90 | var _id; 91 | 92 | switch(action.actionType) { 93 | case app.InterestConstants.INTEREST_GET: 94 | app.InterestStore.all(); 95 | break; 96 | 97 | case app.InterestConstants.INTEREST_CREATE: 98 | idea_id = action.idea_id; 99 | 100 | app.InterestStore.create(idea_id); 101 | break; 102 | 103 | case app.InterestConstants.INTEREST_DELETE: 104 | _id = action._id; 105 | 106 | app.InterestStore.delete(_id); 107 | break; 108 | 109 | case app.PageConstants.GETROOMDATA: 110 | if (action.room_id){ 111 | app.InterestStore.get(action.room_id); 112 | } 113 | break; 114 | 115 | default: 116 | return true; 117 | } 118 | }); 119 | -------------------------------------------------------------------------------- /client/app/stores/IdeaStore.js: -------------------------------------------------------------------------------- 1 | var socket = io.connect(); 2 | 3 | app.IdeaStore = _.extend({}, EventEmitter.prototype, { 4 | _ideas: [], 5 | 6 | _room: function() { 7 | return app.PageStore.currentRoute.props; 8 | }, 9 | 10 | getAll: function() { 11 | return this._ideas; 12 | }, 13 | 14 | get: function (room_id) { 15 | $.ajax({ 16 | type: 'GET', 17 | url: '/ideas/' + room_id, 18 | }) 19 | .done(function (ideas) { 20 | this._ideas = ideas; 21 | // broadcast that _ideas has changed 22 | this.emitChange(); 23 | }.bind(this)) 24 | .fail(function(error) { 25 | console.error(error); 26 | }); 27 | 28 | socket.on('idea-change', function(currentIdeas) { 29 | this._ideas = currentIdeas; 30 | this.emitChange(); 31 | }.bind(this)); 32 | }, 33 | 34 | all: function () { 35 | $.ajax({ 36 | type: 'GET', 37 | url: '/ideas' 38 | }) 39 | .done(function (ideas) { 40 | this._ideas = ideas; 41 | // broadcast that _ideas has changed 42 | this.emitChange(); 43 | }.bind(this)) 44 | .fail(function(error) { 45 | console.error(error); 46 | }); 47 | 48 | socket.on('idea-change', function(currentIdeas) { 49 | this._ideas = currentIdeas; 50 | this.emitChange(); 51 | }.bind(this)); 52 | }, 53 | 54 | create: function (room_id, name) { 55 | $.ajax({ 56 | type: 'POST', 57 | url: '/ideas/' + room_id, 58 | data: {name: name} 59 | }) 60 | .done(function (idea) { 61 | this._ideas.push(idea); 62 | 63 | // broadcast that _ideas has changed 64 | socket.emit('idea-change', this._ideas, this._room()); 65 | this.emitChange(); 66 | }.bind(this)) 67 | .fail(function(error) { 68 | console.error(error); 69 | }); 70 | }, 71 | 72 | edit: function(idea) { 73 | $.ajax({ 74 | type: 'PUT', 75 | url: '/ideas/' + idea.id, 76 | data: idea 77 | }) 78 | .done(function(ideaEdit) { 79 | // look through the ideas until finding a match 80 | // for id and then update the name property 81 | this._ideas.forEach(function(idea) { 82 | if(idea._id === ideaEdit._id) { 83 | idea.name = ideaEdit.name; 84 | 85 | // broadcast that _ideas has changed 86 | socket.emit('idea-change', this._ideas, this._room()); 87 | return this.emitChange(); 88 | } 89 | }.bind(this)); 90 | }.bind(this)) 91 | .fail(function(error) { 92 | console.error(error); 93 | }); 94 | }, 95 | 96 | delete: function(idea) { 97 | $.ajax({ 98 | type: 'DELETE', 99 | url: '/ideas/' + idea.id 100 | }) 101 | .done(function(oldId) { 102 | // find deleted idea by oldId in _ideas and remove 103 | this._ideas.forEach(function(idea, index) { 104 | if(idea._id === oldId._id) { 105 | this._ideas.splice(index, 1); 106 | 107 | // broadcast that _ideas has changed 108 | socket.emit('idea-change', this._ideas, this._room()); 109 | return this.emitChange(); 110 | } 111 | }.bind(this)); 112 | }.bind(this)) 113 | .fail(function(error) { 114 | console.error(error); 115 | }); 116 | }, 117 | 118 | emitChange: function () { 119 | this.emit(CHANGE_EVENT); 120 | }, 121 | 122 | addChangeListener: function (callback) { 123 | this.on(CHANGE_EVENT, callback); 124 | }, 125 | 126 | removeChangeListener: function (callback) { 127 | this.removeListener(CHANGE_EVENT, callback); 128 | } 129 | }); 130 | 131 | // register a callback function with the AppDispatcher 132 | // that will respond to the IdeaConstants listed below 133 | app.AppDispatcher.register(function (payload) { 134 | var action = payload.action; 135 | var name; 136 | 137 | switch (action.actionType) { 138 | case app.IdeaConstants.IDEA_CREATE: 139 | name = action.name.trim(); 140 | 141 | if (name !== '') { 142 | app.IdeaStore.create(action.room_id, name); 143 | } 144 | break; 145 | case app.IdeaConstants.IDEA_EDIT: 146 | if(action.idea.name !== '') { 147 | app.IdeaStore.edit(action.idea); 148 | } 149 | break; 150 | case app.IdeaConstants.IDEA_DELETE: 151 | if(action.idea.id !== '') { 152 | app.IdeaStore.delete(action.idea); 153 | } 154 | break; 155 | case app.PageConstants.GETROOMDATA: 156 | if (action.room_id){ 157 | app.IdeaStore.get(action.room_id); 158 | } 159 | break; 160 | default: 161 | return true; 162 | } 163 | }); 164 | -------------------------------------------------------------------------------- /client/app/stores/CommentStore.js: -------------------------------------------------------------------------------- 1 | app.CommentStore = _.extend({}, EventEmitter.prototype, { 2 | _comments: [], 3 | 4 | _room: function() { 5 | return app.PageStore.currentRoute.props; 6 | }, 7 | 8 | getAll: function (idea_id) { 9 | if (!idea_id) return this._comments; 10 | return _(this._comments).filter(function (comment) { 11 | return comment.idea === idea_id; 12 | }); 13 | }, 14 | 15 | //ajax requests 16 | //TODO: DRY out this code 17 | 18 | get: function (room_id) { 19 | $.ajax({ 20 | type: 'GET', 21 | url: '/comments/' + room_id, 22 | }) 23 | .done(function (comments) { 24 | this._comments = comments; 25 | // broadcast that _ideas has changed 26 | this.emitChange(); 27 | }.bind(this)) 28 | .fail(function(error) { 29 | console.error(error); 30 | }); 31 | 32 | socket.on('comment-change', function(currentComments) { 33 | this._comments = currentComments; 34 | this.emitChange(); 35 | }.bind(this)); 36 | }, 37 | 38 | all: function () { 39 | $.ajax({ 40 | type: 'GET', 41 | url: '/comments' 42 | }) 43 | .done(function (comments) { 44 | this._comments = comments; 45 | this.emitChange(); 46 | }.bind(this)) 47 | .fail(function (error) { 48 | console.log(error); 49 | }); 50 | 51 | socket.on('comment-change', function(currentComments) { 52 | this._comments = currentComments; 53 | this.emitChange(); 54 | }.bind(this)); 55 | }, 56 | 57 | create: function (idea_id, name) { 58 | $.ajax({ 59 | type: 'POST', 60 | url: '/comments/' + idea_id, 61 | data: { 62 | name: name 63 | } 64 | }) 65 | .done(function (comment) { 66 | this._comments.push(comment); 67 | 68 | // broadcast that _comments has changed 69 | socket.emit('comment-change', this._comments, this._room()); 70 | this.emitChange(); 71 | }.bind(this)) 72 | .fail(function (error) { 73 | console.log(error); 74 | }); 75 | }, 76 | 77 | edit: function (_id, name) { 78 | $.ajax({ 79 | type: 'PUT', 80 | url: '/comments/' + _id, 81 | data: { 82 | name: name 83 | } 84 | }) 85 | .done(function (commentEdit) { 86 | //find matching comment and update it 87 | this._comments.forEach(function (comment) { 88 | if (comment._id === commentEdit._id) { 89 | comment.name = commentEdit.name; 90 | } 91 | }.bind(this)); 92 | 93 | // broadcast that _comments has changed 94 | socket.emit('comment-change', this._comments, this._room()); 95 | this.emitChange(); 96 | }.bind(this)) 97 | .fail(function (error) { 98 | console.log(error); 99 | }); 100 | }, 101 | 102 | delete: function (_id) { 103 | $.ajax({ 104 | type: 'DELETE', 105 | url: '/comments/' + _id 106 | }) 107 | .done(function (oldComment) { 108 | //look through comments and splice out comment 109 | this._comments.forEach(function (comment, i) { 110 | if (comment._id === oldComment._id) { 111 | this._comments.splice(i, 1); 112 | } 113 | }.bind(this)); 114 | 115 | // broadcast that _comments has changed 116 | socket.emit('comment-change', this._comments, this._room()); 117 | this.emitChange(); 118 | }.bind(this)) 119 | .fail(function (error) { 120 | console.log(error); 121 | }); 122 | }, 123 | 124 | emitChange: function () { 125 | this.emit(CHANGE_EVENT); 126 | }, 127 | 128 | addChangeListener: function (callback) { 129 | this.on(CHANGE_EVENT, callback); 130 | }, 131 | 132 | removeChangeListener: function (callback) { 133 | this.removeListener(CHANGE_EVENT, callback); 134 | } 135 | 136 | }); 137 | 138 | app.AppDispatcher.register(function (payload) { 139 | var action = payload.action; 140 | var _id; 141 | var idea_id; 142 | var name; 143 | 144 | switch (action.actionType) { 145 | case app.CommentConstants.COMMENT_GET: 146 | app.CommentStore.all(); 147 | break; 148 | 149 | case app.CommentConstants.COMMENT_CREATE: 150 | idea_id = action.idea_id; 151 | name = action.name.trim(); 152 | 153 | if (name !== '') { 154 | app.CommentStore.create(idea_id, name); 155 | } 156 | break; 157 | 158 | case app.CommentConstants.COMMENT_EDIT: 159 | _id = action._id; 160 | name = action.name.trim(); 161 | 162 | if (name !== '') { 163 | app.CommentStore.edit(_id, name); 164 | } 165 | break; 166 | 167 | case app.CommentConstants.COMMENT_DELETE: 168 | _id = action._id; 169 | 170 | app.CommentStore.delete(_id); 171 | break; 172 | 173 | case app.PageConstants.GETROOMDATA: 174 | if (action.room_id){ 175 | app.CommentStore.get(action.room_id); 176 | } 177 | break; 178 | 179 | default: 180 | return true; 181 | } 182 | 183 | }); 184 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## General Workflow 4 | 5 | 1. Fork the repo 6 | 1. Cut a namespaced feature branch from master 7 | - bug/... 8 | - feat/... 9 | - test/... 10 | - doc/... 11 | - refactor/... 12 | 1. Make commits to your feature branch. Try to keep the number of commits to a minimum. Follow the existing pattern for commmit naming. 13 | 1. When you've finished with your fix or feature, Rebase upstream changes into your branch. submit a [pull request][] 14 | directly to master. Include a description of your changes. 15 | 1. Your pull request will be reviewed by another maintainer. The point of code 16 | reviews is to help keep the codebase clean and of high quality and, equally 17 | as important, to help you grow as a programmer. If your code reviewer 18 | requests you make a change you don't understand, ask them why. 19 | 1. Fix any issues raised by your code reviewer, and push your fixes as a single 20 | new commit. 21 | 1. Once the pull request has been reviewed, it will be merged by another member of the team. Do not merge your own commits. 22 | 23 | ## Detailed Workflow 24 | 25 | ### Fork the repo 26 | 27 | Use github’s interface to make a fork of the repo, then add that repo as an upstream remote: 28 | 29 | ``` 30 | git remote add upstream https://github.com/hackreactor-labs/.git 31 | ``` 32 | 33 | ### Cut a namespaced feature branch from master 34 | 35 | Your branch should follow this naming convention: 36 | - bug/... 37 | - feat/... 38 | - test/... 39 | - doc/... 40 | - refactor/... 41 | 42 | These commands will help you do this: 43 | 44 | ``` bash 45 | 46 | # Creates your branch and brings you there 47 | git checkout -b `your-branch-name` 48 | ``` 49 | 50 | ### Make commits to your feature branch. 51 | 52 | Prefix each commit like so 53 | - (feat) Added a new feature 54 | - (fix) Fixed inconsistent tests [Fixes #0] 55 | - (refactor) ... 56 | - (cleanup) ... 57 | - (test) ... 58 | - (doc) ... 59 | 60 | Make changes and commits on your branch, and make sure that you 61 | only make changes that are relevant to this branch. If you find 62 | yourself making unrelated changes, make a new branch for those 63 | changes. 64 | 65 | #### Commit Message Guidelines 66 | 67 | - Commit messages should be written in the present tense; e.g. "Fix continuous 68 | integration script.". 69 | - The first line of your commit message should be a brief summary of what the 70 | commit changes. Aim for about 70 characters max. Remember: This is a summary, 71 | not a detailed description of everything that changed. 72 | - If you want to explain the commit in more depth, following the first line should 73 | be a blank line and then a more detailed description of the commit. This can be 74 | as detailed as you want, so dig into details here and keep the first line short. 75 | 76 | ### Rebase upstream changes into your branch 77 | 78 | Once you are done making changes, you can begin the process of getting 79 | your code merged into the main repo. Step 1 is to rebase upstream 80 | changes to the master branch into yours by running this command 81 | from your branch: 82 | 83 | ```bash 84 | git pull --rebase upstream master 85 | ``` 86 | 87 | This will start the rebase process. You must commit all of your changes 88 | before doing this. If there are no conflicts, this should just roll all 89 | of your changes back on top of the changes from upstream, leading to a 90 | nice, clean, linear commit history. 91 | 92 | If there are conflicting changes, git will start yelling at you part way 93 | through the rebasing process. Git will pause rebasing to allow you to sort 94 | out the conflicts. You do this the same way you solve merge conflicts, 95 | by checking all of the files git says have been changed in both histories 96 | and picking the versions you want. Be aware that these changes will show 97 | up in your pull request, so try and incorporate upstream changes as much 98 | as possible. 99 | 100 | You pick a file by `git add`ing it - you do not make commits during a 101 | rebase. 102 | 103 | Once you are done fixing conflicts for a specific commit, run: 104 | 105 | ```bash 106 | git rebase --continue 107 | ``` 108 | 109 | This will continue the rebasing process. Once you are done fixing all 110 | conflicts you should run the existing tests to make sure you didn’t break 111 | anything, then run your new tests (there are new tests, right?) and 112 | make sure they work also. 113 | 114 | If rebasing broke anything, fix it, then repeat the above process until 115 | you get here again and nothing is broken and all the tests pass. 116 | 117 | ### Make a pull request 118 | 119 | Make a clear pull request from your fork and branch to the upstream master 120 | branch, detailing exactly what changes you made and what feature this 121 | should add. The clearer your pull request is the faster you can get 122 | your changes incorporated into this repo. 123 | 124 | At least one other person MUST give your changes a code review, and once 125 | they are satisfied they will merge your changes into upstream. Alternatively, 126 | they may have some requested changes. You should make more commits to your 127 | branch to fix these, then follow this process again from rebasing onwards. 128 | 129 | Once you get back here, make a comment requesting further review and 130 | someone will look at your code again. If they like it, it will get merged, 131 | else, just repeat again. 132 | 133 | Thanks for contributing! 134 | 135 | ### Guidelines 136 | 137 | 1. Uphold the current code standard: 138 | - Keep your code [DRY][]. 139 | - Apply the [boy scout rule][]. 140 | - Follow [STYLE-GUIDE.md](STYLE-GUIDE.md) 141 | 1. Run the [tests][] before submitting a pull request. 142 | 1. Tests are very, very important. Submit tests if your pull request contains 143 | new, testable behavior. 144 | 1. Your pull request is comprised of a single ([squashed][]) commit. 145 | 146 | ## Checklist: 147 | 148 | This is just to help you organize your process 149 | 150 | - [ ] Did I cut my work branch off of master (don't cut new branches from existing feature branches)? 151 | - [ ] Did I follow the correct naming convention for my branch? 152 | - [ ] Is my branch focused on a single main change? 153 | - [ ] Do all of my changes directly relate to this change? 154 | - [ ] Did I rebase the upstream master branch after I finished all my 155 | work? 156 | - [ ] Did I write a clear pull request message detailing what changes I made? 157 | - [ ] Did I get a code review? 158 | - [ ] Did I make any requested changes from that code review? 159 | 160 | If you follow all of these guidelines and make good changes, you should have 161 | no problem getting your changes merged in. 162 | 163 | 164 | 165 | [style guide]: https://github.com/hackreactor-labs/style-guide 166 | [n-queens]: https://github.com/hackreactor-labs/n-queens 167 | [Underbar]: https://github.com/hackreactor-labs/underbar 168 | [curriculum workflow diagram]: http://i.imgur.com/p0e4tQK.png 169 | [cons of merge]: https://f.cloud.github.com/assets/1577682/1458274/1391ac28-435e-11e3-88b6-69c85029c978.png 170 | [Bookstrap]: https://github.com/hackreactor/bookstrap 171 | [Taser]: https://github.com/hackreactor/bookstrap 172 | [tools workflow diagram]: http://i.imgur.com/kzlrDj7.png 173 | [Git Flow]: http://nvie.com/posts/a-successful-git-branching-model/ 174 | [GitHub Flow]: http://scottchacon.com/2011/08/31/github-flow.html 175 | [Squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html 176 | -------------------------------------------------------------------------------- /STYLE-GUIDE.md: -------------------------------------------------------------------------------- 1 | ### Indentation 2 | 3 | When writing any block of code that is logically subordinate to the line immediately before and after it, that block should be indented two spaces more than the surrounding lines 4 | 5 | * Do not put any tab characters anywhere in your code. You would do best to stop pressing the tab key entirely. 6 | * Increase the indent level for all blocks by two extra spaces 7 | * When a line opens a block, the next line starts 2 spaces further in than the line that opened 8 | 9 | ```javascript 10 | // good: 11 | if(condition){ 12 | action(); 13 | } 14 | 15 | // bad: 16 | if(condition){ 17 | action(); 18 | } 19 | ``` 20 | 21 | * When a line closes a block, that line starts at the same level as the line that opened the block 22 | ```javascript 23 | // good: 24 | if(condition){ 25 | action(); 26 | } 27 | 28 | // bad: 29 | if(condition){ 30 | action(); 31 | } 32 | ``` 33 | 34 | * No two lines should ever have more or less than 2 spaces difference in their indentation. Any number of mistakes in the above rules could lead to this, but one example would be: 35 | 36 | ```javascript 37 | // bad: 38 | transmogrify({ 39 | a: { 40 | b: function(){ 41 | } 42 | }}); 43 | ``` 44 | 45 | * use sublime's arrow collapsing as a guide. do the collapsing lines seem like they should be 'contained' by the line with an arrow on it? 46 | 47 | 48 | ### Variable names 49 | 50 | * A single descriptive word is best. 51 | 52 | ```javascript 53 | // good: 54 | var animals = ['cat', 'dog', 'fish']; 55 | 56 | // bad: 57 | var targetInputs = ['cat', 'dog', 'fish']; 58 | ``` 59 | 60 | * Collections such as arrays and maps should have plural noun variable names. 61 | 62 | ```javascript 63 | // good: 64 | var animals = ['cat', 'dog', 'fish']; 65 | 66 | // bad: 67 | var animalList = ['cat', 'dog', 'fish']; 68 | 69 | // bad: 70 | var animal = ['cat', 'dog', 'fish']; 71 | ``` 72 | 73 | * Name your variables after their purpose, not their structure 74 | 75 | ```javascript 76 | // good: 77 | var animals = ['cat', 'dog', 'fish']; 78 | 79 | // bad: 80 | var array = ['cat', 'dog', 'fish']; 81 | ``` 82 | 83 | 84 | ### Language constructs 85 | 86 | * Do not use `for...in` statements with the intent of iterating over a list of numeric keys. Use a for-with-semicolons statement in stead. 87 | 88 | ```javascript 89 | // good: 90 | var list = ['a', 'b', 'c'] 91 | for(var i = 0; i < list.length; i++){ 92 | alert(list[i]); 93 | } 94 | 95 | // bad: 96 | var list = ['a', 'b', 'c'] 97 | for(var i in list){ 98 | alert(list[i]); 99 | } 100 | ``` 101 | 102 | * Never omit braces for statement blocks (although they are technically optional). 103 | ```javascript 104 | // good: 105 | for(key in object){ 106 | alert(key); 107 | } 108 | 109 | // bad: 110 | for(key in object) 111 | alert(key); 112 | ``` 113 | 114 | * Always use `===` and `!==`, since `==` and `!=` will automatically convert types in ways you're unlikely to expect. 115 | 116 | ```javascript 117 | // good: 118 | 119 | // this comparison evaluates to false, because the number zero is not the same as the empty string. 120 | if(0 === ''){ 121 | alert('looks like they\'re equal'); 122 | } 123 | 124 | // bad: 125 | 126 | // This comparison evaluates to true, because after type coercion, zero and the empty string are equal. 127 | if(0 == ''){ 128 | alert('looks like they\'re equal'); 129 | } 130 | ``` 131 | 132 | * Don't use function statements for the entire first half of the course. They introduce a slew of subtle new rules to how the language behaves, and without a clear benefit. Once you and all your peers are expert level in the second half, you can start to use the more (needlessly) complicated option if you like. 133 | 134 | ```javascript 135 | // good: 136 | var go = function(){...}; 137 | 138 | // bad: 139 | function stop(){...}; 140 | ``` 141 | 142 | 143 | ### Semicolons 144 | 145 | * Don't forget semicolons at the end of lines 146 | 147 | ```javascript 148 | // good: 149 | alert('hi'); 150 | 151 | // bad: 152 | alert('hi') 153 | ``` 154 | 155 | * Semicolons are not required at the end of statements that include a block--i.e. `if`, `for`, `while`, etc. 156 | 157 | 158 | ```javascript 159 | // good: 160 | if(condition){ 161 | response(); 162 | } 163 | 164 | // bad: 165 | if(condition){ 166 | response(); 167 | }; 168 | ``` 169 | 170 | * Misleadingly, a function may be used at the end of a normal assignment statement, and would require a semicolon (even though it looks rather like the end of some statement block). 171 | 172 | ```javascript 173 | // good: 174 | var greet = function(){ 175 | alert('hi'); 176 | }; 177 | 178 | // bad: 179 | var greet = function(){ 180 | alert('hi'); 181 | } 182 | ``` 183 | 184 | # Supplemental reading 185 | 186 | ### Code density 187 | 188 | * Conserve line quantity by minimizing the number lines you write in. The more concisely your code is written, the more context can be seen in one screen. 189 | * Conserve line length by minimizing the amount of complexity you put on each line. Long lines are difficult to read. Rather than a character count limit, I recommend limiting the amount of complexity you put on a single line. Try to make it easily read in one glance. This goal is in conflict with the line quantity goal, so you must do your best to balance them. 190 | 191 | ### Comments 192 | 193 | * Provide comments any time you are confident it will make reading your code easier. 194 | * Be aware that comments come at some cost. They make a file longer and can drift out of sync with the code they annotate. 195 | * Comment on what code is attempting to do, not how it will achieve it. 196 | * A good comment is often less effective than a good variable name. 197 | 198 | 199 | ### Padding & additional whitespace 200 | 201 | * Generally, we don't care where you put extra spaces, provided they are not distracting. 202 | * You may use it as padding for visual clarity. If you do though, make sure it's balanced on both sides. 203 | 204 | ```javascript 205 | // optional: 206 | alert( "I chose to put visual padding around this string" ); 207 | 208 | // bad: 209 | alert( "I only put visual padding on one side of this string"); 210 | ``` 211 | 212 | * You may use it to align two similar lines, but it is not recommended. This pattern usually leads to unnecessary edits of many lines in your code every time you change a variable name. 213 | 214 | ```javascript 215 | // discouraged: 216 | var firstItem = getFirst (); 217 | var secondItem = getSecond(); 218 | ``` 219 | 220 | * Put `else` and `else if` statements on the same line as the ending curly brace for the preceding `if` block 221 | ```javascript 222 | // good: 223 | if(condition){ 224 | response(); 225 | }else{ 226 | otherResponse(); 227 | } 228 | 229 | // bad: 230 | if(condition){ 231 | response(); 232 | } 233 | else{ 234 | otherResponse(); 235 | } 236 | ``` 237 | 238 | 239 | 240 | ### Working with files 241 | 242 | * Do not end a file with any character other than a newline. 243 | * Don't use the -a or -m flags for `git commit` for the first half of the class, since they conceal what is actually happening (and do slightly different things than most people expect). 244 | 245 | ```shell 246 | # good: 247 | > git add . 248 | > git commit 249 | [save edits to the commit message file using the text editor that opens] 250 | 251 | # bad: 252 | > git commit -a 253 | [save edits to the commit message file using the text editor that opens] 254 | 255 | # bad: 256 | > git add . 257 | > git commit -m "updated algorithm" 258 | ``` 259 | 260 | 261 | ### Opening or closing too many blocks at once 262 | 263 | * The more blocks you open on a single line, the more your reader needs to remember about the context of what they are reading. Try to resolve your blocks early, and refactor. A good rule is to avoid closing more than two blocks on a single line--three in a pinch. 264 | 265 | ```javascript 266 | // avoid: 267 | _.ajax(url, {success: function(){ 268 | // ... 269 | }}); 270 | 271 | // prefer: 272 | _.ajax(url, { 273 | success: function(){ 274 | // ... 275 | } 276 | }); 277 | ``` 278 | 279 | 280 | ### Variable declaration 281 | 282 | * Use a new var statement for each line you declare a variable on. 283 | * Do not break variable declarations onto multiple lines. 284 | * Use a new line for each variable declaration. 285 | * See http://benalman.com/news/2012/05/multiple-var-statements-javascript/ for more details 286 | 287 | ```javascript 288 | // good: 289 | var ape; 290 | var bat; 291 | 292 | // bad: 293 | var cat, 294 | dog 295 | 296 | // use sparingly: 297 | var eel, fly; 298 | ``` 299 | 300 | ### Capital letters in variable names 301 | 302 | * Some people choose to use capitalization of the first letter in their variable names to indicate that they contain a [class](http://en.wikipedia.org/wiki/Class_(computer_science\)). This capitalized variable might contain a function, a prototype, or some other construct that acts as a representative for the whole class. 303 | * Optionally, some people use a capital letter only on functions that are written to be run with the keyword `new`. 304 | * Do not use all-caps for any variables. Some people use this pattern to indicate an intended "constant" variable, but the language does not offer true constants, only mutable variables. 305 | 306 | 307 | ### Minutia 308 | 309 | * Don't rely on JavaScripts implicit global variables. If you are intending to write to the global scope, export things to `window.*` explicitly instead. 310 | 311 | ```javascript 312 | // good: 313 | var overwriteNumber = function(){ 314 | window.exported = Math.random(); 315 | }; 316 | 317 | // bad: 318 | var overwriteNumber = function(){ 319 | exported = Math.random(); 320 | }; 321 | ``` 322 | 323 | * For lists, put commas at the end of each newline, not at the beginning of each item in a list 324 | 325 | ```javascript 326 | // good: 327 | var animals = [ 328 | 'ape', 329 | 'bat', 330 | 'cat' 331 | ]; 332 | 333 | // bad: 334 | var animals = [ 335 | 'ape' 336 | , 'bat' 337 | , 'cat' 338 | ]; 339 | ``` 340 | 341 | * Avoid use of `switch` statements altogether. They are hard to outdent using the standard whitespace rules above, and are prone to error due to missing `break` statements. See [this article](http://ericleads.com/2012/12/switch-case-considered-harmful/) for more detail. 342 | 343 | * Prefer single quotes around JavaScript strings, rather than double quotes. Having a standard of any sort is preferable to a mix-and-match approach, and single quotes allow for easy embedding of HTML, which prefers double quotes around tag attributes. 344 | 345 | ```javascript 346 | // good: 347 | var dog = 'dog'; 348 | var cat = 'cat'; 349 | 350 | // acceptable: 351 | var dog = "dog"; 352 | var cat = "cat"; 353 | 354 | // bad: 355 | var dog = 'dog'; 356 | var cat = "cat"; 357 | ``` 358 | 359 | 360 | ### HTML 361 | 362 | * Do not use ids for html elements. Use a class instead. 363 | 364 | ```html 365 | 366 | 367 | 368 | 369 | 370 | ``` 371 | 372 | * Do not include a `type=text/javascript"` attribute on script tags 373 | 374 | ```html 375 | 376 | 377 | 378 | 379 | 380 | ``` 381 | --------------------------------------------------------------------------------