├── images ├── Thumbs-Up.png └── Thumbs-Down.png ├── Dockerfile ├── server ├── config │ ├── database.json │ ├── authentication.js │ ├── middleware.js │ └── routes.js ├── server.js ├── controllers │ ├── categoryController.js │ ├── discussionController.js │ ├── voteController.js │ ├── videoController.js │ └── userController.js └── db │ └── index.js ├── .babelrc ├── client ├── components │ ├── SearchPage.jsx │ ├── SearchBar.jsx │ ├── VideoPlayer.jsx │ ├── UserInfo.jsx │ ├── VideoDurationValidater.jsx │ ├── HomePage.jsx │ ├── FeedbackTab.jsx │ ├── VotesSection.jsx │ ├── VideoGrid.jsx │ ├── DiscussionSection.jsx │ ├── PlayerPage.jsx │ ├── SignUpModal.jsx │ ├── Featured.jsx │ ├── SignInModal.jsx │ ├── ProfilePage.jsx │ ├── NavBar.jsx │ ├── Q_ATab.jsx │ ├── CategoriesBar.jsx │ └── UploadModal.jsx ├── styles │ ├── materialTheme.js │ └── style.css ├── index.html ├── App.jsx ├── reducers │ └── reducer.jsx └── actions │ └── actions.jsx ├── migrations ├── 20160325193757-unnamed-migration.js ├── 20160325194527-unnamed-migration.js ├── 20160325194725-unnamed-migration.js ├── 20160401121803-unnamed-migration.js ├── 20160325164433-unnamed-migration.js ├── 20160324184612-unnamed-migration.js ├── 20160324185018-unnamed-migration.js └── 20160322164359-unnamed-migration.js ├── .eslintrc.json ├── config └── config.json ├── .gitignore ├── webpack.config.js ├── models └── index.js ├── test ├── integrationTest.js └── reducersSpec.js └── package.json /images/Thumbs-Up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galvanizedfern/EDify/HEAD/images/Thumbs-Up.png -------------------------------------------------------------------------------- /images/Thumbs-Down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galvanizedfern/EDify/HEAD/images/Thumbs-Down.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:argon 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | EXPOSE 8000 15 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /server/config/database.json: -------------------------------------------------------------------------------- 1 | { 2 | "production":{ 3 | "host":"galvanizedfern-mysql1.cn3yqtfbbjtf.us-west-1.rds.amazonaws.com", 4 | "port":3306, 5 | "database":"thesis" 6 | }, 7 | 8 | "local":{ 9 | "host":"localhost", 10 | "port":3306, 11 | "database":"thesis" 12 | } 13 | } -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | // var db = require('./db'); 3 | var path = require('path'); 4 | 5 | var app = express(); 6 | 7 | require('./config/middleware')(app, express); 8 | require('./config/routes')(app, express); 9 | 10 | var port = Number(process.env.PORT || 8000); 11 | app.listen(port, function() { 12 | console.log(`Listening on port ${port}...`); 13 | }); 14 | 15 | module.exports = app; -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ], 7 | "env": { 8 | "development": { 9 | "plugins": [ 10 | ["react-transform", { 11 | "transforms": [{ 12 | "transform": "react-transform-hmr", 13 | "imports": [ 14 | "react", 15 | "redbox-react" 16 | ], 17 | "locals": ["module"] 18 | }] 19 | }] 20 | ] 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /client/components/SearchPage.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, {Component} from 'react'; 3 | import {connect} from 'react-redux'; 4 | import VideoGrid from './VideoGrid.jsx'; 5 | import CategoriesBar from './CategoriesBar.jsx' 6 | 7 | 8 | export default class SearchPage extends Component { 9 | 10 | render(){ 11 | return ( 12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | } 19 | export default connect ()(SearchPage); 20 | -------------------------------------------------------------------------------- /migrations/20160325193757-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.dropTable('Comments'); 6 | /* 7 | Add altering commands here. 8 | Return a promise to correctly handle asynchronicity. 9 | 10 | Example: 11 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 12 | */ 13 | }, 14 | 15 | down: function (queryInterface, Sequelize) { 16 | /* 17 | Add reverting commands here. 18 | Return a promise to correctly handle asynchronicity. 19 | 20 | Example: 21 | return queryInterface.dropTable('users'); 22 | */ 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /client/styles/materialTheme.js: -------------------------------------------------------------------------------- 1 | import Colors from 'material-ui/lib/styles/colors'; 2 | import ColorManipulator from 'material-ui/lib/utils/color-manipulator'; 3 | import Spacing from 'material-ui/lib/styles/spacing'; 4 | import zIndex from 'material-ui/lib/styles/zIndex'; 5 | 6 | export default { 7 | spacing: Spacing, 8 | zIndex: zIndex, 9 | fontFamily: 'Raleway, sans-serif', 10 | palette: { 11 | primary1Color: "#303F9F", 12 | primary2Color: "#8BC34A", 13 | primary3Color: "#DCEDC8", 14 | accentColor: "white", 15 | textColor: "#212121", 16 | alternateTextColor: "#f0f0f5", 17 | canvasColor: 'white', 18 | borderColor: '#e0e0e0', 19 | pickerHeaderColor: '#00bcd4', 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "ecmaFeatures": { 8 | "experimentalObjectRestSpread": true, 9 | "jsx": true 10 | }, 11 | "sourceType": "module" 12 | }, 13 | "plugins": [ 14 | "react" 15 | ], 16 | "rules": { 17 | "indent": [ 18 | "error", 19 | 2 20 | ], 21 | "curly": [ 22 | "error", 23 | "all" 24 | ], 25 | "quotes": [ 26 | 1, 27 | "single" 28 | ], 29 | "semi": [ 30 | "error", 31 | "always" 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /migrations/20160325194527-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.renameColumn('Videos', 'Category', 'CategoryId') 6 | /* 7 | Add altering commands here. 8 | Return a promise to correctly handle asynchronicity. 9 | 10 | Example: 11 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 12 | */ 13 | }, 14 | 15 | down: function (queryInterface, Sequelize) { 16 | /* 17 | Add reverting commands here. 18 | Return a promise to correctly handle asynchronicity. 19 | 20 | Example: 21 | return queryInterface.dropTable('users'); 22 | */ 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /migrations/20160325194725-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.renameColumn('Videos', 'Category', 'CategoryId') 6 | /* 7 | Add altering commands here. 8 | Return a promise to correctly handle asynchronicity. 9 | 10 | Example: 11 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 12 | */ 13 | }, 14 | 15 | down: function (queryInterface, Sequelize) { 16 | /* 17 | Add reverting commands here. 18 | Return a promise to correctly handle asynchronicity. 19 | 20 | Example: 21 | return queryInterface.dropTable('users'); 22 | */ 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "username": "rootPROD", 4 | "password": "passwordPROD", 5 | "database": "thesis", 6 | "host": "galvanizedfern-mysql1.cn3yqtfbbjtf.us-west-1.rds.amazonaws.com", 7 | "dialect": "mysql", 8 | "port:": 3306 9 | }, 10 | "test": { 11 | "username": "root", 12 | "password": "password", 13 | "database": "thesis", 14 | "host": "127.0.0.1", 15 | "dialect": "mysql" 16 | }, 17 | "production": { 18 | "username": "rootPROD", 19 | "password": "passwordPROD", 20 | "database": "thesis", 21 | "host": "galvanizedfern-mysql1.cn3yqtfbbjtf.us-west-1.rds.amazonaws.com", 22 | "dialect": "mysql", 23 | "port:": 3306 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /migrations/20160401121803-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.addColumn( 6 | 'Videos', 7 | 'userName', 8 | { 9 | type: Sequelize.STRING, 10 | allowNull: false 11 | } 12 | ) 13 | /* 14 | Add altering commands here. 15 | Return a promise to correctly handle asynchronicity. 16 | 17 | Example: 18 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 19 | */ 20 | }, 21 | 22 | down: function (queryInterface, Sequelize) { 23 | /* 24 | Add reverting commands here. 25 | Return a promise to correctly handle asynchronicity. 26 | 27 | Example: 28 | return queryInterface.dropTable('users'); 29 | */ 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/20160325164433-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.addColumn( 6 | 'Videos', 7 | 'Category', 8 | { 9 | type: Sequelize.INTEGER, 10 | allowNull: false, 11 | defaultValue: 0 12 | } 13 | ) 14 | /* 15 | Add altering commands here. 16 | Return a promise to correctly handle asynchronicity. 17 | 18 | Example: 19 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 20 | */ 21 | }, 22 | 23 | down: function (queryInterface, Sequelize) { 24 | /* 25 | Add reverting commands here. 26 | Return a promise to correctly handle asynchronicity. 27 | 28 | Example: 29 | return queryInterface.dropTable('users'); 30 | */ 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /migrations/20160324184612-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.addColumn( 6 | 'Videos', 7 | 'Votes', 8 | { 9 | type: Sequelize.INTEGER, 10 | allowNull: false, 11 | defaultValue: 0 12 | } 13 | ) 14 | 15 | /* 16 | Add altering commands here. 17 | Return a promise to correctly handle asynchronicity. 18 | 19 | Example: 20 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 21 | */ 22 | }, 23 | 24 | down: function (queryInterface, Sequelize) { 25 | /* 26 | Add reverting commands here. 27 | Return a promise to correctly handle asynchronicity. 28 | 29 | Example: 30 | return queryInterface.dropTable('users'); 31 | */ 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /migrations/20160324185018-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.addColumn( 6 | 'Videos', 7 | 'downVotes', 8 | { 9 | type: Sequelize.INTEGER, 10 | allowNull: false, 11 | defaultValue: 0 12 | } 13 | ) 14 | 15 | /* 16 | Add altering commands here. 17 | Return a promise to correctly handle asynchronicity. 18 | 19 | Example: 20 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 21 | */ 22 | }, 23 | 24 | down: function (queryInterface, Sequelize) { 25 | /* 26 | Add reverting commands here. 27 | Return a promise to correctly handle asynchronicity. 28 | 29 | Example: 30 | return queryInterface.dropTable('users'); 31 | */ 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /migrations/20160322164359-unnamed-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.addColumn( 6 | 'Videos', 7 | 'upVotes', 8 | { 9 | type: Sequelize.INTEGER, 10 | allowNull: false, 11 | defaultValue: 0 12 | } 13 | ) 14 | }, 15 | 16 | 17 | /* 18 | Add altering commands here. 19 | Return a promise to correctly handle asynchronicity. 20 | 21 | Example: 22 | return queryInterface.createTable('users', { id: Sequelize.INTEGER }); 23 | */ 24 | 25 | 26 | down: function (queryInterface, Sequelize) { 27 | /* 28 | Add reverting commands here. 29 | Return a promise to correctly handle asynchronicity. 30 | 31 | Example: 32 | return queryInterface.dropTable('users'); 33 | */ 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | #SampleVideos 36 | SampleVideos 37 | 38 | # Webpack Bundle + Map 39 | client/bundle.js 40 | client/bundle.js.map 41 | 42 | # AWS access keys 43 | server/config/aws-config.json 44 | -------------------------------------------------------------------------------- /client/components/SearchBar.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | export const fields = ['title', 'mentor']; 3 | import TextField from 'material-ui/lib/text-field'; 4 | import IconButton from 'material-ui/lib/icon-button'; 5 | 6 | 7 | export default class SearchBar extends Component { 8 | 9 | render() { 10 | return ( 11 |
12 | 13 | this.props.handleSubmit(this.refs.search.getValue(), "title")}>search 14 |
15 | ); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000', 7 | path.resolve(__dirname, 'client/App.jsx') 8 | ], 9 | output: { 10 | path: path.resolve(__dirname, 'client'), 11 | publicPath: 'http://localhost:8000/', 12 | filename: "bundle.js", 13 | }, 14 | devtool: '#source-map', 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | loaders: ['react-hot', 'babel'] 21 | }, 22 | { 23 | test: /\.css$/, loader: 'style-loader!css-loader' 24 | } 25 | 26 | ] 27 | }, 28 | plugins: [ 29 | new webpack.optimize.OccurenceOrderPlugin(), 30 | new webpack.HotModuleReplacementPlugin(), 31 | new webpack.NoErrorsPlugin() 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /client/components/VideoPlayer.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | 4 | export default class VideoPlayer extends Component { 5 | 6 | componentDidMount() { 7 | var video, wrapper; 8 | wrapper = document.createElement('div'); 9 | wrapper.innerHTML = ""; 10 | video = wrapper.firstChild; 11 | this.refs.target.getDOMNode().appendChild(video); 12 | return videojs(video, {}); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |
19 |
20 |

{this.props.title}

21 |
22 |
23 |
24 |
25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /server/controllers/categoryController.js: -------------------------------------------------------------------------------- 1 | var db = require('../db'); 2 | 3 | // initialize table with all categories 4 | // db.Category.bulkCreate([ 5 | // {name: 'Arts'}, 6 | // {name: 'Literature'}, 7 | // {name: 'Languages'}, 8 | // {name: 'History'}, 9 | // {name: 'Music'}, 10 | // {name: 'Philosophy'}, 11 | // {name: 'Medicine'}, 12 | // {name: 'Science'}, 13 | // {name: 'Engineering'}, 14 | // {name: 'Mathematics'}, 15 | // {name: 'Cooking'}, 16 | // {name: 'Business'}, 17 | // {name: 'Education'}, 18 | // {name: 'Sports'}, 19 | // {name: 'Other'}, 20 | // ]) 21 | // .then(function() { 22 | // return db.Category.findAll(); 23 | // }) 24 | // .then(function(categories) { 25 | // console.log(categories); 26 | // }); 27 | 28 | module.exports = { 29 | loadCategories: function(req, res) { 30 | db.Category.findAll() 31 | .then(function(categories) { 32 | res.send(categories); 33 | }) 34 | .catch(function(err) { 35 | throw err; 36 | res.sendstatus(500); 37 | }); 38 | } 39 | }; -------------------------------------------------------------------------------- /client/components/UserInfo.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | export default function UserInfo ({user, updateUserInfo}) { 5 | 6 | 7 | 8 | var aboutMeEdit =
9 | 10 | 13 |
14 | var aboutMe =
updateUserInfo(null,user) }>hey {user.aboutMe}
15 | return ( 16 |
17 |
Welcome Back, {user.username}
18 |
19 |
20 | {user.aboutMeEdit ? aboutMeEdit : aboutMe} 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EDify 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var basename = path.basename(module.filename); 7 | var env = 'production' || process.env.NODE_ENV ; 8 | var config = require(__dirname + '/../config/config.json')[env]; 9 | var db = {}; 10 | 11 | if (config.use_env_variable) { 12 | var sequelize = new Sequelize(process.env[config.use_env_variable]); 13 | } else { 14 | var sequelize = new Sequelize(config.database, config.username, config.password, config); 15 | 16 | } 17 | 18 | fs 19 | .readdirSync(__dirname) 20 | .filter(function(file) { 21 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 22 | }) 23 | .forEach(function(file) { 24 | var model = sequelize['import'](path.join(__dirname, file)); 25 | db[model.name] = model; 26 | }); 27 | 28 | Object.keys(db).forEach(function(modelName) { 29 | if (db[modelName].associate) { 30 | db[modelName].associate(db); 31 | } 32 | }); 33 | 34 | db.sequelize = sequelize; 35 | db.Sequelize = Sequelize; 36 | 37 | module.exports = db; 38 | -------------------------------------------------------------------------------- /test/integrationTest.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | // var assert = require('assert'); 3 | // var express = require('express'); 4 | var app = require('../server/server.js'); 5 | // var expect = require('chai').expect; 6 | 7 | // describe('GET', function () { 8 | // it('responds with a 200 (OK)', function (done) { 9 | // request(app) 10 | // .get('/fetch') 11 | // .expect(200,done) 12 | // // .end(done) 13 | // }); 14 | // }); 15 | 16 | it('respond with json', function(done){ 17 | this.timeout(30000) 18 | request(app) 19 | .get('/fetch') 20 | .set('Accept', 'application/json') 21 | .expect(200) 22 | .end(function(err, res){ 23 | if (err) { 24 | console.log(err) 25 | } 26 | done() 27 | }); 28 | }); 29 | 30 | 31 | describe('GET /loadCategories', function(){ 32 | it('get request to loadCategories', function(done){ 33 | request(app) 34 | .get('/loadCategories') 35 | .set('Accept', 'application/json') 36 | .expect(200, done) 37 | .end(function(err, res){ 38 | if (err) { 39 | console.log(err) 40 | } 41 | done() 42 | }); 43 | 44 | }); 45 | }); -------------------------------------------------------------------------------- /client/components/VideoDurationValidater.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import $ from 'jquery'; 3 | 4 | export default class VideoDurationValidater extends Component { 5 | 6 | componentDidMount() { 7 | var stopVideoDurationCheck = this.props.stopVideoDurationCheck; 8 | var filename = this.props.filename; 9 | var videoValidatedTrue = this.props.videoValidatedTrue; 10 | var videoValidatedFalse = this.props.videoValidatedFalse; 11 | 12 | var video, wrapper; 13 | wrapper = document.createElement('div'); 14 | wrapper.innerHTML = ""; 15 | video = wrapper.firstChild; 16 | this.refs.target.getDOMNode().appendChild(video); 17 | var player = videojs(video, {}, function() { 18 | this.on('loadedmetadata', function() { 19 | if(this.duration() > 300) { 20 | $.post('/deleteVideoFromBucket', {filename: filename}) 21 | .done(function() { 22 | videoValidatedFalse(); 23 | }); 24 | } else { 25 | videoValidatedTrue() 26 | } 27 | stopVideoDurationCheck(); 28 | }); 29 | }); 30 | } 31 | 32 | render() { 33 | return ( 34 | 35 |
36 |

Validating video length

37 |
38 |
39 | 40 | ); 41 | } 42 | } -------------------------------------------------------------------------------- /server/config/authentication.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var Strategy = require('passport-local').Strategy; 3 | var db = require('../db'); 4 | var bcrypt = require('bcrypt'); 5 | 6 | // Uses 'passport-local' pre-made strategy to handle standard username and password verification 7 | passport.use(new Strategy( 8 | function(username, password, done) { 9 | db.User.findOne({where: {username: username}}) 10 | .then(function(user) { 11 | if (!user) { 12 | return done(null, false, {message: 'Username incorrect'}); 13 | } 14 | bcrypt.compare(password, user.password, function(err, match) { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (!match) { 19 | return done(null, false, {message: 'Password incorrect'}); 20 | } else { 21 | return done(null, user); 22 | } 23 | }); 24 | }) 25 | .catch(function(err) { 26 | return done(err); 27 | }); 28 | } 29 | )); 30 | 31 | // Handles session stuff (still not entirely sure how exactly) 32 | passport.serializeUser(function(user, done) { 33 | done(null, user.id); 34 | }); 35 | 36 | // ^^ Same as above ^^ 37 | passport.deserializeUser(function(id, done) { 38 | db.User.findOne({where: {id: id}}) 39 | .then(function(user) { 40 | done(null, user); 41 | }) 42 | .catch(function(err) { 43 | return done(err); 44 | }); 45 | }); 46 | 47 | // Checks to make sure user is logged in before allowing route request to continue 48 | var ensureAuthenticated = function(req, res, next) { 49 | if (req.session.userId) { 50 | return next(); 51 | } else { 52 | res.sendStatus(401); 53 | } 54 | }; 55 | 56 | module.exports.passport = passport; 57 | module.exports.ensureAuthenticated = ensureAuthenticated; 58 | -------------------------------------------------------------------------------- /client/components/HomePage.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Featured from './Featured.jsx'; 4 | import VideoGrid from './VideoGrid.jsx'; 5 | import CategoriesBar from './CategoriesBar.jsx'; 6 | import { fetchVideoList, loadCategories } from '../actions/actions.jsx'; 7 | import $ from 'jquery'; 8 | 9 | //Component Code 10 | export class HomePage extends Component { 11 | 12 | componentDidMount(){ 13 | this.props.fetchVideos(); 14 | this.props.fetchCategories(); 15 | } 16 | 17 | render(){ 18 | var durationCheck =

CHECKING DURATION !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

19 | console.log(window.location) 20 | 21 | //this.props.fetchVideos(); 22 | return ( 23 |
24 |
25 |

Learn something new
in five minutes or less!
26 | 27 |
28 | 29 |
30 | {this.props.checkVideoDuration === true ? durationCheck : ''} 31 |
32 | ); 33 | } 34 | } 35 | 36 | //Container Code 37 | const mapStateToProps = (state) => { 38 | return { 39 | videos: state.videos, 40 | checkVideoDuration: state.checkVideoDuration 41 | } 42 | }; 43 | 44 | const mapDispatchToProps = (dispatch) => { 45 | return { 46 | fetchVideos: () => { 47 | $.get('/fetch') 48 | .done(function(res){ 49 | dispatch(fetchVideoList(res)); 50 | }); 51 | }, 52 | fetchCategories: () => { 53 | dispatch(loadCategories()) 54 | } 55 | }; 56 | }; 57 | 58 | export default connect( 59 | mapStateToProps, 60 | mapDispatchToProps 61 | )(HomePage); 62 | 63 | 64 | -------------------------------------------------------------------------------- /server/config/middleware.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var morgan = require('morgan'); 3 | var bodyParser = require('body-parser'); 4 | var passport = require('passport'); 5 | var session = require('express-session'); 6 | var webpack = require('webpack'); 7 | var webpackConfig = require(process.env.WEBPACK_CONFIG ? process.env.WEBPACK_CONFIG : '../../webpack.config'); 8 | var compiler = webpack(webpackConfig); 9 | var request = require('supertest'); 10 | 11 | 12 | // Gets the proper credentials (hidden in file aws-config.json) for connecting to our S3 bucket 13 | var AWS = require('aws-sdk'); 14 | AWS.config.loadFromPath(__dirname + '/aws-config.json'); 15 | 16 | module.exports = function(app, express) { 17 | 18 | app.use(morgan('dev')); 19 | 20 | app.use(bodyParser.urlencoded({extended: true})); 21 | app.use(bodyParser.json()); 22 | 23 | app.use(require('webpack-dev-middleware')(compiler, { 24 | inline: true, 25 | hot: true, 26 | noInfo: true, 27 | publicPath: webpackConfig.output.publicPath 28 | })); 29 | 30 | app.use(require('webpack-hot-middleware')(compiler, { 31 | log: console.log, 32 | path: '/__webpack_hmr', heartbeat: 10 * 1000 33 | })); 34 | 35 | //app.use(express.static(path.resolve(__dirname, '../../client'))); 36 | 37 | app.use(function (req, res, next) { 38 | res.header("Access-Control-Allow-Origin", "*"); 39 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 40 | next(); 41 | }); 42 | 43 | app.use(express.static(__dirname + '/../../client')); 44 | app.use(session({ secret: 'galvanized fern' })); 45 | app.use(passport.initialize()); 46 | app.use(passport.session()); 47 | 48 | // Essentially requests access to the S3 bucket so that a video may be uploaded directly from the frontend to S3 49 | app.use('/s3', require('react-s3-uploader/s3router')({ 50 | bucket: "video.bucket1", 51 | headers: {'Access-Control-Allow-Origin': '*'}, // optional 52 | region: 'us-west-1', //optional 53 | ACL: 'private' // this is default 54 | })); 55 | }; 56 | -------------------------------------------------------------------------------- /client/components/FeedbackTab.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import TextField from 'material-ui/lib/text-field'; 3 | import RaisedButton from 'material-ui/lib/raised-button'; 4 | 5 | 6 | export default class FeedbackTab extends Component { 7 | 8 | render() { 9 | var feedbackFound; 10 | var feedback = this.props.feedback; 11 | var addFeedback = this.props.addFeedback; 12 | var videoid = this.props.currentVideo.id; 13 | var username = this.props.currentUser.username; 14 | var userid = this.props.currentUser.id; 15 | console.log(feedback); 16 | 17 | 18 | // Feedback is initially an empty object (before becoming an array of 19 | // feedback objects) so it must be verified as an array before trying to map it 20 | if (Array.isArray(feedback)) { 21 | feedbackFound = ( 22 |
23 | {feedback.map(function (singleFeedback){ 24 | return( 25 |
26 |
27 |

{singleFeedback.username}

28 |

{singleFeedback.feedback}

29 |
30 | ); 31 | })} 32 |
33 | ); 34 | } 35 | 36 | return ( 37 |
38 | 46 |
47 | addFeedback(this.refs.feedback.getValue(), username, videoid, userid)}/> 48 | {feedbackFound} 49 |
50 | ) 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /client/components/VotesSection.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { upVoteMore, downVoteMore } from '../actions/actions.jsx' 4 | import TextField from 'material-ui/lib/text-field'; 5 | import RaisedButton from 'material-ui/lib/raised-button'; 6 | import $ from 'jquery'; 7 | 8 | 9 | //Component Code 10 | export default class VotesSection extends Component { 11 | 12 | render() { 13 | return ( 14 |
15 | this.props.upVote(this.props.user.id, this.props.currentVideo.id)}/> 16 | {this.props.currentVideo.upVotes} 17 | this.props.downVote(this.props.user.id, this.props.currentVideo.id)}/> 18 | {this.props.currentVideo.downVotes} 19 |
20 | ); 21 | }; 22 | } 23 | 24 | //Container Code 25 | const mapStateToProps = (state) => { 26 | return { 27 | user: state.user, 28 | currentVideo: state.currentVideo 29 | } 30 | }; 31 | 32 | const mapDispatchToProps = (dispatch) => { 33 | return { 34 | upVote: (userID, videoID) => { 35 | var vote = { 36 | userID: userID, 37 | videoID:videoID 38 | }; 39 | $.post('/upVote', vote) 40 | .then((voteCount) => 41 | { 42 | console.log("from container: ",voteCount.upVotes); 43 | dispatch(upVoteMore(voteCount.upVotes)); 44 | }); 45 | }, 46 | downVote: (userID, videoID) => { 47 | var vote = { 48 | userID: userID, 49 | videoID:videoID 50 | }; 51 | $.post('/downVote', vote) 52 | .then((voteCount) => 53 | { 54 | console.log("from container: ",voteCount.downVotes); 55 | dispatch(downVoteMore(voteCount.downVotes)); 56 | }); 57 | } 58 | } 59 | }; 60 | 61 | export default connect( 62 | mapStateToProps, 63 | mapDispatchToProps 64 | )(VotesSection); -------------------------------------------------------------------------------- /server/controllers/discussionController.js: -------------------------------------------------------------------------------- 1 | var db = require('../db'); 2 | 3 | module.exports = { 4 | loadFeedback: function(req, res) { 5 | var videoid = req.body.videoid; 6 | db.Feedback.findAll({where: {videoID: videoid}}) 7 | .then(function(feedback) { 8 | res.send(200, feedback); 9 | }) 10 | .catch(function(err) { 11 | throw err; 12 | res.sendStatus(500); 13 | }); 14 | }, 15 | 16 | addFeedback: function(req, res) { 17 | var feedback = req.body.feedback; 18 | var videoID = req.body.videoID; 19 | var userID = req.body.userID; 20 | var username = req.body.username; 21 | db.Feedback.create({feedback: feedback, username: username, VideoId: videoID, UserId: userID}) 22 | .then(function() { 23 | res.sendStatus(201); 24 | }) 25 | .catch(function(err) { 26 | throw err; 27 | res.sendStatus(500); 28 | }); 29 | }, 30 | 31 | loadQuestions: function(req, res) { 32 | var videoid = req.body.videoid; 33 | db.Question.findAll({where: {videoID: videoid}}) 34 | .then(function(questions) { 35 | res.send(200, questions); 36 | }) 37 | .catch(function(err) { 38 | throw err; 39 | res.sendStatus(500); 40 | }); 41 | }, 42 | 43 | addQuestion: function(req, res) { 44 | var question = req.body.content; 45 | var asker = req.body.asker; 46 | var videoID = req.body.videoID; 47 | var userID = req.body.userID; 48 | db.Question.create({question: question, asker: asker, VideoId: videoID, UserId: userID}) 49 | .then(function() { 50 | res.sendStatus(201); 51 | }) 52 | .catch(function(err) { 53 | throw err; 54 | res.sendStatus(500); 55 | }); 56 | }, 57 | 58 | addAnswer: function(req, res) { 59 | var answer = req.body.answer; 60 | var questionId = req.body.questionID; 61 | db.Question.findOne({where: {id: questionId}}) 62 | .then(function(question) { 63 | question.update({answer: answer}) 64 | .then(function() { 65 | res.sendStatus(200); 66 | }) 67 | .catch(function(err) { 68 | throw err; 69 | res.sendStatus(500); 70 | }); 71 | }) 72 | .catch(function(err) { 73 | throw err; 74 | res.sendStatus(500); 75 | }); 76 | } 77 | }; -------------------------------------------------------------------------------- /client/components/VideoGrid.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { changeCurrentVideo, addToWatch } from '../actions/actions.jsx'; 4 | import StarBorder from 'material-ui/lib/svg-icons/toggle/star-border'; 5 | import IconButton from 'material-ui/lib/icon-button'; 6 | import GridList from 'material-ui/lib/grid-list/grid-list'; 7 | import GridTile from 'material-ui/lib/grid-list/grid-tile'; 8 | 9 | 10 | //Component code 11 | export function VideoGrid({ user,videos , selectVideo, addToWatch}) { 12 | const styles = { 13 | root: { 14 | flexWrap: 'wrap', 15 | justifyContent: 'space-around', 16 | 17 | }, 18 | gridList: { 19 | width: 1000, 20 | overflowY: 'auto', 21 | marginBottom: 15, 22 | fontFamily: 'Raleway' 23 | }, 24 | }; 25 | 26 | if(Object.keys(videos).length !== 0){ 27 | return ( 28 |
29 | 30 | { videos.map(function(video){ 31 | 32 | return by {video.userName}} actionIcon={ addToWatch(video,user) } >} > 34 | selectVideo(video)} />; 35 | 36 | })} 37 | 38 |
39 | ); 40 | }else{ 41 | return ( 42 |

No videos found

43 | ) 44 | } 45 | } 46 | // onClick = {() => selectVideo(video)} 47 | 48 | //Container Code 49 | const mapStateToProps = (state) => { 50 | return { 51 | videos: state.videos, 52 | user: state.user 53 | } 54 | }; 55 | 56 | const mapDispatchToProps = (dispatch) => { 57 | return { 58 | selectVideo: (value) => { 59 | console.log('Selected video!'); 60 | dispatch(changeCurrentVideo(value)); 61 | window.location = '/#/player' 62 | }, 63 | addToWatch: (video,user) => { 64 | console.log("Saved"); 65 | var info = {video: video, user: user} 66 | console.log("info is ", info); 67 | dispatch(addToWatch(info)); 68 | } 69 | } 70 | }; 71 | 72 | export default connect( 73 | mapStateToProps, 74 | mapDispatchToProps 75 | )(VideoGrid); 76 | 77 | -------------------------------------------------------------------------------- /client/components/DiscussionSection.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { addFeedback, addQuestion, showAnswerEdit, hideAnswerEdit, addAnswer } from '../actions/actions.jsx'; 4 | import Tabs from 'material-ui/lib/tabs/tabs'; 5 | import Tab from 'material-ui/lib/tabs/tab'; 6 | import FeedbackTab from './FeedbackTab.jsx'; 7 | import Q_ATab from './Q_ATab.jsx'; 8 | 9 | 10 | class DiscussionSection extends Component { 11 | 12 | render() { 13 | return ( 14 | 17 | 18 | 23 | 24 | 25 | 34 | 35 | 36 | ) 37 | }; 38 | } 39 | 40 | 41 | const mapStateToProps = (state) => { 42 | return { 43 | feedback: state.feedback, 44 | questions: state.questions, 45 | currentVideo: state.currentVideo, 46 | currentUser: state.user, 47 | answerEdit: state.answerEdit 48 | } 49 | }; 50 | 51 | const mapDispatchToProps = (dispatch) => { 52 | return { 53 | addFeedback: (feedback, username, videoID, userID) => { 54 | dispatch(addFeedback(feedback, username, videoID, userID)); 55 | }, 56 | addQuestion: (question, asker, videoID, userID) => { 57 | dispatch(addQuestion(question, asker, videoID, userID)); 58 | }, 59 | addAnswer: (answer, questionID, videoID) => { 60 | dispatch(addAnswer(answer, questionID, videoID)); 61 | dispatch(hideAnswerEdit()); 62 | }, 63 | showAnswerEdit: (questionID) => { 64 | dispatch(showAnswerEdit(questionID)); 65 | }, 66 | hideAnswerEdit: () => { 67 | dispatch(hideAnswerEdit()); 68 | }, 69 | }; 70 | }; 71 | 72 | export default connect( 73 | mapStateToProps, 74 | mapDispatchToProps 75 | )(DiscussionSection); -------------------------------------------------------------------------------- /client/components/PlayerPage.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import VideoPlayer from './VideoPlayer.jsx'; 4 | import VotesSection from './VotesSection.jsx'; 5 | import DiscussionSection from './DiscussionSection.jsx'; 6 | import GridList from 'material-ui/lib/grid-list/grid-list'; 7 | import GridTile from 'material-ui/lib/grid-list/grid-tile'; 8 | import { loadFeedback, loadQuestions } from '../actions/actions.jsx'; 9 | import $ from 'jquery'; 10 | 11 | //Component Code 12 | export class PlayerPage extends Component { 13 | 14 | componentWillMount(){ 15 | this.props.loadFeedback(this.props.currentVideo.id); 16 | this.props.loadQuestions(this.props.currentVideo.id); 17 | } 18 | 19 | /* 20 | shouldComponentUpdate{ 21 | 22 | } 23 | **/ 24 | 25 | render(){ 26 | if(this.props.currentVideo){ 27 | return ( 28 | 29 |
30 |

{this.props.currentVideo.title}

31 |

{this.props.currentVideo.userName}

32 |
33 | 34 |
35 | 36 |
37 |

{this.props.currentVideo.description}

38 | 39 |
40 | 41 |

42 |
43 | 44 | ); 45 | } else { 46 | return ( 47 |

Player Page

48 | ); 49 | } 50 | } 51 | } 52 | 53 | 54 | //Container Code 55 | const mapStateToProps = (state) => { 56 | return { 57 | currentVideo: state.currentVideo, 58 | } 59 | }; 60 | 61 | const mapDispatchToProps = (dispatch) => { 62 | return { 63 | loadFeedback: (videoid) => { 64 | dispatch(loadFeedback(videoid)); 65 | }, 66 | loadQuestions: (videoid) => { 67 | dispatch(loadQuestions(videoid)); 68 | } 69 | }; 70 | }; 71 | 72 | export default connect( 73 | mapStateToProps, 74 | mapDispatchToProps 75 | )(PlayerPage); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Virtuoso", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "lint": "eslint -c .eslintrc.json --ignore-path .gitignore .", 7 | "start": "nodemon --watch server server/server.js", 8 | "build": "webpack --progress --colors", 9 | "test": "mocha --compilers js:babel-register --recursive" 10 | }, 11 | "pre-commit": [ 12 | "lint" 13 | ], 14 | "license": "UNLICENSED", 15 | "main": "client/index.jsx", 16 | "dependencies": { 17 | "aws-sdk": "^2.2.44", 18 | "babel-plugin-react-transform": "^2.0.2", 19 | "bcrypt": "^0.8.5", 20 | "bcrypt-nodejs": "0.0.3", 21 | "bluebird": "^3.3.1", 22 | "body-parser": "^1.4.3", 23 | "eslint-plugin-react": "^4.2.3", 24 | "express": "^4.4.5", 25 | "express-session": "^1.13.0", 26 | "jquery": "^2.2.0", 27 | "material-ui": "^0.15.0-alpha.1", 28 | "method-override": "~2.0.2", 29 | "morgan": "^1.1.1", 30 | "mysql": "^2.10.2", 31 | "passport": "^0.3.2", 32 | "passport-local": "^1.0.0", 33 | "path": "^0.12.7", 34 | "q": "^1.4.1", 35 | "react": "^0.14.7", 36 | "react-dom": "^0.14.7", 37 | "react-hot-loader": "^1.3.0", 38 | "react-redux": "^4.4.1", 39 | "react-router": "^2.0.1", 40 | "react-s3-uploader": "^3.0.2", 41 | "react-tap-event-plugin": "^0.2.2", 42 | "redux": "^3.3.1", 43 | "redux-logger": "^2.6.1", 44 | "redux-promise": "^0.5.1", 45 | "redux-thunk": "^2.0.1", 46 | "request": "^2.69.0", 47 | "request-promise": "^2.0.1", 48 | "sequelize": "^3.19.3", 49 | "sequelize-cli": "^2.3.1", 50 | "underscore": "^1.8.3" 51 | }, 52 | "devDependencies": { 53 | "babel-cli": "^6.6.5", 54 | "babel-core": "^6.7.2", 55 | "babel-loader": "^6.2.3", 56 | "babel-plugin-react-transform": "^2.0.0", 57 | "babel-plugin-transform-runtime": "^6.6.0", 58 | "babel-preset-es2015": "^6.6.0", 59 | "babel-preset-react": "^6.5.0", 60 | "babel-preset-react-hmre": "^1.1.0", 61 | "babel-preset-stage-0": "^6.5.0", 62 | "babel-register": "^6.7.2", 63 | "chai": "^3.5.0", 64 | "css-loader": "^0.23.1", 65 | "eslint-plugin-react": "^4.2.1", 66 | "mocha": "^2.4.5", 67 | "pre-commit": "^1.1.2", 68 | "react-transform-catch-errors": "^1.0.2", 69 | "react-transform-debug-inspector": "^0.1.3", 70 | "react-transform-hmr": "^1.0.2", 71 | "react-transform-render-visualizer": "^0.4.0", 72 | "style-loader": "^0.13.0", 73 | "supertest": "^1.2.0", 74 | "webpack": "^1.12.12", 75 | "webpack-dev-middleware": "^1.5.1", 76 | "webpack-hot-middleware": "^2.9.1" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import NavBar from './components/NavBar.jsx'; 3 | import { render } from 'react-dom'; 4 | import { Router, Route, IndexRoute, Link, hashHistory } from 'react-router'; 5 | import HomePage from './components/HomePage.jsx'; 6 | import SearchPage from './components/SearchPage.jsx'; 7 | import ProfilePage from './components/ProfilePage.jsx'; 8 | import PlayerPage from './components/PlayerPage.jsx'; 9 | import SignInModal from './components/SignInModal.jsx'; 10 | import SignUpModal from './components/SignUpModal.jsx'; 11 | import UploadModal from './components/UploadModal.jsx'; 12 | import ReactS3Uploader from 'react-s3-uploader'; 13 | import ReactDOM from 'react-dom'; 14 | import { Provider } from 'react-redux'; 15 | import { createStore, getState, applyMiddleware } from 'redux'; 16 | import thunk from 'redux-thunk'; 17 | import logger from 'redux-logger'; 18 | import ReduxPromise from 'redux-promise'; 19 | import VideoAppHandler from './reducers/reducer.jsx'; 20 | import injectTapEventPlugin from 'react-tap-event-plugin'; 21 | import ThemeManager from 'material-ui/lib/styles/theme-manager'; 22 | import MyTheme from './styles/materialTheme.js'; 23 | 24 | injectTapEventPlugin(); 25 | 26 | let store = createStore(VideoAppHandler, applyMiddleware(ReduxPromise, thunk, logger())); 27 | 28 | export default class App extends Component { 29 | 30 | constructor(props) { 31 | super(props) 32 | } 33 | 34 | //the key passed through context must be called "muiTheme" 35 | childContextTypes = { 36 | muiTheme: React.PropTypes.object, 37 | }; 38 | getChildContext() { 39 | return { 40 | muiTheme: ThemeManager.getMuiTheme(MyTheme), 41 | }; 42 | } 43 | 44 | render(){ 45 | return ( 46 |
47 | 48 | 49 | 50 |
51 | {this.props.children} 52 |
53 | ) 54 | } 55 | } 56 | 57 | App.childContextTypes = { 58 | muiTheme: React.PropTypes.object 59 | }; 60 | 61 | render(( 62 | // React Router allows user to access different pages, depending how 63 | // the window location is set. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ), document.getElementById('app')); 75 | 76 | -------------------------------------------------------------------------------- /client/components/SignUpModal.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Dialog from 'material-ui/lib/dialog'; 3 | import FlatButton from 'material-ui/lib/flat-button'; 4 | import TextField from 'material-ui/lib/text-field'; 5 | import {connect} from 'react-redux'; 6 | import { signUpUser, hideSignUpModal, toggleSignUpModal, authError } from '../actions/actions.jsx'; 7 | 8 | export default class SignUpModal extends Component { 9 | render() { 10 | var signUp = this.props.signUp; 11 | var closeModal = this.props.closeModal; 12 | var displaySignUpModal = this.props.displaySignUpModal; 13 | var authError = this.props.authError; 14 | var authErrorReset = this.props.authErrorReset; 15 | 16 | const customContentStyle = { 17 | width: 350, 18 | maxWidth: 'none', 19 | }; 20 | 21 | const actions = [ 22 | { 26 | closeModal(); 27 | authErrorReset(); 28 | }} 29 | style={{color: '#ff4f1a'}} 30 | />, 31 | { 35 | signUp({username: this.refs.username.getValue(), password: this.refs.password.getValue()}); 36 | }} 37 | /> 38 | ]; 39 | 40 | return ( 41 |
42 | 49 | {authError === 'username-SignUp' ? 50 | (Username already exists.) : 51 | ""} 52 | 57 | 63 | 64 |
65 | ); 66 | } 67 | } 68 | 69 | const mapStateToProps = (state) => { 70 | return { 71 | displaySignUpModal: state.displaySignUpModal, 72 | authError: state.authError 73 | } 74 | }; 75 | 76 | const mapDispatchToProps = (dispatch) => { 77 | return { 78 | signUp: (user) => { 79 | dispatch(signUpUser(user)) 80 | }, 81 | closeModal: () => { 82 | dispatch(hideSignUpModal()) 83 | }, 84 | authErrorReset: () => { 85 | dispatch(authError(null)); 86 | } 87 | }; 88 | }; 89 | 90 | export default connect(mapStateToProps, mapDispatchToProps)(SignUpModal); -------------------------------------------------------------------------------- /client/components/Featured.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { changeCurrentVideo } from '../actions/actions.jsx'; 4 | import Card from 'material-ui/lib/card/card'; 5 | import CardMedia from 'material-ui/lib/card/card-media'; 6 | import CardTitle from 'material-ui/lib/card/card-title'; 7 | import GridList from 'material-ui/lib/grid-list/grid-list'; 8 | import GridTile from 'material-ui/lib/grid-list/grid-tile'; 9 | 10 | 11 | //Component Code 12 | export default function Featured({videos, selectVideo}) { 13 | 14 | if(Object.keys(videos).length !== 0){ 15 | var vidLength = videos.length; 16 | var currentVideo1 = videos[Math.floor(Math.random()*vidLength)]; 17 | var currentVideo2 = videos[Math.floor(Math.random()*vidLength)]; 18 | while (currentVideo2 === currentVideo1) { 19 | currentVideo2 = videos[Math.floor(Math.random()*vidLength)]; 20 | }; 21 | 22 | return( 23 | 24 |
25 | 30 | selectVideo(currentVideo1)} 32 | key={1} 33 | style={{fontFamily: 'Raleway'}} 34 | title={currentVideo1.title} 35 | subtitle={'by ' + currentVideo1.userName} 36 | cols={1} 37 | > 38 | 39 | 40 | selectVideo(currentVideo2)} 42 | key={2} 43 | style={{fontFamily: 'Raleway'}} 44 | title={currentVideo2.title} 45 | style={{fontFamily: 'Raleway'}} 46 | subtitle={'by ' + currentVideo2.userName} 47 | cols={1} 48 | > 49 | 50 | 51 | 52 |
53 | 54 | ); 55 | 56 | }else{ 57 | return ( 58 |

Featured

59 | ); 60 | } 61 | } 62 | 63 | //Container Code 64 | const mapStateToProps = (state) => { 65 | return { 66 | videos: state.videos 67 | } 68 | }; 69 | 70 | const mapDispatchToProps = (dispatch) => { 71 | return { 72 | selectVideo: (value) => { 73 | console.log('Selected video!'); 74 | dispatch(changeCurrentVideo(value)); 75 | window.location = '/#/player' 76 | }, 77 | changeFeatured: (value) => { 78 | console.log('Changing featured video!'); 79 | dispatch(changeFeatured()); 80 | } 81 | }; 82 | }; 83 | 84 | export default connect( 85 | mapStateToProps, 86 | mapDispatchToProps 87 | )(Featured); 88 | 89 | -------------------------------------------------------------------------------- /client/components/SignInModal.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Dialog from 'material-ui/lib/dialog'; 3 | import FlatButton from 'material-ui/lib/flat-button'; 4 | import TextField from 'material-ui/lib/text-field'; 5 | import {connect} from 'react-redux'; 6 | import { signInUser, hideSignInModal, toggleSignInModal, authError } from '../actions/actions.jsx'; 7 | 8 | export default class SignInModal extends Component { 9 | render() { 10 | var signIn = this.props.signIn; 11 | var closeModal = this.props.closeModal; 12 | var displaySignInModal = this.props.displaySignInModal; 13 | var authError = this.props.authError; 14 | var authErrorReset = this.props.authErrorReset; 15 | 16 | const customContentStyle = { 17 | width: 350, 18 | maxWidth: 'none', 19 | }; 20 | 21 | const actions = [ 22 | { 26 | closeModal(); 27 | authErrorReset(); 28 | }} 29 | style={{color: '#ff4f1a'}} 30 | />, 31 | { 35 | signIn({username: this.refs.username.getValue(), password: this.refs.password.getValue()}); 36 | }} 37 | /> 38 | ]; 39 | 40 | return ( 41 |
42 | 49 | {authError === 'username-SignIn' ? 50 | (Username does not exist.) : 51 | authError === 'password' ? 52 | (Incorrect password.) : 53 | ""} 54 | 59 | 65 | 66 |
67 | ); 68 | } 69 | } 70 | 71 | const mapStateToProps = (state) => { 72 | return { 73 | displaySignInModal: state.displaySignInModal, 74 | authError: state.authError 75 | } 76 | }; 77 | 78 | const mapDispatchToProps = (dispatch) => { 79 | return { 80 | signIn: (user) => { 81 | dispatch(signInUser(user)) 82 | 83 | }, 84 | closeModal: () => { 85 | dispatch(hideSignInModal()) 86 | }, 87 | authErrorReset: () => { 88 | dispatch(authError(null)); 89 | } 90 | }; 91 | }; 92 | 93 | export default connect(mapStateToProps, mapDispatchToProps)(SignInModal); -------------------------------------------------------------------------------- /server/config/routes.js: -------------------------------------------------------------------------------- 1 | var userController = require('../controllers/userController'); 2 | var videoController = require('../controllers/videoController'); 3 | var categoryController = require('../controllers/categoryController'); 4 | var voteController = require('../controllers/voteController'); 5 | var discussionController = require('../controllers/discussionController'); 6 | var passport = require('./authentication').passport; 7 | var ensureAuthenticated = require('./authentication').ensureAuthenticated; 8 | 9 | module.exports = function(app, express) { 10 | // Handles request to sign up new user 11 | app.post('/signup', userController.userSignUp); 12 | 13 | // Handles request to sign in existing user 14 | app.post('/signin', userController.userSignIn); 15 | 16 | // Handles user sign out 17 | app.get('/signout', userController.userSignOut); 18 | 19 | // Handles fetching inital videos from db to populate video grid on home page 20 | app.get('/fetch', videoController.fetchAll); 21 | 22 | 23 | // Handles fetching uploaded videos from db to populate video grid on profile page 24 | app.post('/fetch', videoController.fetchUserVideo); 25 | 26 | // Handles fetching videos that match the specified search query 27 | app.post('/search', videoController.fetchVideo); 28 | 29 | // Handles adding a new video to the db 30 | app.post('/addVideo', videoController.addVideo); 31 | 32 | // Load feedback for current Video 33 | app.post('/loadFeedback', discussionController.loadFeedback); 34 | 35 | // Add new feedback to db 36 | app.post('/addFeedback', ensureAuthenticated, discussionController.addFeedback); 37 | 38 | // Load questions for current Video 39 | app.post('/loadQuestions', discussionController.loadQuestions); 40 | 41 | // Add new question to db 42 | app.post('/addQuestion', discussionController.addQuestion); 43 | 44 | // Increases the upvote of certain video 45 | app.post('/upVote', voteController.upVotes); 46 | 47 | // Increases the upvote of certain video 48 | app.post('/downVote', voteController.downVotes); 49 | 50 | //Handles adding about me on the profile page 51 | app.post('/aboutMe', userController.editAboutMe); 52 | 53 | //Handles fetching all video categories 54 | app.get('/loadCategories', categoryController.loadCategories); 55 | 56 | // Add's answer to an existing question 57 | app.post('/addAnswer', discussionController.addAnswer); 58 | 59 | // Add's a video to a user's watchlist 60 | app.post('/addToWatch', videoController.addWatchListVideo); 61 | 62 | // Fetches the watchlist for a particular user 63 | app.post('/fetchWatchList', videoController.fetchWatchList); 64 | 65 | // Delete video from S3 bucket if duration was too long 66 | app.post('/deleteVideoFromBucket', videoController.deleteVideoFromBucket); 67 | 68 | }; -------------------------------------------------------------------------------- /test/reducersSpec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import VideoAppHandler from '../client/reducers/reducer.jsx'; 3 | 4 | describe('reducer', () => { 5 | 6 | it('handles SELECT_VIDEO action', () => { 7 | const initialState = {}; 8 | const action = {type: 'SELECT_VIDEO', data: {video: 'Awesome Video'}}; 9 | const nextState = VideoAppHandler(initialState, action); 10 | 11 | expect(nextState.currentVideo).to.deep.equal({video: 'Awesome Video'}); 12 | expect(initialState.currentVideo).to.equal(undefined); 13 | }); 14 | 15 | it('handles FETCH_VIDEOS action', () => { 16 | const initialState = {}; 17 | const action = {type: 'FETCH_VIDEOS', videos: [{video: 'Awesome Video 1'}, {video: 'Awesome Video 2'}, {video: 'Awesome Video 3'}] }; 18 | const nextState = VideoAppHandler(initialState, action); 19 | 20 | expect(nextState.videos).to.deep.equal([{video: 'Awesome Video 1'}, {video: 'Awesome Video 2'}, {video: 'Awesome Video 3'}]); 21 | expect(initialState.videos).to.equal(undefined); 22 | }); 23 | 24 | it('handles CHANGE_USER action', () => { 25 | const initialState = {}; 26 | const action = {type: 'CHANGE_USER', user: {name: 'The', password: 'Man'} }; 27 | const nextState = VideoAppHandler(initialState, action); 28 | 29 | expect(nextState.user).to.deep.equal({name: 'The', password: 'Man'}); 30 | expect(initialState.user).to.equal(undefined); 31 | }); 32 | 33 | it('handles SHOW_SIGNIN_MODAL and HIDE_SIGNIN_MODAL actions', () => { 34 | const initialState_SHOW = {}; 35 | const action_SHOW = {type: 'SHOW_SIGNIN_MODAL'}; 36 | const nextState_SHOW = VideoAppHandler(initialState_SHOW, action_SHOW); 37 | 38 | const initialState_HIDE = {}; 39 | const action_HIDE = {type: 'HIDE_SIGNIN_MODAL'}; 40 | const nextState_HIDE = VideoAppHandler(initialState_HIDE, action_HIDE); 41 | 42 | expect(nextState_SHOW.displaySignInModal).to.deep.equal(true); 43 | expect(initialState_SHOW.displaySignInModal).to.equal(undefined); 44 | 45 | expect(nextState_HIDE.displaySignInModal).to.deep.equal(false); 46 | expect(initialState_HIDE.displaySignInModal).to.equal(undefined); 47 | }); 48 | 49 | it('handles SHOW_SIGNUP_MODAL and HIDE_SIGNUP_MODAL actions', () => { 50 | const initialState_SHOW = {}; 51 | const action_SHOW = {type: 'SHOW_SIGNUP_MODAL'}; 52 | const nextState_SHOW = VideoAppHandler(initialState_SHOW, action_SHOW); 53 | 54 | const initialState_HIDE = {}; 55 | const action_HIDE = {type: 'HIDE_SIGNUP_MODAL'}; 56 | const nextState_HIDE = VideoAppHandler(initialState_HIDE, action_HIDE); 57 | 58 | expect(nextState_SHOW.displaySignUpModal).to.deep.equal(true); 59 | expect(initialState_SHOW.displaySignUpModal).to.equal(undefined); 60 | 61 | expect(nextState_HIDE.displaySignUpModal).to.deep.equal(false); 62 | expect(initialState_HIDE.displaySignUpModal).to.equal(undefined); 63 | }); 64 | 65 | }); -------------------------------------------------------------------------------- /server/controllers/voteController.js: -------------------------------------------------------------------------------- 1 | var db = require('../db'); 2 | 3 | module.exports = { 4 | upVotes: function (req, res) { 5 | var videoID = req.body.videoID; 6 | var userID = req.body.userID; 7 | if (userID !== undefined){ 8 | db.Votes.findAll({where: {videoID: videoID, userID: userID}}) 9 | .then(function(vote) { 10 | if(vote.length === 0) { 11 | db.Votes.create({videoID: videoID, userID: userID, upVote: 1, downVote:0}) 12 | .then(function() { 13 | // db.Votes.find({ 14 | // attributes: [[db.fn('COUNT', db.col(upVote)),'upVote'], 'downVote'], 15 | // include: [{ 16 | // attributes : [], 17 | // where: { 18 | // videoID: videoID}}] 19 | //}) 20 | db.db.query("select SUM(upVote) as upVote from Votes where videoID = "+videoID, {type: db.db.QueryTypes.SELECT}) 21 | .then(function(voteCount) { 22 | db.Video.findOne({where: {id: videoID}}) 23 | .then(function(video) { 24 | video.updateAttributes({ 25 | upVotes: voteCount[0].upVote 26 | }) 27 | .then(function(){ 28 | db.Video.findOne({where: {id: videoID}}) 29 | .then(function(video){ 30 | res.send(video); 31 | }); 32 | }); 33 | }); 34 | }) 35 | .catch(function(err) { 36 | throw err; 37 | res.sendStatus(500); 38 | }); 39 | }) ; 40 | } 41 | }); 42 | 43 | // .catch(function(err) { 44 | // throw err; 45 | // res.status(500); 46 | // }); 47 | 48 | } 49 | 50 | }, 51 | downVotes: function (req, res) { 52 | var videoID = req.body.videoID; 53 | var userID = req.body.userID; 54 | db.Votes.findAll({where: {videoID: videoID, userID: userID}}) 55 | .then(function(vote) { 56 | if(vote.length === 0) { 57 | db.Votes.create({videoID: videoID, userID: userID, upVote: 0, downVote:1}) 58 | .then(function() { 59 | // db.Votes.find({ 60 | // attributes: [[db.fn('COUNT', db.col(upVote)),'upVote'], 'downVote'], 61 | // include: [{ 62 | // attributes : [], 63 | // where: { 64 | // videoID: videoID}}] 65 | //}) 66 | db.db.query("select SUM(downVote) as downVote from Votes where videoID = "+videoID, {type: db.db.QueryTypes.SELECT}) 67 | .then(function(voteCount) { 68 | db.Video.findOne({where: {id: videoID}}) 69 | .then(function(video) { 70 | video.updateAttributes({ 71 | downVotes: voteCount[0].downVote 72 | }) 73 | .then(function(){ 74 | db.Video.findOne({where: {id: videoID}}) 75 | .then(function(video){ 76 | res.send(video); 77 | }); 78 | }); 79 | }); 80 | }) 81 | .catch(function(err) { 82 | throw err; 83 | res.sendStatus(500); 84 | }); 85 | }); 86 | } 87 | }); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /server/controllers/videoController.js: -------------------------------------------------------------------------------- 1 | var db = require('../db'); 2 | var AWS = require('aws-sdk'); 3 | 4 | module.exports = { 5 | // Handles importing a video to the S3 storage 6 | addVideo: function (req, res) { 7 | var video = req.body; 8 | db.Video.create({ 9 | title: video.title, 10 | description: video.description, 11 | cover: video.cover, 12 | url: video.url, 13 | userName: video.user.username, 14 | UserId: video.user.id, 15 | CategoryId: video.categoryId 16 | }) 17 | .then(function(video) { 18 | res.send(201, video); 19 | }) 20 | .catch(function(err) { 21 | throw err; 22 | res.sendStatus(500); 23 | }); 24 | }, 25 | // Handles fetching of specified video from S3 storage 26 | fetchVideo: function (req, res) { 27 | var query = req.body.query; 28 | var queryType= req.body.queryType; 29 | console.log("queryType is;", queryType); 30 | var search ={}; 31 | search[queryType] = {$like : '%' + query + '%'}; 32 | db.Video.findAll({where: search}) 33 | .then(function(videos) { 34 | res.send(200, videos); 35 | }) 36 | .catch(function(err) { 37 | throw err; 38 | res.sendStatus(500); 39 | }); 40 | }, 41 | fetchUserVideo: function (req, res) { 42 | db.Video.findAll({where: {userid: req.body.id}}) 43 | .then(function(videos) { 44 | res.send(200, videos); 45 | }) 46 | .catch(function(err) { 47 | throw err; 48 | res.sendStatus(500); 49 | }); 50 | }, 51 | 52 | fetchAll: function (req, res) { 53 | db.Video.findAll({}) 54 | .then(function(videos) { 55 | res.send(200, videos); 56 | }) 57 | .catch(function(err) { 58 | throw err; 59 | res.sendStatus(500); 60 | }); 61 | }, 62 | 63 | fetchWatchList: function (req, res) { 64 | var userId = req.body.userid; 65 | db.db.query('select * from Videos inner join WatchListVideos on (Videos.id = WatchListVideos.videoid) inner join Users on \ 66 | (WatchListVideos.userid = Users.id) where Users.id ='+userId, {type: db.db.QueryTypes.SELECT}) 67 | .then(function(data) { 68 | res.send(200, data); 69 | }) 70 | .catch(function(err) { 71 | throw err; 72 | res.send(500); 73 | }); 74 | }, 75 | 76 | addWatchListVideo: function (req, res) { 77 | var userID = req.body.user.id; 78 | var videoID = req.body.video.id; 79 | db.WatchListVideo.create({ 80 | videoID: videoID, 81 | userID: userID 82 | }) 83 | .then(function() { 84 | res.sendStatus(201); 85 | }) 86 | .catch(function(err) { 87 | throw err; 88 | res.sendStatus(500); 89 | }); 90 | }, 91 | 92 | deleteVideoFromBucket: function(req, res) { 93 | var filename = req.body.filename; 94 | var bucketInstance = new AWS.S3(); 95 | var params = { 96 | Bucket: 'video.bucket1', 97 | Key: filename 98 | }; 99 | bucketInstance.deleteObject(params, function (err, data) { 100 | if (data) { 101 | console.log("File deleted successfully"); 102 | res.send(200); 103 | } 104 | else { 105 | console.log("Check if you have sufficient permissions : "+err); 106 | res.send(500); 107 | } 108 | }); 109 | } 110 | 111 | }; -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | var db = require('../db'); 2 | var bcrypt = require('bcrypt'); 3 | var session = require('express-session'); 4 | 5 | module.exports = { 6 | // Handles signing up a new user and adding them to the database 7 | userSignUp: function (req, res) { 8 | db.User.findOne({where: {username: req.body.username}}) 9 | .then(function(user) { 10 | if (user !== null) { 11 | res.send(401, 'username-SignUp'); 12 | return; 13 | } 14 | bcrypt.hash(req.body.password, 10, function(err, hash) { 15 | if (err) { 16 | throw err; 17 | res.sendStatus(500); 18 | } 19 | db.User.create({ 20 | username: req.body.username, 21 | password: hash, 22 | }) 23 | .then(function(user) { 24 | req.session.regenerate(function (err) { 25 | if (err) { 26 | throw err; 27 | res.sendStatus(500); 28 | } 29 | req.session.userId = user.id; 30 | var usr = user; 31 | usr.password = ""; 32 | res.send(200, usr); 33 | }); 34 | }) 35 | .catch(function(err) { 36 | throw err; 37 | res.sendStatus(500); 38 | }); 39 | }); 40 | }) 41 | .catch(function(err) { 42 | throw err; 43 | res.sendStatus(500); 44 | }); 45 | }, 46 | 47 | // Handles signing in an existing user and checking they entered the correct username/password 48 | userSignIn: function (req, res) { 49 | var username = req.body.username; 50 | var password = req.body.password; 51 | 52 | db.User.findOne({where: {username: req.body.username}}) 53 | .then(function(user) { 54 | if (user === null) { 55 | res.send(401, 'username-SignIn'); 56 | return; 57 | } 58 | bcrypt.compare(password, user.password, function(err, match) { 59 | if (err) { 60 | throw err; 61 | res.sendStatus(500); 62 | } 63 | if (!match) { 64 | res.send(401, 'password'); 65 | return; 66 | } else { 67 | req.session.regenerate(function (err) { 68 | if (err) { 69 | throw err; 70 | res.sendStatus(500); 71 | } 72 | req.session.userId = user.id; 73 | var usr = user; 74 | usr.password = ""; 75 | res.send(200, usr); 76 | }); 77 | } 78 | }); 79 | }) 80 | .catch(function(err) { 81 | throw (err); 82 | res.sendStatus(500); 83 | }); 84 | }, 85 | 86 | // Handles user signing out and removes their existing session 87 | userSignOut: function (req, res) { 88 | req.session.destroy(function(err) { 89 | if (err) { 90 | throw err; 91 | res.sendStatus(500); 92 | } 93 | res.send(200); 94 | }); 95 | }, 96 | 97 | 98 | editAboutMe: function (req,res) { 99 | db.User.findOne({where: {username: req.body.username}}) 100 | .then(function(user) { 101 | user.aboutMe = req.body.info; 102 | user.save() 103 | .then(function(user) { 104 | res.send(201, user); 105 | }) 106 | .catch(function(err) { 107 | throw err; 108 | res.sendStatus(500); 109 | }); 110 | }) 111 | .catch(function(err) { 112 | throw err; 113 | res.sendStatus(500); 114 | }); 115 | } 116 | }; -------------------------------------------------------------------------------- /client/components/ProfilePage.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchVideoList, updateAboutMe, aboutMeEdit, showAboutMeEdit, hideAboutMeEdit, fetchWatchList} from '../actions/actions.jsx'; 4 | import UserInfo from './UserInfo.jsx'; 5 | import VideoGrid from './VideoGrid.jsx'; 6 | import $ from 'jquery'; 7 | import Tab from 'material-ui/lib/tabs/tab'; 8 | import Tabs from 'material-ui/lib/tabs/tabs'; 9 | import GridList from 'material-ui/lib/grid-list/grid-list'; 10 | import GridTile from 'material-ui/lib/grid-list/grid-tile'; 11 | 12 | 13 | const mapStateToProps = (state) => { 14 | return { 15 | user: state.user, 16 | aboutMeEdit:state.aboutMeEdit, 17 | aboutMe: state.user.aboutMe, 18 | videos: state.videos 19 | } 20 | } 21 | 22 | const mapDispatchToProps = (dispatch) => { 23 | 24 | return { 25 | updateUserInfo: function(info, user, aboutMeEdit) { 26 | 27 | if (aboutMeEdit === true) { 28 | $.post('/aboutMe', {info:info, username: user.username}) 29 | .done(function(data){ 30 | dispatch(updateAboutMe(data)); 31 | dispatch(hideAboutMeEdit()); 32 | }) 33 | } else { 34 | dispatch(showAboutMeEdit()) 35 | } 36 | 37 | }, 38 | fetchUploadedVideos: function(user) { 39 | $.post('/fetch', user) 40 | .done(function(res){ 41 | dispatch(fetchVideoList(res)); 42 | }); 43 | }, 44 | fetchWatchList: function(userid) { 45 | $.post('/fetchWatchList', {userid: userid}) 46 | .done( (videos) => { 47 | dispatch(fetchVideoList(videos)); 48 | }); 49 | } 50 | } 51 | } 52 | 53 | 54 | export class ProfilePage extends Component { 55 | 56 | 57 | componentDidMount(){ 58 | this.props.fetchUploadedVideos(this.props.user); 59 | } 60 | 61 | 62 | render(){ 63 | var aboutMeEdit =
64 | 65 | 68 |
69 | var aboutMe =
this.props.updateUserInfo(null,this.props.user,this.props.aboutMeEdit) }> {this.props.aboutMe}
70 | 71 | var divider = { 72 | borderRight: '1px solid black' 73 | } 74 | 75 | return ( 76 |
77 | 83 | 87 |

Welcome Back, {this.props.user.username}!

88 |
89 |

About me:

90 |
91 | {this.props.aboutMeEdit === true ? aboutMeEdit : aboutMe} 92 |
93 |
94 |
95 | 96 | 97 | 98 | this.props.fetchUploadedVideos(this.props.user)}> 99 | 100 | 101 | 102 | 103 | this.props.fetchWatchList(this.props.user.id)}> 104 | 105 | 106 | 107 | 108 | 109 |
110 |
111 | ); 112 | } 113 | } 114 | 115 | 116 | 117 | // 118 | 119 | 120 | export default connect( 121 | mapStateToProps, 122 | mapDispatchToProps 123 | )(ProfilePage); 124 | -------------------------------------------------------------------------------- /server/db/index.js: -------------------------------------------------------------------------------- 1 | 2 | var Sequelize = require('sequelize'); 3 | const config = require('../config/database.json'); 4 | const env = config.production; 5 | 6 | // Fill in with your own mysql info (you'll probably be using root-user too) 7 | // db-name , user , password 8 | // var db = new Sequelize('thesis', 'test', 'password'); 9 | 10 | var db = new Sequelize( 11 | env.database, 12 | 'rootPROD', 13 | 'passwordPROD', 14 | { port: env.port, host: env.host, logging: console.log } 15 | ); 16 | 17 | // User's schema 18 | var User = db.define('User', { 19 | username: {type: Sequelize.STRING, unique: true}, 20 | password: Sequelize.STRING, 21 | aboutMe: {type: Sequelize.STRING, allowNull: false, defaultValue: "Edit About Me"} 22 | }); 23 | // Video's schema 24 | var Video = db.define('Video', { 25 | title: Sequelize.STRING, 26 | description: Sequelize.STRING, 27 | url:Sequelize.STRING, 28 | cover:Sequelize.STRING, 29 | userName: Sequelize.STRING, 30 | upVotes:Sequelize.INTEGER, 31 | downVotes:Sequelize.INTEGER 32 | }); 33 | 34 | //Feedback schema 35 | var Feedback = db.define('Feedback', { 36 | feedback: Sequelize.TEXT, 37 | username: Sequelize.STRING 38 | }); 39 | 40 | //Votes's schema 41 | var Votes = db.define('Votes', { 42 | videoID: Sequelize.STRING, 43 | userID: Sequelize.STRING, 44 | upVote:Sequelize.INTEGER, 45 | downVote:Sequelize.INTEGER 46 | },{ 47 | timestamps: false 48 | }); 49 | 50 | var WatchListVideo = db.define('WatchListVideo', { 51 | videoID: Sequelize.STRING, 52 | userID: Sequelize.STRING 53 | }); 54 | 55 | // Category's schema 56 | var Category = db.define('Category', { 57 | name: Sequelize.STRING 58 | }); 59 | 60 | // Question's schema 61 | var Question = db.define('Question', { 62 | question: Sequelize.STRING(600), 63 | answer: Sequelize.TEXT, 64 | asker: Sequelize.STRING 65 | }); 66 | 67 | // Sets up one-to-many relationship between User and Question, and Video and Question 68 | Question.belongsTo(User); 69 | User.hasMany(Question); 70 | Question.belongsTo(Video); 71 | Video.hasMany(Question); 72 | 73 | // Sets up one-to-many relationship between User and Feedback, and Video and Feedback 74 | Feedback.belongsTo(User); 75 | User.hasMany(Feedback); 76 | Feedback.belongsTo(Video); 77 | Video.hasMany(Feedback); 78 | 79 | // Set's up many-to-many relationship between Video and Tag (creates join table Video_Tag) 80 | // Tag.belongsToMany(Video, {through: 'Video_Tag'}); 81 | // Video.belongsToMany(Tag, {through: 'Video_Tag'}); 82 | 83 | // Sets up one-to-many relationship between User and Video 84 | Video.belongsTo(User); 85 | User.hasMany(Video); 86 | Video.belongsTo(Category); 87 | Category.hasMany(Video); 88 | // Syncs schemas with mysql, creating the actual tables in the DB 89 | User.sync() 90 | .then(function() { 91 | Video.sync() 92 | .then(function() { 93 | Category.sync() 94 | .then(function() { 95 | Feedback.sync() 96 | .then(function() { 97 | Votes.sync() 98 | .then(function() { 99 | Question.sync() 100 | .then(function() { 101 | WatchListVideo.sync() 102 | .then(function() { 103 | console.log('Tables successfully created'); 104 | }) 105 | .catch(function(err) { 106 | throw err; 107 | }); 108 | }) 109 | .catch(function(err) { 110 | throw err; 111 | }); 112 | }) 113 | .catch(function(err) { 114 | }); 115 | }) 116 | .catch(function(err) { 117 | throw err; 118 | }); 119 | }) 120 | .catch(function(err) { 121 | throw err; 122 | }); 123 | }) 124 | .catch(function(err) { 125 | throw err; 126 | }); 127 | }) 128 | .catch(function(err) { 129 | throw err; 130 | }); 131 | 132 | exports.Feedback = Feedback; 133 | exports.User = User; 134 | exports.Video = Video; 135 | exports.Votes = Votes; 136 | exports.Question = Question; 137 | exports.Category = Category; 138 | exports.WatchListVideo = WatchListVideo; 139 | exports.db = db; -------------------------------------------------------------------------------- /client/components/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import SearchBar from './SearchBar.jsx'; 3 | import {fetchVideoList, toggleSignInModal, toggleSignUpModal, toggleUploadModal, signOutUser} from '../actions/actions.jsx' 4 | import {connect} from 'react-redux'; 5 | import $ from 'jquery'; 6 | import FontIcon from 'material-ui/lib/font-icon'; 7 | import NavigationExpandMoreIcon from 'material-ui/lib/svg-icons/navigation/expand-more'; 8 | import RaisedButton from 'material-ui/lib/raised-button'; 9 | import Toolbar from 'material-ui/lib/toolbar/toolbar'; 10 | import ToolbarGroup from 'material-ui/lib/toolbar/toolbar-group'; 11 | import ToolbarSeparator from 'material-ui/lib/toolbar/toolbar-separator'; 12 | import ToolbarTitle from 'material-ui/lib/toolbar/toolbar-title'; 13 | import TextField from 'material-ui/lib/text-field'; 14 | import IconButton from 'material-ui/lib/icon-button'; 15 | 16 | 17 | const mapStateToProps = (state) => { 18 | return { 19 | state: state, 20 | displaySignInModal: state.displaySignInModal, 21 | displaySignOutModal: state.displaySignOutModal, 22 | user: state.user 23 | } 24 | }; 25 | 26 | const mapDispatchToProps = (dispatch) => { 27 | return { 28 | handleSubmit: (query, queryType) => { 29 | $.post('/search', {query: query, queryType:queryType}).done(function(res){ 30 | dispatch(fetchVideoList(res)) 31 | window.location = '/#/search' 32 | }) 33 | }, 34 | goHome: () => { 35 | window.location = '/#/'; 36 | }, 37 | goProfile: () => { 38 | window.location = '/#/profile' 39 | }, 40 | signOut: () => { 41 | console.log('Signing out user') 42 | dispatch(signOutUser()) 43 | window.location = '/#/' 44 | }, 45 | showSignInModal: () => { 46 | console.log('Showing SignInModal') 47 | dispatch(toggleSignInModal()) 48 | }, 49 | showSignUpModal: () => { 50 | console.log('Showing SignUpModal') 51 | dispatch(toggleSignUpModal()) 52 | }, 53 | showUploadModal: () => { 54 | console.log('Showing UploadModal') 55 | dispatch(toggleUploadModal()) 56 | } 57 | }; 58 | } 59 | 60 | class NavBar extends Component { 61 | 62 | 63 | render(){ 64 | 65 | const buttonStyles = { 66 | margin: 10, 67 | marginTop: 18 68 | } 69 | 70 | var noAuth = 71 | this.props.showSignInModal()}/> 72 | this.props.showSignUpModal()}/> 73 | 74 | 75 | var Auth = 76 | this.props.showUploadModal()}/> 77 | this.props.goProfile()}/> 78 | this.props.signOut()}/> 79 | 80 | 81 | 82 | return ( 83 | 87 | 88 | this.props.goHome()}/> 89 | 90 | 91 | 92 | 93 | 94 | {this.props.user.username !== undefined ? Auth : noAuth } 95 | 96 | ); 97 | } 98 | } 99 | 100 | 101 | export default connect(mapStateToProps, mapDispatchToProps)(NavBar); 102 | -------------------------------------------------------------------------------- /client/components/Q_ATab.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import TextField from 'material-ui/lib/text-field'; 3 | import RaisedButton from 'material-ui/lib/raised-button'; 4 | 5 | 6 | export default class Q_ATab extends Component { 7 | 8 | render() { 9 | var questionsFound; 10 | var questions = this.props.questions; 11 | var asker = this.props.currentUser.username; 12 | var videoid = this.props.currentVideo.id; 13 | var video_userid = this.props.currentVideo.UserId; 14 | var userid = this.props.currentUser.id; 15 | var addQuestion = this.props.addQuestion; 16 | var addAnswer = this.props.addAnswer; 17 | var showAnswerEdit = this.props.showAnswerEdit; 18 | var hideAnswerEdit = this.props.hideAnswerEdit; 19 | var answerEdit = this.props.answerEdit; 20 | 21 | /* 22 | * This code block determines the structure and style of each question for a given video. 23 | * 'questions' is initially an empty object (before becoming an array of 24 | * feedback objects) so it must be verified as an array before trying to map it 25 | */ 26 | if (Array.isArray(questions)) { 27 | var _this = this; 28 | questionsFound = ( 29 |
30 | { questions.map(function (question){ 31 | return( 32 |
33 |
34 |

{question.asker}

35 |

{question.question}

36 |

{question.answer ? question.answer : ""}

37 | {/* 38 | If current user is creator of current video, and either answerEdit hasn't been set yet (starts as an object, have to 39 | check this before checking length) or the question isn't being edited currently, the 'Answer' (or 'Edit') 40 | button will render 41 | */} 42 | { 43 | (userid === video_userid && (typeof answerEdit === 'object' || answerEdit.length === 0)) ? (question.answer ? 44 | showAnswerEdit(question.id)}/> : 45 | showAnswerEdit(question.id)}/>) : "" 46 | } 47 | {/* 48 | If answerEdit is equal to the current question's id (user is 'Answer'ing this question) a text field with associated 'Submit' 49 | and 'Cancel' buttons will render underneath the question. Text field will be pre-populated with existing answer if one exists. 50 | */} 51 | { 52 | answerEdit === question.id ? 53 | (
54 | 62 | addAnswer(_this.refs.answer.getValue(), question.id, videoid)}/> 63 | hideAnswerEdit()}/> 64 |
) : "" 65 | } 66 |
67 | ); 68 | })} 69 |
70 | ); 71 | } 72 | 73 | return ( 74 |
75 | 83 | addQuestion(this.refs.question.getValue(), asker, videoid, userid)}/> 84 | {questionsFound} 85 |
86 | ) 87 | }; 88 | } -------------------------------------------------------------------------------- /client/components/CategoriesBar.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import $ from 'jquery'; 4 | 5 | import IconMenu from 'material-ui/lib/menus/icon-menu'; 6 | import IconButton from 'material-ui/lib/icon-button'; 7 | import FontIcon from 'material-ui/lib/font-icon'; 8 | import NavigationExpandMoreIcon from 'material-ui/lib/svg-icons/navigation/expand-more'; 9 | import MenuItem from 'material-ui/lib/menus/menu-item'; 10 | import DropDownMenu from 'material-ui/lib/DropDownMenu'; 11 | import RaisedButton from 'material-ui/lib/raised-button'; 12 | import FlatButton from 'material-ui/lib/flat-button'; 13 | import Toolbar from 'material-ui/lib/toolbar/toolbar'; 14 | import ToolbarGroup from 'material-ui/lib/toolbar/toolbar-group'; 15 | import ToolbarSeparator from 'material-ui/lib/toolbar/toolbar-separator'; 16 | import ToolbarTitle from 'material-ui/lib/toolbar/toolbar-title'; 17 | import {fetchVideoList} from '../actions/actions.jsx' 18 | 19 | export default class CategoriesBar extends React.Component { 20 | 21 | constructor(props) { 22 | super(props); 23 | } 24 | 25 | render() { 26 | 27 | return ( 28 | 29 | 32 | 33 | {this.props.searchByCategory(8)}} /> 35 | 36 | {this.props.searchByCategory(1)}} /> 38 | 39 | {this.props.searchByCategory(12)}} /> 41 | 42 | {this.props.searchByCategory(14)}} /> 44 | 45 | {this.props.searchByCategory(3)}} /> 47 | 48 | {this.props.searchByCategory(11)}} /> 50 | 51 | {this.props.searchByCategory(4)}} /> 53 | 54 | 55 | 56 | 58 | 59 | 60 | } 61 | anchorOrigin={{horizontal: 'right', vertical: 'top'}} 62 | targetOrigin={{horizontal: 'right', vertical: 'top'}} 63 | onChange={(evt, index, item) => {this.props.searchByCategory(item)}} 64 | backgroundColor='#4CAF50' 65 | > 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ) 79 | 80 | } 81 | 82 | }; 83 | 84 | const mapStateToProps = (state) => { 85 | return { 86 | categories: state.categories 87 | } 88 | }; 89 | 90 | const mapDispatchToProps = (dispatch) => { 91 | return { 92 | searchByCategory: (categoryId) => { 93 | $.post('/search', {query: categoryId, queryType: 'CategoryId'}).done(function(res){ 94 | dispatch(fetchVideoList(res)) 95 | window.location = '/#/search' 96 | }) 97 | } 98 | } 99 | }; 100 | 101 | export default connect(mapStateToProps, mapDispatchToProps)(CategoriesBar); -------------------------------------------------------------------------------- /client/reducers/reducer.jsx: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import $ from 'jquery'; 3 | 4 | const CurrentVideo = (state = {}, action) => { 5 | var video = Object.assign({},state); 6 | switch (action.type) { 7 | case 'SELECT_VIDEO': 8 | return action.data; 9 | case 'UP_VOTE': 10 | console.log("videois:", video); 11 | video.upVotes = action.payload; 12 | return video; 13 | case 'DOWN_VOTE': 14 | video.downVotes = action.payload; 15 | return video; 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | const VideoList = (state = {}, action) => { 22 | switch (action.type) { 23 | case 'FETCH_VIDEOS': 24 | return action.videos; 25 | default: 26 | return state; 27 | } 28 | } 29 | 30 | 31 | const User = (state = {}, action) => { 32 | var user = action.data; 33 | switch(action.type) { 34 | case 'CHANGE_USER': 35 | user = action.user; 36 | return user; 37 | case 'UPDATE_ABOUTME': 38 | user.aboutMe = action.data.aboutMe; 39 | return user; 40 | default: 41 | return state; 42 | } 43 | } 44 | 45 | 46 | const ToggleAboutMeEdit= (state = {}, action) => { 47 | switch(action.type) { 48 | case 'SHOW_ABOUTME_EDIT': 49 | return true; 50 | case "HIDE_ABOUTME_EDIT": 51 | return false; 52 | default: 53 | return state; 54 | } 55 | } 56 | 57 | const ToggleAnswerEdit= (state = {}, action) => { 58 | switch(action.type) { 59 | case 'SHOW_ANSWER_EDIT': 60 | return action.question; 61 | case "HIDE_ANSWER_EDIT": 62 | return ""; 63 | default: 64 | return state; 65 | } 66 | } 67 | 68 | 69 | const SignInModal = (state = {}, action) => { 70 | switch (action.type) { 71 | case 'SHOW_SIGNIN_MODAL': 72 | return true; 73 | case 'HIDE_SIGNIN_MODAL': 74 | return false; 75 | default: 76 | return state; 77 | } 78 | } 79 | 80 | const SignUpModal = (state = {}, action) => { 81 | switch (action.type) { 82 | case 'SHOW_SIGNUP_MODAL': 83 | return true; 84 | case 'HIDE_SIGNUP_MODAL': 85 | return false; 86 | default: 87 | return state; 88 | } 89 | } 90 | 91 | const UploadModal = (state = {}, action) => { 92 | switch (action.type) { 93 | case 'SHOW_UPLOAD_MODAL': 94 | return true; 95 | case 'HIDE_UPLOAD_MODAL': 96 | return false; 97 | default: 98 | return state; 99 | } 100 | } 101 | 102 | const Feedback = (state = {}, action) => { 103 | switch (action.type) { 104 | case 'LOAD_FEEDBACK': 105 | return action.payload; 106 | default: 107 | return state; 108 | } 109 | } 110 | 111 | const Categories = (state = {}, action) => { 112 | switch (action.type) { 113 | case 'LOAD_CATEGORIES': 114 | return action.categories; 115 | default: 116 | return state; 117 | } 118 | } 119 | 120 | const Questions = (state = {}, action) => { 121 | switch(action.type) { 122 | case 'LOAD_QUESTIONS' : 123 | return action.payload; 124 | default: 125 | return state; 126 | } 127 | } 128 | 129 | const CheckVideoDuration = (state = {}, action) => { 130 | var videoCheck = Object.assign({},state); 131 | switch(action.type) { 132 | case 'START_VIDEO_DURATION_CHECK': 133 | videoCheck.checking = true; 134 | videoCheck.videoURL = action.videoURL; 135 | videoCheck.filename = action.filename; 136 | return videoCheck; 137 | case 'STOP_VIDEO_DURATION_CHECK': 138 | videoCheck.checking = false; 139 | return videoCheck; 140 | case 'START_UPLOAD_PROGRESS': 141 | videoCheck.proccessing = true; 142 | return videoCheck; 143 | case 'STOP_UPLOAD_PROGRESS': 144 | videoCheck.proccessing = false; 145 | return videoCheck; 146 | default: 147 | return state; 148 | } 149 | } 150 | 151 | const VideoIsValidated = (state = {}, action) => { 152 | switch(action.type) { 153 | case 'VIDEO_VALIDATED_TRUE': 154 | return true; 155 | case 'VIDEO_VALIDATED_FALSE': 156 | return false; 157 | case 'VIDEO_VALIDATED_RESET': 158 | return {}; 159 | default: 160 | return state; 161 | } 162 | } 163 | 164 | const AuthError = (state = {}, action) => { 165 | switch(action.type) { 166 | case 'AUTH_ERROR': 167 | return action.error; 168 | default: 169 | return state; 170 | } 171 | } 172 | 173 | const CategoriesMenu = (state = {}, action) => { 174 | switch(action.type) { 175 | case 'CATEGORIES_MENU_CHANGE': 176 | return action.categoryid 177 | default: 178 | return state; 179 | } 180 | } 181 | 182 | const VideoAppHandler = combineReducers({ 183 | currentVideo: CurrentVideo, 184 | videos: VideoList, 185 | user: User, 186 | categories: Categories, 187 | feedback: Feedback, 188 | questions: Questions, 189 | displaySignInModal: SignInModal, 190 | displaySignUpModal: SignUpModal, 191 | displayUploadModal: UploadModal, 192 | aboutMeEdit: ToggleAboutMeEdit, 193 | answerEdit: ToggleAnswerEdit, 194 | checkVideoDuration: CheckVideoDuration, 195 | videoIsValidated: VideoIsValidated, 196 | authError: AuthError, 197 | categoriesMenu: CategoriesMenu 198 | }); 199 | 200 | export default VideoAppHandler; -------------------------------------------------------------------------------- /client/styles/style.css: -------------------------------------------------------------------------------- 1 | #header { 2 | display: flex; 3 | height: 100px; 4 | } 5 | 6 | #box { 7 | width: 75% /* Or whatever */ 8 | margin: auto; 9 | } 10 | #sheet { 11 | background-color : #f0f0f5; 12 | width : 1200px; 13 | padding: 40px; 14 | margin: 0px; 15 | padding-top: 5px; 16 | padding-left: 120px; 17 | } 18 | #sheett { 19 | background-color : #8BC34A; 20 | display: flex; 21 | width : 1200px; 22 | padding: 0px; 23 | margin:auto; 24 | margin-top: 0px; 25 | overflow: hidden; 26 | } 27 | #cover { 28 | background-color : #8BC34A; 29 | width : 750px; 30 | height: 340px; 31 | margin: auto; 32 | padding: 30px; 33 | margin-top: 25px; 34 | /*display: flex;*/ 35 | /*align-items: center;*/*/ 36 | } 37 | 38 | 39 | #PlayerPage { 40 | background-color : #f0f0f5; 41 | } 42 | 43 | .welcomeBackTitle { 44 | padding-top: 5px; 45 | margin-top: 5px; 46 | padding-left: 12px; 47 | font-family:'Raleway'; 48 | color: #303F9F ; 49 | font-size: 20px; 50 | } 51 | 52 | #AboutMe { 53 | padding: 0px 10px 10px 10px; 54 | border:1px solid #303F9F ; 55 | background-color: white; 56 | height: 350px; 57 | width: 230px; 58 | margin: 10px 59 | margin-right: 20px; 60 | margin-left: 17px; 61 | font-family:'Raleway'; 62 | } 63 | 64 | 65 | 66 | #Playercover { 67 | background-color : #f0f0f5; 68 | width : 1200px; 69 | height: 380px; 70 | padding-left: 0px; 71 | padding-top: 0px; 72 | padding-bottom: 0px; 73 | margin:auto; 74 | margin-top: 0px; 75 | margin-bottom: 0px; 76 | 77 | } 78 | 79 | #description { 80 | background-color : white; 81 | margin-top: 20px; 82 | padding-left: 100px; 83 | padding-top: 40px; 84 | padding-bottom: 40px; 85 | width : 1200px; 86 | height: 200px; 87 | margin:auto; 88 | } 89 | 90 | #cardCover{ 91 | background-color : #8BC34A; 92 | margin: 0px; 93 | width : 1200px; 94 | height: 750px; 95 | padding: 20px; 96 | margin-bottom: 0px; 97 | 98 | } 99 | #tabs { 100 | margin-left: 0px; 101 | } 102 | #inner { 103 | max-width: 100%; 104 | max-height: 100%; 105 | /*#featured { 106 | width: 200px; Or whatever 107 | height: 200px; 108 | margin: auto; 109 | }*/ 110 | } 111 | 112 | #VideoGrid { 113 | width: 100px; /* Or whatever */ 114 | height: 100px; /* Or whatever */ 115 | margin: auto; 116 | } 117 | 118 | #durationCheck { 119 | z-index: -5; 120 | position: absolute; 121 | } 122 | 123 | .uploadTitle { 124 | margin-bottom: 5px; 125 | margin-top: 15px; 126 | } 127 | 128 | body { 129 | margin: 0; 130 | padding: 0; 131 | } 132 | .container { 133 | width: 100%; 134 | height: 0; 135 | padding-bottom: 56.25%; //16 x 9 136 | position: relative; 137 | .player { 138 | position: absolute; 139 | top: 0; 140 | width: 100%; 141 | height: 100%; 142 | } 143 | } 144 | .fill { 145 | display:flex; 146 | justify-content:center; 147 | align-items:center; 148 | overflow:hidden 149 | max-width: 600px; 150 | max-height: 300px; 151 | margin-top:10px; 152 | 153 | } 154 | .fill img { 155 | flex-shrink:0; 156 | min-width:100%; 157 | min-height:100%; 158 | float:left; 159 | 160 | } 161 | 162 | /*.video-play video__control { 163 | width = 30px; 164 | }*/ 165 | 166 | .homeImage { 167 | max-width: 100px; 168 | max-height: 100px; 169 | border: 3px solid #73AD21; 170 | } 171 | 172 | .homeImage img { 173 | max-width: 100%; 174 | max-height: 100%; 175 | } 176 | 177 | .videoGrid { 178 | display: flex; 179 | } 180 | 181 | /*.videoThumbnail { 182 | width: 250px; 183 | height: 250px; 184 | border: 3px solid #73AD21; 185 | margin: auto; 186 | }*/ 187 | 188 | .image_thumbnail { 189 | max-width: 250px; 190 | max-height: 250px; 191 | border: 3px solid #73AD21; 192 | } 193 | 194 | .image_thumbnail img { 195 | max-width: 100%; 196 | max-height: 100%; 197 | } 198 | 199 | /* =============== NavBar ================ */ 200 | 201 | .navContent { 202 | display: inline; 203 | } 204 | 205 | .navBar { 206 | background-color: #689F38; 207 | } 208 | 209 | .authBox { 210 | display: inline; 211 | float: right; 212 | } 213 | 214 | 215 | .searchForm { 216 | padding-bottom: 20px; 217 | } 218 | 219 | .searchTitle { 220 | font-size: 20px; 221 | } 222 | 223 | .navLeft { 224 | display: inline; 225 | float: left; 226 | width: 20%; 227 | } 228 | 229 | .navMiddle { 230 | display: inline; 231 | width: 50%; 232 | } 233 | 234 | .navRight { 235 | display: inline; 236 | width:30%; 237 | } 238 | 239 | .searchIcon { 240 | background-image: 241 | } 242 | 243 | body { 244 | background-color: #f0f0f5; 245 | } 246 | 247 | #HomePage { 248 | background-color : white; 249 | padding: 0px 0px 0px 0px; 250 | } 251 | 252 | .Nav { 253 | background-color: white; 254 | } 255 | 256 | 257 | .Votes { 258 | width: 25%; 259 | } 260 | 261 | title { 262 | font-size: 20px; 263 | } 264 | 265 | p { 266 | font-size: 35px; 267 | font-weight: bold; 268 | margin-top: 0px; 269 | margin-left: 60px; 270 | } 271 | 272 | .upvotes { 273 | margin-left: 20px; 274 | } 275 | 276 | div span.downvotes { 277 | margin-left: 70px; 278 | } 279 | 280 | .info { 281 | font-size: : 200px; 282 | background-color: #f0f0f5; 283 | padding-bottom: 37px; 284 | margin: auto; 285 | text-align:center 286 | } 287 | 288 | .tabs { 289 | background-color: #f0f0f5; 290 | } 291 | 292 | 293 | .feedback { 294 | background-color: white; 295 | width: 100%; 296 | } 297 | 298 | 299 | .videoplayer { 300 | margin-left: 225px; 301 | background-color: #f0f0f5; 302 | } 303 | 304 | .feedback { 305 | width: 80%; 306 | margin: auto; 307 | } 308 | 309 | .side { 310 | width: 80%; 311 | height: 100px; 312 | margin: auto; 313 | background-color: #f0f0f5; 314 | margin-bottom: 10px; 315 | } 316 | 317 | .Votes { 318 | width: 25%; 319 | } 320 | 321 | -------------------------------------------------------------------------------- /client/components/UploadModal.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Dialog from 'material-ui/lib/dialog'; 3 | import FlatButton from 'material-ui/lib/flat-button'; 4 | import TextField from 'material-ui/lib/text-field'; 5 | import RaisedButton from 'material-ui/lib/raised-button'; 6 | import FloatingActionButton from 'material-ui/lib/floating-action-button'; 7 | import ContentAdd from 'material-ui/lib/svg-icons/content/add'; 8 | import DropDownMenu from 'material-ui/lib/DropDownMenu'; 9 | import MenuItem from 'material-ui/lib/menus/menu-item'; 10 | import {connect} from 'react-redux'; 11 | import ReactS3Uploader from 'react-s3-uploader'; 12 | import VideoDurationValidater from './VideoDurationValidater.jsx' 13 | 14 | import { addVideo, hideUploadModal, loadCategories, startVideoDurationCheck, stopVideoDurationCheck, videoValidatedTrue } from '../actions/actions.jsx'; 15 | import { videoValidatedFalse, videoValidatedReset, categoriesMenu, startUploadProgress, stopUploadProgress } from '../actions/actions.jsx'; 16 | 17 | export default class UploadModal extends Component { 18 | 19 | render() { 20 | var displayUploadModal = this.props.displayUploadModal; 21 | var user = this.props.user; 22 | var categories = this.props.categories 23 | var videoURL = this.props.checkVideoDuration.videoURL; 24 | var filename = this.props.checkVideoDuration.filename; 25 | var checking = this.props.checkVideoDuration.checking; 26 | var submitVideo = this.props.submitVideo; 27 | var closeModal = this.props.closeModal; 28 | var startVideoDurationCheck = this.props.startVideoDurationCheck; 29 | var stopVideoDurationCheck = this.props.stopVideoDurationCheck; 30 | var videoIsValidated = this.props.videoIsValidated; 31 | var videoValidatedTrue = this.props.videoValidatedTrue; 32 | var videoValidatedFalse = this.props.videoValidatedFalse; 33 | var videoValidatedReset = this.props.videoValidatedReset; 34 | var categoriesMenu = this.props.categoriesMenu; 35 | var categorySelected = this.props.categorySelected; 36 | var startUploadProgress = this.props.startUploadProgress; 37 | var stopUploadProgress = this.props.stopUploadProgress; 38 | var processing = this.props.checkVideoDuration.proccessing; 39 | console.log(processing); 40 | 41 | 42 | const items = []; 43 | for (let i = 0; i < categories.length; i++) { 44 | items.push(); 45 | } 46 | 47 | const style = { 48 | marginRight: 20, 49 | }; 50 | 51 | const customContentStyle = { 52 | width: 330, 53 | maxWidth: 'none', 54 | }; 55 | 56 | 57 | let coverUrl; 58 | 59 | const actions = [ 60 | { 64 | closeModal(); 65 | videoValidatedReset(); 66 | categoriesMenu({}); 67 | stopUploadProgress(); 68 | }} 69 | />, 70 | { 75 | submitVideo({title: this.refs.title.getValue(), description: this.refs.description.getValue(), cover: coverUrl, user: user, url: videoURL, categoryId: categorySelected}); 76 | videoValidatedReset(); 77 | categoriesMenu({}); 78 | }} 79 | /> 80 | ]; 81 | 82 | return ( 83 |
84 | 91 | 96 | 102 | Categories 103 | { 106 | categoriesMenu(item); 107 | }} 108 | ref="category"> 109 | {items} 110 | 111 |

Video File (.mp4)

112 | { 115 | if (percent > 0 && percent < 20) { 116 | startUploadProgress(); 117 | } 118 | }} 119 | onFinish={(videoResponse) => { 120 | var filename = videoResponse.filename; 121 | videoURL = 'https://s3-us-west-1.amazonaws.com/video.bucket1/' + filename; 122 | startVideoDurationCheck(videoURL, filename); 123 | stopUploadProgress(); 124 | }} 125 | />
126 | {videoIsValidated === true ? 127 | (

Thumbnail File (.jpg)

128 | { 131 | console.log('cover response', coverResponse.filename) 132 | coverUrl = 'https://s3-us-west-1.amazonaws.com/video.bucket1/' + coverResponse.filename; 133 | }} 134 | />
) : videoIsValidated === false ? 135 | (Your video was too long! Only videos under 5 minutes long may be uploaded.) 136 | : ""} 137 | {processing === true ? 138 | (Processing video...) 139 | : ""} 140 |
141 |
142 | {checking === true ? 143 | : 149 | ''} 150 |
151 |
152 | 153 | ); 154 | } 155 | } 156 | 157 | const mapStateToProps = (state) => { 158 | return { 159 | displayUploadModal: state.displayUploadModal, 160 | user: state.user, 161 | categories: state.categories, 162 | checkVideoDuration: state.checkVideoDuration, 163 | videoIsValidated: state.videoIsValidated, 164 | categorySelected: state.categoriesMenu 165 | } 166 | }; 167 | 168 | const mapDispatchToProps = (dispatch) => { 169 | return { 170 | // only handles adding the new video to the database; s3 uploader handles adding the video to s3 171 | submitVideo: (video) => { 172 | console.log('Submitting new video') 173 | dispatch(addVideo(video)) 174 | dispatch(hideUploadModal()) 175 | }, 176 | closeModal: () => { 177 | dispatch(hideUploadModal()) 178 | }, 179 | startVideoDurationCheck: (videoURL, filename) => { 180 | dispatch(startVideoDurationCheck(videoURL, filename)); 181 | }, 182 | stopVideoDurationCheck: () => { 183 | dispatch(stopVideoDurationCheck()); 184 | }, 185 | videoValidatedTrue: () => { 186 | dispatch(videoValidatedTrue()); 187 | }, 188 | videoValidatedFalse: () => { 189 | dispatch(videoValidatedFalse()); 190 | }, 191 | videoValidatedReset: () => { 192 | dispatch(videoValidatedReset()); 193 | }, 194 | categoriesMenu: (id) => { 195 | dispatch(categoriesMenu(id)); 196 | }, 197 | startUploadProgress: () => { 198 | dispatch(startUploadProgress()); 199 | }, 200 | stopUploadProgress: () => { 201 | dispatch(stopUploadProgress()); 202 | } 203 | }; 204 | }; 205 | 206 | export default connect(mapStateToProps, mapDispatchToProps)(UploadModal); -------------------------------------------------------------------------------- /client/actions/actions.jsx: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | export const changeVideo = (value) => { 4 | return { 5 | type: 'CHANGE_VIDEO', 6 | value: value 7 | }; 8 | }; 9 | 10 | export const fetchVideoList = (videos) => { 11 | return { 12 | type: 'FETCH_VIDEOS', 13 | videos: videos 14 | }; 15 | }; 16 | 17 | 18 | export const updateAboutMe = (data) => { 19 | return { 20 | type: 'UPDATE_ABOUTME', 21 | data:data 22 | 23 | } 24 | }; 25 | 26 | export const showAboutMeEdit = () => { 27 | return { 28 | type: 'SHOW_ABOUTME_EDIT', 29 | } 30 | }; 31 | 32 | export const hideAboutMeEdit = () => { 33 | return { 34 | type: 'HIDE_ABOUTME_EDIT', 35 | } 36 | }; 37 | 38 | 39 | export const changeCurrentVideo = (video) => { 40 | return { 41 | type: 'SELECT_VIDEO', 42 | data: video 43 | }; 44 | }; 45 | 46 | export const addToWatch = (info) => { 47 | return(dispatch) => { 48 | $.post('/addToWatch', info) 49 | } 50 | }; 51 | 52 | export const videoFetch = () => { 53 | return(dispatch) => { 54 | dispatch(fetchVideoList()); 55 | $.get('/fetch') 56 | .then((response) => 57 | { 58 | dispatch(receivedVideoList(response)); 59 | }); 60 | } 61 | }; 62 | 63 | export const changeUser = (user) => { 64 | return { 65 | type: 'CHANGE_USER', 66 | user: user 67 | } 68 | }; 69 | 70 | export const signInUser = (user) => { 71 | return(dispatch) => { 72 | $.post('/signin', user) 73 | .then((data) => 74 | { 75 | dispatch(changeUser(data)); 76 | dispatch(authError(null)); 77 | dispatch(hideSignInModal()); 78 | }, (error) => 79 | { 80 | dispatch(authError(error.responseText)); 81 | }); 82 | } 83 | }; 84 | 85 | export const signOutUser = () => { 86 | return(dispatch) => { 87 | $.get('/signout') 88 | .then((response) => 89 | { 90 | dispatch(changeUser({})); 91 | }); 92 | } 93 | }; 94 | 95 | export const signUpUser = (user) => { 96 | return(dispatch) => { 97 | $.post('/signup', user) 98 | .then((response) => 99 | { 100 | dispatch(signInUser(user)); 101 | dispatch(changeUser(response)); 102 | dispatch(authError(null)); 103 | dispatch(hideSignUpModal()); 104 | }, (error) => 105 | { 106 | dispatch(authError(error.responseText)); 107 | }); 108 | } 109 | }; 110 | 111 | export const authError = (error) => { 112 | return { 113 | type: 'AUTH_ERROR', 114 | error: error 115 | } 116 | } 117 | 118 | export const addVideo = (video) => { 119 | return(dispatch) => { 120 | $.post('/addVideo', video) 121 | .then((response) => 122 | { 123 | dispatch(changeCurrentVideo(response)); 124 | }); 125 | } 126 | }; 127 | 128 | export const toggleSignInModal= () => { 129 | return { 130 | type: 'SHOW_SIGNIN_MODAL', 131 | } 132 | }; 133 | 134 | export const hideSignInModal= () => { 135 | return { 136 | type: 'HIDE_SIGNIN_MODAL', 137 | } 138 | }; 139 | 140 | export const toggleSignUpModal= () => { 141 | return { 142 | type: 'SHOW_SIGNUP_MODAL', 143 | } 144 | }; 145 | 146 | export const hideSignUpModal= () => { 147 | return { 148 | type: 'HIDE_SIGNUP_MODAL', 149 | } 150 | }; 151 | 152 | export const loadAllFeedback = (feedback) => { 153 | return { 154 | type: 'LOAD_FEEDBACK', 155 | payload: feedback 156 | } 157 | }; 158 | 159 | export const loadQuestions = (videoid) => { 160 | return(dispatch) => { 161 | $.post('/loadQuestions', {videoid:videoid}) 162 | .then((questions) => 163 | { 164 | dispatch(loadAllQuestions(questions)); 165 | }); 166 | } 167 | }; 168 | 169 | export const loadAllQuestions = (questions) => { 170 | return { 171 | type: 'LOAD_QUESTIONS', 172 | payload: questions 173 | } 174 | }; 175 | 176 | export const toggleUploadModal= () => { 177 | return { 178 | type: 'SHOW_UPLOAD_MODAL', 179 | } 180 | }; 181 | 182 | export const hideUploadModal= () => { 183 | return { 184 | type: 'HIDE_UPLOAD_MODAL', 185 | } 186 | }; 187 | 188 | export const loadFeedback = (videoid) => { 189 | return(dispatch) => { 190 | $.post('/loadFeedback', {videoid:videoid}) 191 | .then((feedback) => 192 | { 193 | dispatch(loadAllFeedback(feedback)); 194 | }); 195 | } 196 | }; 197 | 198 | export const addFeedback = (feedback, username, videoID, userID) => { 199 | var newFeedback = { 200 | feedback: feedback, 201 | username: username, 202 | videoID: videoID, 203 | userID: userID 204 | }; 205 | return(dispatch) => { 206 | $.post('/addFeedback', newFeedback) 207 | .then(() => 208 | { 209 | dispatch(loadFeedback(videoID)); 210 | }); 211 | } 212 | }; 213 | 214 | export const addQuestion = (question, asker, videoID, userID) => { 215 | var newQuestion = { 216 | content: question, 217 | asker: asker, 218 | videoID: videoID, 219 | userID: userID 220 | }; 221 | return(dispatch) => { 222 | $.post('/addQuestion', newQuestion) 223 | .then(() => 224 | { 225 | dispatch(loadQuestions(videoID)); 226 | }); 227 | } 228 | }; 229 | 230 | export const loadCategories = () => { 231 | return(dispatch) => { 232 | $.get('/loadCategories') 233 | .then((categories) => 234 | { 235 | dispatch(populateCategories(categories)); 236 | }); 237 | } 238 | }; 239 | 240 | export const populateCategories = (categories) => { 241 | return { 242 | type: 'LOAD_CATEGORIES', 243 | categories: categories 244 | } 245 | }; 246 | 247 | export const upVote = (userID,videoID) => { 248 | var vote = { 249 | userID: userID, 250 | videoID:videoID 251 | }; 252 | return(dispatch) => { 253 | $.post('/upVote', vote) 254 | .then((voteCount) => 255 | { 256 | dispatch(upVoteMore(voteCount)); 257 | }); 258 | } 259 | } 260 | 261 | export const upVoteMore = (voteCount) => { 262 | return{ 263 | type:'UP_VOTE', 264 | payload:voteCount 265 | } 266 | } 267 | 268 | /*******************/ 269 | 270 | export const downVote = (userID,videoID) => { 271 | console.log("from down container"); 272 | var vote = { 273 | userID: userID, 274 | videoID:videoID 275 | }; 276 | return(dispatch) => { 277 | $.post('/downVote', vote) 278 | .then((voteCount) => 279 | { 280 | console.log("from action: ",voteCount.upVotes); 281 | dispatch(upVoteMore(voteCount.upVotes)); 282 | }); 283 | } 284 | } 285 | 286 | export const downVoteMore = (voteCount) => { 287 | return{ 288 | type:'DOWN_VOTE', 289 | payload:voteCount 290 | } 291 | } 292 | 293 | export const addAnswer = (answer, questionID, videoID) => { 294 | return(dispatch) => { 295 | $.post('/addAnswer', {answer: answer, questionID: questionID}) 296 | .then(() => 297 | { 298 | dispatch(loadQuestions(videoID)); 299 | }); 300 | } 301 | }; 302 | 303 | export const showAnswerEdit = (questionID) => { 304 | return { 305 | type: 'SHOW_ANSWER_EDIT', 306 | question: questionID 307 | } 308 | }; 309 | 310 | export const hideAnswerEdit = () => { 311 | return { 312 | type: 'HIDE_ANSWER_EDIT' 313 | } 314 | }; 315 | 316 | export const startVideoDurationCheck = (videoURL, filename) => { 317 | return { 318 | type: 'START_VIDEO_DURATION_CHECK', 319 | videoURL: videoURL, 320 | filename: filename 321 | } 322 | }; 323 | 324 | export const stopVideoDurationCheck = () => { 325 | return { 326 | type: 'STOP_VIDEO_DURATION_CHECK' 327 | } 328 | }; 329 | 330 | export const videoValidatedTrue = () => { 331 | return { 332 | type: 'VIDEO_VALIDATED_TRUE' 333 | } 334 | } 335 | 336 | export const videoValidatedFalse = () => { 337 | return { 338 | type: 'VIDEO_VALIDATED_FALSE' 339 | } 340 | } 341 | 342 | export const videoValidatedReset = () => { 343 | return { 344 | type: 'VIDEO_VALIDATED_RESET' 345 | } 346 | } 347 | 348 | export const categoriesMenu = (categoryid) => { 349 | return { 350 | type: 'CATEGORIES_MENU_CHANGE', 351 | categoryid: categoryid 352 | } 353 | } 354 | 355 | export const startUploadProgress = () => { 356 | return { 357 | type: 'START_UPLOAD_PROGRESS' 358 | } 359 | } 360 | 361 | export const stopUploadProgress = () => { 362 | return { 363 | type: 'STOP_UPLOAD_PROGRESS' 364 | } 365 | } --------------------------------------------------------------------------------