├── db
└── pgdb.js
├── Procfile
├── .babelrc
├── .gitignore
├── src
├── index.js
├── components
│ ├── SongPlayTile.jsx
│ ├── SongQueueTile.jsx
│ ├── SongResultTile.jsx
│ ├── SongQueue.jsx
│ ├── SongPlayer.jsx
│ └── SongSearch.jsx
└── slick.jsx
├── .travis.yml
├── .eslintrc
├── test
├── dom-mock.js
├── client_test
│ └── test.js
└── test.js
├── index.html
├── privateKeys.js
├── LICENSE
├── Gulpfile.js
├── server
├── test
│ ├── mock-server.js
│ └── server-test.js
├── server.js
└── controllers
│ ├── songsController.js
│ └── parseHelpers.js
├── package.json
├── README.md
└── styles
└── home.css
/db/pgdb.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node server/server.js
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015"],
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | node_modules/
3 | npm-debug.log
4 | privateKeys.js
5 | .DS_Store
6 | /*.env
7 | bundle.js
8 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import Slick from './slick.jsx';
4 |
5 | render(, document.getElementById('content'));
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | - "5.1"
5 | - "4"
6 | - "4.2"
7 | - "4.1"
8 | - "4.0"
9 | - "0.12"
10 | - "0.11"
11 | - "0.10"
12 | - "iojs"
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-use-before-define": 0,
5 | "no-param-reassign": 0,
6 | "no-console": 0,
7 | "react/sort-comp": 0,
8 | "max-len": 0
9 | },
10 | "globals": {
11 | "io": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/dom-mock.js:
--------------------------------------------------------------------------------
1 | module.exports = function(markup) {
2 | if (typeof document !== 'undefined') return;
3 |
4 | var jsdom = require('jsdom').jsdom;
5 |
6 | global.document = jsdom(markup || '');
7 | global.window = document.defaultView;
8 | global.navigator = {
9 | userAgent: 'node.js'
10 | };
11 | };
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Slick
6 |
7 |
8 |
9 | slick
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/privateKeys.js:
--------------------------------------------------------------------------------
1 | const privateKeys = {
2 | googleApiKey: 'AIzaSyBCv_kW7-ggyzKlfVHYfDVzAF5V3M2uwRM',
3 | // Gracenote
4 | clientId: '1064631179',
5 | clientTag: 'D53C77DB2157936F00C4BE8D2AADAF12',
6 | userId: '87071795693237026-C14D11FA6E840FCCE3D1129E34AC8208',
7 |
8 | ClientIdSpotify: "a22cb34e6ab84becac3a72b819ec4f67",
9 | ClientSecretSpotify: "b2644472d210462c816ea397060c99c6",
10 | // Database
11 | postgresURI: 'postgres://usewxxncwyaejx:tZJCuwYOCU0QArl3JUv9_gyE-n@ec2-23-21-164-237.compute-1.amazonaws.com:5432/d4imhd4libvku4'
12 | };
13 |
14 | module.exports = privateKeys;
15 |
--------------------------------------------------------------------------------
/src/components/SongPlayTile.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const SongPlayTile = ({ currSong: { albumImg, artist, title, album } }) => (
4 |
5 |

6 |
7 | - {artist}
8 | - {title}
9 | - {album}
10 |
11 |
12 | );
13 |
14 | SongPlayTile.propTypes = {
15 | currSong: PropTypes.object,
16 | albumImg: PropTypes.string,
17 | artist: PropTypes.string,
18 | title: PropTypes.string,
19 | album: PropTypes.string,
20 | };
21 |
22 | export default SongPlayTile;
23 |
--------------------------------------------------------------------------------
/test/client_test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const mocha = require('mocha');
3 | const expect = require('chai').expect
4 | const sd = require('skin-deep');
5 |
6 | describe('Slick' , function() {
7 | let vdom, instance, tree;
8 | //'put functions/stuff here that we need'
9 |
10 |
11 | describe('stuff we are doing with slick component or something', function() {
12 | beforeEach(function() {
13 | tree = sd.shallowRender(
14 |
15 | );
16 | instance = tree.getMountedInstance();
17 | vdom = tree.getRenderOutput();
18 | });
19 |
20 |
21 | it('should do something', function() {
22 | expect(vdom.state.songInfo).to.not.eql([])
23 | })
24 |
25 |
26 | })
27 |
28 |
29 | })
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | })
40 |
--------------------------------------------------------------------------------
/src/components/SongQueueTile.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const SongQueueTile = ({ handleNewSongClick, itemNum, albumImg, artist, title, album, numberOfSongs }) => {
4 | const newSongClick = () => {
5 | handleNewSongClick(itemNum);
6 | };
7 |
8 | return (
9 |
10 |

{artist} - {title} - {album} - {Number(itemNum) + 1} of {numberOfSongs}
11 |
12 | );
13 | };
14 |
15 | SongQueueTile.propTypes = {
16 | handleNewSongClick: PropTypes.func,
17 | itemNum: PropTypes.number,
18 | artist: PropTypes.string,
19 | title: PropTypes.string,
20 | album: PropTypes.string,
21 | albumImg: PropTypes.string,
22 | numberOfSongs: PropTypes.number,
23 | };
24 |
25 | export default SongQueueTile;
26 |
--------------------------------------------------------------------------------
/src/components/SongResultTile.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const SongResultTile = ({ addSongToQueue, itemNum, albumImg, artist, title, album }) => {
4 | const addSong = () => {
5 | addSongToQueue(itemNum);
6 | };
7 |
8 | return (
9 |
10 |

11 |
12 | - {artist}
13 | - {title}
14 | - {album}
15 |
16 |
17 | );
18 | };
19 |
20 | SongResultTile.propTypes = {
21 | addSongToQueue: PropTypes.func,
22 | itemNum: PropTypes.number,
23 | artist: PropTypes.string,
24 | title: PropTypes.string,
25 | album: PropTypes.string,
26 | albumImg: PropTypes.string,
27 | };
28 |
29 |
30 | export default SongResultTile;
31 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | require('./dom-mock')('');
4 |
5 | const jsdom = require('mocha-jsdom');
6 | const assert = require('assert');
7 | const React = require('react');
8 | const TestUtils = require('react-addons-test-utils');
9 |
10 | describe('Testing my div', function() {
11 | jsdom({ skipWindowCheck: true });
12 |
13 | beforeEach(function() {
14 | const testing = require('..src/bundle.js')
15 | })
16 |
17 | it('should contain text: Lovely! Here it is - my very first React component!', function() {
18 |
19 | const asdf = TestUtils.renderIntoDocument(
20 |
21 | );
22 |
23 | var divText = TestUtils.findRenderedDOMComponentWithTag(
24 | myDiv, 'span');
25 |
26 | assert.equal(divText.textContent, 'Lovely! Here it is - my very first React component!');
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/components/SongQueue.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import SongQueueTile from './SongQueueTile.jsx';
3 |
4 | const SongQueue = ({ songInfo, handleNewSongClick }) => {
5 | // iterating over json to make song divs
6 | const songList = songInfo.map((song, i) =>
7 | ()
19 | );
20 |
21 | return (
22 |
23 | {songList}
24 |
25 | );
26 | };
27 |
28 | SongQueue.propTypes = {
29 | songInfo: PropTypes.array,
30 | handleNewSongClick: PropTypes.func,
31 | };
32 |
33 | export default SongQueue;
34 |
--------------------------------------------------------------------------------
/src/components/SongPlayer.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import YouTube from 'react-youtube';
3 | import SongPlayTile from './SongPlayTile.jsx';
4 |
5 | const opts = {
6 | height: '390',
7 | width: '640',
8 | playerVars: {
9 | autoplay: 1,
10 | fs: 0,
11 | enablejsapi: 1,
12 | },
13 | };
14 |
15 | const SongPlayer = ({ currSong, onReady, onPlay, onPause, onEnded }) => (
16 |
17 |
25 |
26 |
27 | );
28 |
29 | SongPlayer.propTypes = {
30 | currSong: PropTypes.object,
31 | onReady: PropTypes.func,
32 | onPlay: PropTypes.func,
33 | onPause: PropTypes.func,
34 | onEnded: PropTypes.func,
35 | };
36 |
37 | export default SongPlayer;
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 cs-slick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const browserify = require('browserify');
3 | const source = require('vinyl-source-stream');
4 | const watchify = require('watchify');
5 | const gutil = require('gulp-util');
6 | const nodemon = require('gulp-nodemon');
7 | const uglify = require('gulp-uglify');
8 | const streamify = require('gulp-streamify');
9 | const notify = require('gulp-notify');
10 | // const gzip = require('gulp-gzip');
11 |
12 | const b = watchify(browserify({
13 | entries: './src/index.js',
14 | cache: {},
15 | packageCache: {},
16 | debug: true,
17 | require: ['react', 'react-dom'],
18 | }));
19 |
20 | gulp.task('watch:js', bundle);
21 | b.on('update', bundle);
22 |
23 | function bundle() {
24 | return b
25 | .transform('babelify', { presets: ['es2015', 'react'] })
26 | .bundle()
27 | .on('error', gutil.log)
28 | .pipe(source('bundle.js'))
29 | .pipe(streamify(uglify('./dist/')))
30 | // .pipe(streamify(gzip({ append: true })))
31 | .pipe(gulp.dest('./'))
32 | .pipe(notify('Built Bundle'));
33 | }
34 |
35 | gulp.task('nodemon', serve);
36 |
37 | function serve() {
38 | nodemon({
39 | script: 'server/server.js',
40 | ignore: ['client/', 'build/'],
41 | });
42 | }
43 |
44 |
45 | gulp.task('default', ['watch:js', 'nodemon']);
46 |
--------------------------------------------------------------------------------
/src/components/SongSearch.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import SongResultTile from './SongResultTile.jsx';
3 |
4 | const SongSearch = ({ addSongToQueue, searchResults, handleSearchEvent }) => {
5 | const searchTileArray = searchResults.map((result, i) =>
6 |
17 | );
18 | return (
19 |
20 |
29 |
30 | {(searchTileArray.length > 0) ? searchTileArray : null}
31 |
32 |
33 | );
34 | };
35 |
36 | SongSearch.propTypes = {
37 | addSongToQueue: PropTypes.func,
38 | searchResults: PropTypes.array,
39 | handleSearchEvent: PropTypes.func,
40 | };
41 |
42 | export default SongSearch;
43 |
--------------------------------------------------------------------------------
/server/test/mock-server.js:
--------------------------------------------------------------------------------
1 | var io = require('socket.io').listen(5000);
2 | var clients = {};
3 |
4 | var verboseServer = false; //Good for debugging the server
5 |
6 | verboseServer && console.log("ChatServer Started"); //If Verbose Debug
7 | io.sockets.on('connection', function (socket) {
8 | socket.on('client connect', user => {
9 | io.sockets.emit('playSong', {user: 'Mike', action: 'play', data: 'kanyewest.mp3'});
10 | });
11 | // verboseServer && console.log("New Connection"); //If Verbose Debug
12 | // var userName;
13 | // socket.on('connection name',function(user){
14 | // verboseServer && console.log("Connection Name"); //If Verbose Debug
15 | // userName = user.name;
16 | // clients[user.name] = socket;
17 | // io.sockets.emit('new user', user.name + " has joined.");
18 | // });
19 | //
20 | // socket.on('message', function(msg){
21 | // verboseServer && console.log("New msg"); //If Verbose Debug
22 | // io.sockets.emit('message', msg);
23 | // });
24 | //
25 | // socket.on('private message', function(msg){
26 | // verboseServer && console.log("New PM"); //If Verbose Debug
27 | // fromMsg = {from:userName, txt:msg.txt}
28 | // clients[msg.to].emit('private message', fromMsg);
29 | // });
30 | //
31 | // socket.on('disconnect', function(){
32 | // verboseServer && console.log("disconnect"); //If Verbose Debug
33 | // delete clients[userName];
34 | // });
35 | });
36 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const server = app.listen(process.env.PORT || 3000);
4 | const io = require('socket.io')(server);
5 | const songsController = require('./controllers/songsController');
6 | const cors = require('cors');
7 | const bodyParser = require('body-parser');
8 |
9 | app.use(cors());
10 | // body parser middleware
11 | app.use(bodyParser.urlencoded({ extended: true }));
12 | // setting up path directory and going up one level
13 | app.use(express.static(`${__dirname}/..`));
14 | // sending the html file
15 | app.get('/', (req, res) => {
16 | res.sendFile('index.html');
17 | });
18 | // client logic will request this API once index.html loads
19 | // to retrieve stock song queue to generate list of songs
20 | // to render on page
21 | app.get('/songQueue', (req, res) => {
22 | res.json(songsController.playerState);
23 | });
24 | app.get('/favicon.ico', (req, res) => {
25 | res.send();
26 | });
27 | app.get('/untitled', (req, res) => {
28 | res.send();
29 | });
30 | app.post('/search', songsController.getSpotifyData, songsController.getYouTubeData, (req, res) => {
31 | // res.writeHeader(200, {'Content-Type': 'application/json'});
32 | res.send(req.body.final);
33 | });
34 | // listen for song being clicked and added to the queue, then update everyone's state
35 | io.on('connection', socket => {
36 | // console.log('new client connected');
37 | socket.on('playSong', (newSongState) => {
38 | // console.log('received updated Song State: ', newSongState);
39 | songsController.playerState = newSongState;
40 | io.emit('playSong', newSongState);
41 | });
42 | // listen for queue update and then emit the new song state for all clients to update their state
43 | socket.on('updateQueue', (newSongState) => {
44 | songsController.playerState = newSongState;
45 | io.emit('updateQueue', newSongState);
46 | });
47 | // add playCurrent event handler
48 | socket.on('playCurrent', () => io.emit('playCurrent'));
49 | // add pauseCurrent event handler
50 | socket.on('pauseCurrent', () => io.emit('pauseCurrent'));
51 | socket.on('songEnded', (newSongState) => {
52 | // console.log('song has ended!');
53 | songsController.playerState = newSongState;
54 | io.emit('songEnded', newSongState);
55 | });
56 | });
57 |
58 | module.exports = app;
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slick",
3 | "version": "1.0.0",
4 | "description": "Slack for Music: provide real time event driven collaboration between users for synchronized music playback and DJ delegation",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "node_modules/.bin/gulp",
8 | "test": "mocha ./server/test/*.js || exit 0",
9 | "start": "NODE_ENV=production gulp"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/midMEETdle/slick"
14 | },
15 | "engines": {
16 | "Node": "5.8.0"
17 | },
18 | "author": "Michael Blanchard, Sandra Winkler, Bryan Kitchener, Matt McLaughlin",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/midMEETdle/slick"
22 | },
23 | "homepage": "https://github.com/midMEETdle/slick",
24 | "dependencies": {
25 | "body-parser": "^1.15.1",
26 | "cors": "^2.7.1",
27 | "express": "^4.13.4",
28 | "lodash": "^4.13.1",
29 | "node": "0.0.0",
30 | "node-gracenote": "github:ddanninger/node-gracenote",
31 | "node-jsdom": "^3.1.5",
32 | "request": "^2.72.0",
33 | "querystring": "^0.2.0",
34 | "socket.io": "^1.4.6",
35 | "socket.io-client": "^1.4.6"
36 | },
37 | "devDependencies": {
38 | "babel-core": "6.10.4",
39 | "babel-loader": "6.2.4",
40 | "babel-preset-es2015": "6.9.0",
41 | "babel-preset-react": "6.11.1",
42 | "babelify": "^7.3.0",
43 | "browserify": "^13.0.1",
44 | "chai": "^3.5.0",
45 | "chai-enzyme": "^0.4.2",
46 | "enzyme": "^2.3.0",
47 | "eslint": "2.13.1",
48 | "eslint-config-airbnb": "9.0.1",
49 | "eslint-plugin-import": "1.9.2",
50 | "eslint-plugin-jsx-a11y": "1.5.3",
51 | "eslint-plugin-react": "5.2.2",
52 | "gulp": "^3.9.1",
53 | "gulp-gzip": "1.4.0",
54 | "gulp-nodemon": "^2.0.7",
55 | "gulp-notify": "2.2.0",
56 | "gulp-uglify": "1.5.4",
57 | "gulp-util": "^3.0.7",
58 | "jquery": "^2.2.4",
59 | "mocha": "^2.5.3",
60 | "mocha-jsdom": "^1.1.0",
61 | "react": "^15.1.0",
62 | "react-addons-test-utils": "^15.1.0",
63 | "react-dom": "^15.1.0",
64 | "react-youtube": "^6.1.0",
65 | "reactify": "^1.1.1",
66 | "skin-deep": "^0.16.0",
67 | "snazzy": "4.0.0",
68 | "standard": "7.1.0",
69 | "supertest": "^1.2.0",
70 | "vinyl-source-stream": "^1.1.0",
71 | "watchify": "^3.7.0",
72 | "whatwg-fetch": "^1.0.0",
73 | "youtube-player": "^3.0.4"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # slick [](https://travis-ci.org/cs-slick/slick) [](https://coveralls.io/github/cs-slick/slick?branch=master)
2 | Slack for Music: provide real time event driven collaboration between users for synchronized music playback and DJ delegation
3 |
4 | ##Overview
5 | ###Tech Stack
6 | - React
7 | - rollup (bundling)
8 | - Socket.io
9 | - Mocha
10 | - Chai
11 | - Supertest
12 | - socket.io-client
13 | - Node + Express
14 |
15 | ###Front-end
16 | - built in React
17 | - 4 different components
18 | ``
19 |
20 |
21 |
22 | ``
23 | - ```` is component where DOM event and socket event handling happens (ex: play, pause, onPlay socket emit events, etc..)
24 | - ```` is component where ``