├── 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 |
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 =
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 | {this.props.aboutMe}
65 | this.props.updateUserInfo(this.refs.aboutMe.value, this.props.user,this.props.aboutMeEdit)}>
66 | Save Changes
67 |
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 | }
--------------------------------------------------------------------------------