SPACEBOX
+Recently Playing
} +diff --git a/.circleci/config.yml b/.circleci/config.yml
index d282729..918c063 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -4,6 +4,7 @@ jobs:
working_directory: ~/origin-spacebox
docker:
- image: circleci/node:8.11.0
+ - image: mongo:4.0.3
steps:
- checkout
- run:
@@ -16,10 +17,12 @@ jobs:
- run:
name: Mocha Test Suite
command: 'npm run test'
+
deploy-job:
working_directory: ~/origin-spacebox
docker:
- image: circleci/node:8.11.0
+ - image: mongo:4.0.3
steps:
- checkout
- run:
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..3ee22e5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# http://editorconfig.org
+
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.eslintignore b/.eslintignore
old mode 100755
new mode 100644
index d636ff1..1248ed9
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
+/client/
/node_modules
/build
.vscode
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..a6e5297
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "loopback"
+}
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100755
index 08a27e6..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "airbnb",
- "env": {
- "browser": true,
- "node":true
- }
-}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b885285..f654410 100755
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,7 @@ package-lock.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-.env
\ No newline at end of file
+.env
+Mockup.sketch
+dist
+
diff --git a/.yo-rc.json b/.yo-rc.json
new file mode 100644
index 0000000..02f3fc1
--- /dev/null
+++ b/.yo-rc.json
@@ -0,0 +1,3 @@
+{
+ "generator-loopback": {}
+}
\ No newline at end of file
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..dd00c9e
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,3 @@
+## Client
+
+This is the place for your application front-end files.
diff --git a/public/index.html b/client/index.html
similarity index 100%
rename from public/index.html
rename to client/index.html
diff --git a/public/moon.jpeg b/client/moon.jpeg
similarity index 100%
rename from public/moon.jpeg
rename to client/moon.jpeg
diff --git a/common/models/queue.js b/common/models/queue.js
new file mode 100644
index 0000000..9897796
--- /dev/null
+++ b/common/models/queue.js
@@ -0,0 +1,137 @@
+'use strict';
+const { getPlaylist, updatePlaylist } = require('../../server/utils/playlist');
+const { addToDefaultSongs } = require('../../server/utils/adminQueue');
+const { playCurrentSong, pauseCurrentSong } = require('../../server/utils/player');
+
+module.exports = function (Queue) {
+ Queue.getPlaylist = function (id, cb) {
+ getPlaylist(id)
+ .then((tracks) => cb(null, tracks))
+ .catch(err => cb(err));
+ }
+
+ Queue.remoteMethod('getPlaylist', {
+ description: 'Gets current playlist from Spotify',
+ accepts: {
+ arg: 'id',
+ type: 'string'
+ },
+ http: {
+ path: '/getPlaylist',
+ verb: 'get'
+ },
+ returns: {
+ arg: 'data',
+ type: 'array',
+ root: true
+ },
+ });
+
+ Queue.updatePlaylist = function (id, songID, cb) {
+ updatePlaylist(id, songID)
+ .then((queue) => {
+ Queue.replaceOrCreate(queue, cb);
+ })
+ .catch(err => cb(err));
+ }
+
+ Queue.remoteMethod('updatePlaylist', {
+ description: 'Adds new song to playlist and removes current playing song from playlist. Adds default songs if needed.',
+ accepts: [{
+ arg: 'id',
+ type: 'string'
+ },
+ {
+ arg: 'songID',
+ type: 'string',
+ required: false
+ }],
+ http: {
+ path: '/updatePlaylist',
+ verb: 'put'
+ },
+ returns: {
+ arg: 'data',
+ type: 'array',
+ root: true
+ },
+ });
+
+ Queue.addToDefaultSongs = function (id, uri, cb) {
+ addToDefaultSongs(id, uri)
+ .then((queue) => {
+ Queue.replaceOrCreate(queue, cb);
+ })
+ .catch(err => cb(err));
+ };
+
+ Queue.remoteMethod('addToDefaultSongs', {
+ description: 'Adds new song to default song array',
+ accepts: [
+ {
+ arg: 'id',
+ type: 'string'
+ },
+ {
+ arg: 'uri',
+ type: 'string'
+ }],
+ http: {
+ path: '/addToDefaultSongs',
+ verb: 'post'
+ },
+ returns: {
+ arg: 'data',
+ type: 'array',
+ root: true
+ },
+ });
+
+ Queue.pauseCurrentSong = function (id, cb) {
+ pauseCurrentSong(id)
+ .then(response => cb(null, response))
+ .catch(err => cb(err));
+ };
+
+ Queue.remoteMethod('pauseCurrentSong', {
+ description: 'Pauses currently playing song',
+ accepts: {
+ arg: 'id',
+ type: 'string'
+ },
+ http: {
+ path: '/pauseCurrentSong',
+ verb: 'get'
+ },
+ returns: {
+ arg: 'data',
+ type: 'array',
+ root: true
+ },
+ });
+
+ Queue.playCurrentSong = function (id, cb) {
+ playCurrentSong(id)
+ .then(response => cb(null, response))
+ .catch(err => cb(err));
+ };
+
+ Queue.remoteMethod('playCurrentSong', {
+ description: 'Starts/resumes currently playing song',
+ accepts: {
+ arg: 'id',
+ type: 'string'
+ },
+ http: {
+ path: '/playCurrentSong',
+ verb: 'get'
+ },
+ returns: {
+ arg: 'data',
+ type: 'array',
+ root: true
+ },
+ });
+
+};
+
diff --git a/common/models/queue.json b/common/models/queue.json
new file mode 100644
index 0000000..f754893
--- /dev/null
+++ b/common/models/queue.json
@@ -0,0 +1,42 @@
+{
+ "name": "Queue",
+ "base": "PersistedModel",
+ "idInjection": true,
+ "options": {
+ "validateUpsert": true
+ },
+ "properties": {
+ "defaultSongs": {
+ "type": [
+ "object"
+ ],
+ "required": true
+ }
+ },
+ "validations": [],
+ "relations": {
+ "default": {
+ "type": "referencesMany",
+ "model": "Song",
+ "foreignKey": "defaultSongs",
+ "options": {
+ "nestRemoting": true
+ }
+ },
+ "songs": {
+ "type": "referencesMany",
+ "model": "Song",
+ "foreignKey": "",
+ "options": {
+ "nestRemoting": true
+ }
+ },
+ "user": {
+ "type": "belongsTo",
+ "model": "user",
+ "foreignKey": ""
+ }
+ },
+ "acls": [],
+ "methods": {}
+}
diff --git a/common/models/song.js b/common/models/song.js
new file mode 100644
index 0000000..8d1992e
--- /dev/null
+++ b/common/models/song.js
@@ -0,0 +1,29 @@
+'use strict';
+const { getSong } = require('../../server/utils/song');
+const { getMoreMusicFromSpotify} = require('../../server/utils/getMoreFromSpotify');
+
+module.exports = function(Song) {
+ Song.getTrackData = function(songUri, userID, cb) {
+ getSong(songUri, userID)
+ .then(song => cb(null, song))
+ .catch(err => cb(err));
+ }
+
+ Song.remoteMethod('getTrackData', {
+ accepts: [{arg: 'songUri', type: 'string'},{arg: 'userID', type:'string'}],
+ returns: {arg: 'song', type: 'object'}
+ });
+
+ Song.getMoreFromSpotify = function(userId, query, types, cb) {
+ getMoreMusicFromSpotify(userId, query, types)
+ .then((songs) => cb(null, songs))
+ .catch(err => cb(err));
+ };
+
+ Song.remoteMethod('getMoreFromSpotify', {
+ description: 'Searchs for spotify songs',
+ accepts: [{arg: 'userId', type: 'string'}, {arg: 'query', type: 'string'}, {arg: 'types', type: 'array'}],
+ http: {path: '/getMoreFromSpotify', verb: 'get'},
+ returns: {arg: 'data', type: 'array', root: true},
+ });
+};
diff --git a/common/models/song.json b/common/models/song.json
new file mode 100644
index 0000000..6597c32
--- /dev/null
+++ b/common/models/song.json
@@ -0,0 +1,38 @@
+{
+ "name": "Song",
+ "base": "PersistedModel",
+ "idInjection": true,
+ "options": {
+ "validateUpsert": true
+ },
+ "properties": {
+ "name": {
+ "type": "string",
+ "required": true
+ },
+ "duration": {
+ "type": "number",
+ "required": true
+ },
+ "artist": {
+ "type": "string",
+ "required": true
+ },
+ "uri": {
+ "type": "string",
+ "required": true
+ },
+ "albumCover": {
+ "type": "string",
+ "required": true
+ },
+ "spotifyId": {
+ "type": "string",
+ "required": true
+ }
+ },
+ "validations": [],
+ "relations": {},
+ "acls": [],
+ "methods": {}
+}
diff --git a/common/models/user.js b/common/models/user.js
new file mode 100644
index 0000000..6214935
--- /dev/null
+++ b/common/models/user.js
@@ -0,0 +1,4 @@
+'use strict';
+
+module.exports = function(User) {
+};
diff --git a/common/models/user.json b/common/models/user.json
new file mode 100644
index 0000000..1cfc199
--- /dev/null
+++ b/common/models/user.json
@@ -0,0 +1,63 @@
+{
+ "name": "user",
+ "plural": "users",
+ "base": "User",
+ "idInjection": true,
+ "options": {
+ "validateUpsert": true
+ },
+ "properties": {
+ "firstName": {
+ "type": "string"
+ },
+ "lastName": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string",
+ "required": true
+ },
+ "username": {
+ "type": "string",
+ "required": true
+ },
+ "password": {
+ "type": "string",
+ "required": true
+ },
+ "accessToken": {
+ "type": "string",
+ "required": false
+ },
+ "spotifyID": {
+ "type": "string",
+ "required": false
+ },
+ "playlistID": {
+ "type": "string",
+ "required": false
+ },
+ "spotifyRefreshToken": {
+ "type": "string",
+ "required": false
+ },
+ "spotifyAccessToken": {
+ "type": "string",
+ "required": false
+ },
+ "spotifyAccessToken": {
+ "type": "string",
+ "required": false
+ }
+ },
+ "validations": [],
+ "relations": {
+ "queue": {
+ "type": "hasOne",
+ "model": "Queue",
+ "foreignKey": ""
+ }
+ },
+ "acls": [],
+ "methods": {}
+}
diff --git a/package.json b/package.json
old mode 100755
new mode 100644
index c261e16..bc4707a
--- a/package.json
+++ b/package.json
@@ -1,18 +1,19 @@
{
"name": "origin-spacebox",
"version": "1.0.0",
- "private": true,
+ "main": "server/server.js",
+ "engines": {
+ "node": ">=4"
+ },
"description": "Origin Code Academy's Jukebox",
"scripts": {
"watch": "webpack -w",
- "server": "nodemon server",
- "build": "webpack -p",
- "start": "node server",
+ "server": "nodemon server/server",
+ "build": "webpack",
+ "start": "node server/server",
"dev": "concurrently \"webpack -w\" \"nodemon server\"",
- "test": "mocha tests"
+ "test": "npm run build && mocha tests/*.spec.js --exit"
},
- "author": "",
- "license": "ISC",
"dependencies": {
"@babel/core": "^7.0.0-beta.39",
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.39",
@@ -20,28 +21,47 @@
"@babel/preset-react": "^7.0.0-beta.39",
"axios": "^0.17.1",
"babel-loader": "^8.0.0-beta.0",
- "chai": "^4.1.2",
+ "chai": "^4.2.0",
"chai-http": "^4.0.0",
- "dotenv": "^6.0.0",
+ "compression": "^1.0.3",
+ "cors": "^2.5.2",
+ "dotenv": "^6.1.0",
"ejs": "^2.5.7",
- "express": "^4.15.2",
+ "express": "^4.16.4",
"file-loader": "^1.1.11",
+ "helmet": "^3.10.0",
"http": "0.0.0",
+ "loopback": "^3.23.2",
+ "loopback-boot": "^2.6.5",
+ "loopback-component-explorer": "^6.2.0",
+ "loopback-connector-mongodb": "^3.9.1",
+ "loopback-datasource-juggler": "^4.1.1",
"mocha": "^5.2.0",
+ "nightmare": "^3.0.1",
"path": "^0.12.7",
"react": "^16.4.1",
- "react-dom": "^16.2.0",
- "socket.io": "^2.0.4",
+ "react-dom": "^16.6.0",
+ "react-redux": "^5.1.0",
+ "react-router": "^4.3.1",
+ "react-router-dom": "^4.3.1",
+ "redux": "^4.0.1",
+ "redux-promise-middleware": "^5.1.1",
+ "serve-favicon": "^2.0.1",
+ "socket.io": "^2.1.1",
"socket.io-client": "^2.0.4",
- "spotify-web-api-node": "^3.1.1",
- "url-loader": "^1.0.1"
+ "spotify-web-api-node": "^4.0.0",
+ "strong-error-handler": "^3.0.0",
+ "url-loader": "^1.0.1",
+ "webpack": "^3.10.0"
},
"devDependencies": {
"babel-eslint": "^8.2.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"concurrently": "^3.5.1",
"css-loader": "^0.28.9",
+ "eslint": "^3.19.0",
"eslint-config-airbnb": "^16.1.0",
+ "eslint-config-loopback": "^8.0.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.9.1",
@@ -52,5 +72,10 @@
"sass-loader": "^6.0.6",
"style-loader": "^0.20.1",
"webpack": "^3.10.0"
- }
+ },
+ "repository": {
+ "type": "",
+ "url": ""
+ },
+ "license": "ISC"
}
diff --git a/server/boot/authentication.js b/server/boot/authentication.js
new file mode 100644
index 0000000..7fd9c55
--- /dev/null
+++ b/server/boot/authentication.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = function enableAuthentication(server) {
+ // enable authentication
+ // server.enableAuth();
+};
diff --git a/server/boot/create-admin.js b/server/boot/create-admin.js
new file mode 100644
index 0000000..e47e508
--- /dev/null
+++ b/server/boot/create-admin.js
@@ -0,0 +1,48 @@
+'use-strict';
+
+module.exports = app => {
+ const {user, Role, RoleMapping} = app.models;
+ user.findOrCreate(
+ {
+ where: {
+ 'username': process.env.DEFAULT_ADMIN_USERNAME,
+ },
+ },
+ {
+ 'username': process.env.DEFAULT_ADMIN_USERNAME,
+ 'email': process.env.DEFAULT_ADMIN_EMAIL,
+ 'password': process.env.DEFAULT_ADMIN_PASSWORD,
+ },
+ (err, user) => {
+ if (err) console.log(err);
+ Role.findOrCreate(
+ {
+ where: {
+ 'name': 'admin',
+ },
+ },
+ {
+ 'name': 'admin',
+ },
+ (err) => {
+ if (err) console.log(err);
+ RoleMapping.findOrCreate(
+ {
+ where: {
+ principalType: 'admin',
+ principalId: user.id,
+ },
+ },
+ {
+ principalType: 'admin',
+ principalId: user.id,
+ },
+ (err) => {
+ if (err) console.log(err);
+ }
+ );
+ }
+ );
+ }
+ );
+};
diff --git a/server/boot/root.js b/server/boot/root.js
new file mode 100644
index 0000000..c548aab
--- /dev/null
+++ b/server/boot/root.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = function(server) {
+ var router = server.loopback.Router();
+ server.use(router);
+};
diff --git a/server/boot/spotify.js b/server/boot/spotify.js
new file mode 100644
index 0000000..12cab07
--- /dev/null
+++ b/server/boot/spotify.js
@@ -0,0 +1,62 @@
+'use strict';
+const SpotifyWebApi = require('spotify-web-api-node');
+
+module.exports = function(server) {
+ var router = server.loopback.Router();
+
+ router.get('/spotify', (req, res) => {
+
+ const spotifyApi = new SpotifyWebApi({
+ clientId: process.env.SPOTIFY_CLIENT_ID,
+ clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
+ redirectUri: `${process.env.SITE_URL}/auth`,
+ });
+
+ const scopes = [
+ 'playlist-modify-public',
+ 'playlist-read-private',
+ 'playlist-modify-private',
+ 'streaming',
+ 'app-remote-control',
+ 'user-modify-playback-state',
+ 'user-read-currently-playing',
+ 'user-read-playback-state'
+ ];
+
+ res.redirect(spotifyApi.createAuthorizeURL(scopes, 'spacebox'))
+ });
+
+ router.get('/auth', (req, res) => {
+ const { code, state } = req.query;
+
+ if (state === 'spacebox' && code) {
+ const spotifyApi = new SpotifyWebApi({
+ clientId: process.env.SPOTIFY_CLIENT_ID,
+ clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
+ redirectUri: `${process.env.SITE_URL}/auth`,
+ });
+
+ spotifyApi.authorizationCodeGrant(code)
+ .then(({ body: { 'access_token': accessToken, 'refresh_token': refreshToken } }) => {
+
+ const UserModel = server.models.user;
+
+ return UserModel.findOrCreate({ where: { username: process.env.ORIGIN_USERNAME } }, {
+ "email": process.env.ORIGIN_EMAIL,
+ "username": process.env.ORIGIN_USERNAME,
+ "password": process.env.ORIGIN_PASSWORD,
+ "spotifyAccessToken": accessToken,
+ "spotifyRefreshToken": refreshToken
+ });
+ })
+ .then(() => res.redirect('/'))
+ .catch((err) => res.send(err));
+ }
+ else {
+ res.redirect('/');
+ }
+ })
+
+
+ server.use(router);
+};
\ No newline at end of file
diff --git a/server/component-config.json b/server/component-config.json
new file mode 100644
index 0000000..ae873f5
--- /dev/null
+++ b/server/component-config.json
@@ -0,0 +1,6 @@
+{
+ "loopback-component-explorer": {
+ "mountPath": "/explorer",
+ "generateOperationScopedModels": true
+ }
+}
diff --git a/server/config.json b/server/config.json
new file mode 100644
index 0000000..d371cd2
--- /dev/null
+++ b/server/config.json
@@ -0,0 +1,22 @@
+{
+ "restApiRoot": "/api",
+ "host": "0.0.0.0",
+ "port": 3000,
+ "remoting": {
+ "context": false,
+ "rest": {
+ "handleErrors": false,
+ "normalizeHttpPath": false,
+ "xml": false
+ },
+ "json": {
+ "strict": false,
+ "limit": "100kb"
+ },
+ "urlencoded": {
+ "extended": true,
+ "limit": "100kb"
+ },
+ "cors": false
+ }
+}
diff --git a/server/datasources.json b/server/datasources.json
new file mode 100644
index 0000000..3ab206c
--- /dev/null
+++ b/server/datasources.json
@@ -0,0 +1,16 @@
+{
+ "db": {
+ "name": "db",
+ "connector": "memory"
+ },
+ "MongoDB": {
+ "host": "",
+ "port": 0,
+ "url": "",
+ "database": "",
+ "password": "",
+ "name": "MongoDB",
+ "user": "",
+ "connector": "mongodb"
+ }
+}
diff --git a/server/datasources.production.js b/server/datasources.production.js
new file mode 100644
index 0000000..643d4c0
--- /dev/null
+++ b/server/datasources.production.js
@@ -0,0 +1,7 @@
+module.exports = {
+ 'MongoDB': {
+ 'name': 'MongoDB',
+ 'connector': 'mongodb',
+ 'url': process.env.MONGODB_URI,
+ },
+};
diff --git a/server/default.js b/server/default.js
deleted file mode 100644
index cbc5cc8..0000000
--- a/server/default.js
+++ /dev/null
@@ -1,65 +0,0 @@
-module.exports = [{
- id: '299vmLW2iaQxe8y9HLWNiH',
- name: 'just ask',
- artist: 'weird inside',
- albumCover: 'https://i.scdn.co/image/6b440d0d81145350b4bf87c7a77d679701dd4f22',
- duration: 173846,
- uri: 'spotify:track:299vmLW2iaQxe8y9HLWNiH'
- },
- {
- id: '5HE2u1IOizZwM4kWMF5Jpb',
- name: 'Sunset',
- artist: 'Coubo',
- albumCover: 'https://i.scdn.co/image/2362328b20a356c1d23897d38531b1b93844c788',
- duration: 213361,
- uri: 'spotify:track:5HE2u1IOizZwM4kWMF5Jpb'
- },
- {
- id: '4uZ6QSZJPzCD4d7Jtscwes',
- name: 'Minutes',
- artist: 'Freddie Joachim',
- albumCover: 'https://i.scdn.co/image/786f2d9d6117cb1f7262e835c43850237444259a',
- duration: 227186,
- uri: 'spotify:track:4uZ6QSZJPzCD4d7Jtscwes'
- },
- {
- id: '2GPKo5nrX2uwB8eSCebIIk',
- name: 'sincerely, yours',
- artist: 'Nohidea',
- albumCover: 'https://i.scdn.co/image/96b8069a0d544d1111f65bb37da3fb05f7b59b54',
- duration: 139684,
- uri: 'spotify:track:2GPKo5nrX2uwB8eSCebIIk'
- },
- {
- id: '0ej5zbkF48ZiPZOubIX1e1',
- name: 'Days To Come - Instrumental',
- artist: 'Bonobo',
- albumCover: 'https://i.scdn.co/image/fe00d367ac66e49eae8a8783bb9c36779ccee613',
- duration: 230400,
- uri: 'spotify:track:0ej5zbkF48ZiPZOubIX1e1'
- },
- {
- id: '3m9X1ZYQPKKZXZpykC2jBf',
- name: 'Whispers',
- artist: 'Freddie Joachim',
- albumCover: 'https://i.scdn.co/image/aea57c1c3d68e1dfb5c01aaeacd2bad0c0a2927c',
- duration: 133799,
- uri: 'spotify:track:3m9X1ZYQPKKZXZpykC2jBf'
- },
- {
- albumCover: "https://i.scdn.co/image/1cf857b33913c2237ab017d33a49631b372e9fe6",
- artist: "D Numbers",
- duration: 313413,
- id: "5Hs9xwUmu8xoU31dydcyTd",
- name: "Xylem Up",
- uri: "spotify:track:5Hs9xwUmu8xoU31dydcyTd",
- },
- {
- albumCover: "https://i.scdn.co/image/c21c1e4c58342abb6f88dfe1b291c718f6a70e43",
- artist: "Maxence Cyrin",
- duration: 165160,
- id: "4jNQkWhuzqrbqQuqanFFJ6",
- name: "Where Is My Mind",
- uri: "spotify:track:4jNQkWhuzqrbqQuqanFFJ6"
- }
-]
\ No newline at end of file
diff --git a/server/middleware.development.json b/server/middleware.development.json
new file mode 100644
index 0000000..071c11a
--- /dev/null
+++ b/server/middleware.development.json
@@ -0,0 +1,10 @@
+{
+ "final:after": {
+ "strong-error-handler": {
+ "params": {
+ "debug": true,
+ "log": true
+ }
+ }
+ }
+}
diff --git a/server/middleware.json b/server/middleware.json
new file mode 100644
index 0000000..5dc1eb2
--- /dev/null
+++ b/server/middleware.json
@@ -0,0 +1,59 @@
+{
+ "initial:before": {
+ "loopback#favicon": {}
+ },
+ "initial": {
+ "compression": {},
+ "cors": {
+ "params": {
+ "origin": true,
+ "credentials": true,
+ "maxAge": 86400
+ }
+ },
+ "helmet#xssFilter": {},
+ "helmet#frameguard": {
+ "params": {
+ "action": "deny"
+ }
+ },
+ "helmet#hsts": {
+ "params": {
+ "maxAge": 0,
+ "includeSubdomains": true
+ }
+ },
+ "helmet#hidePoweredBy": {},
+ "helmet#ieNoOpen": {},
+ "helmet#noSniff": {},
+ "helmet#noCache": {
+ "enabled": false
+ }
+ },
+ "session": {},
+ "auth": {},
+ "parse": {},
+ "routes": {
+ "loopback#rest": {
+ "paths": [
+ "${restApiRoot}"
+ ]
+ }
+ },
+ "files": {
+ "loopback#static": [
+ {
+ "params": "$!../dist"
+ },
+ {
+ "params": "$!../client"
+ }
+ ]
+ },
+ "final": {
+ "loopback#urlNotFound": {}
+ },
+ "final:after": {
+ "strong-error-handler": {}
+ }
+}
diff --git a/server/model-config.json b/server/model-config.json
new file mode 100644
index 0000000..601cfc8
--- /dev/null
+++ b/server/model-config.json
@@ -0,0 +1,54 @@
+{
+ "_meta": {
+ "sources": [
+ "loopback/common/models",
+ "loopback/server/models",
+ "../common/models",
+ "./models"
+ ],
+ "mixins": [
+ "loopback/common/mixins",
+ "loopback/server/mixins",
+ "../common/mixins",
+ "./mixins"
+ ]
+ },
+ "AccessToken": {
+ "dataSource": "MongoDB",
+ "public": false,
+ "relations": {
+ "user": {
+ "type": "belongsTo",
+ "model": "user",
+ "foreignKey": "userId"
+ }
+ }
+ },
+ "ACL": {
+ "dataSource": "MongoDB",
+ "public": false
+ },
+ "RoleMapping": {
+ "dataSource": "MongoDB",
+ "public": true,
+ "options": {
+ "strictObjectIDCoercion": true
+ }
+ },
+ "Role": {
+ "dataSource": "MongoDB",
+ "public": true
+ },
+ "Queue": {
+ "dataSource": "MongoDB",
+ "public": true
+ },
+ "Song": {
+ "dataSource": "MongoDB",
+ "public": true
+ },
+ "user": {
+ "dataSource": "MongoDB",
+ "public": true
+ }
+}
diff --git a/server/server.js b/server/server.js
old mode 100755
new mode 100644
index 740cbe3..7ceac15
--- a/server/server.js
+++ b/server/server.js
@@ -1,203 +1,33 @@
-const express = require('express');
-const Server = require('socket.io');
-const app = express();
-const server = require('http').Server(app);
-const io = new Server(server);
+'use strict';
-var SpotifyWebApi = require('spotify-web-api-node');
-const defaultSongs = require('./default');
+var loopback = require('loopback');
+var boot = require('loopback-boot');
require('dotenv').config();
-
-/**
- * A global array of Spotify track objects
- * @type {Array<{}>}
- */
-var songs = [];
-
-/**
- * Last song played
- */
-var lastPlayed = {};
-
-let lastTime = new Date();
-let accessToken = null;
-let refreshToken = process.env.SPOTIFY_REFRESH_TOKEN;
-const spotifyApi = new SpotifyWebApi({
- clientId: process.env.SPOTIFY_CLIENT_ID,
- clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
- redirectUri: process.env.SITE_URL || 'http://localhost:8080',
- refreshToken
-});
-
-/**
- * checks if we have a valid access token and if not refreshes token
- * @param {{}} req users request
- * @param {{}} res servers response obj
- * @param {Function} cb callback function that is invoked after retrieving access token
- */
-const checkToken = (req, res, cb) => {
- const currentTime = new Date();
-
- if (lastTime < currentTime || !accessToken) {
- spotifyApi.refreshAccessToken()
- .then(data => {
- lastTime = new Date();
- lastTime.setSeconds(data.body.expires_in);
- accessToken = data.body['access_token'];
- spotifyApi.setAccessToken(data.body['access_token']);
- cb();
- })
- .catch(err => console.log(err))
- } else {
- cb();
- }
-}
-
-/**
- * takes a track object and formats it
- * @param {{}} track the track object
- * @returns {{ id: String, name: String, artist: String, albumCover: String, duration: Number, uri: String }} formatted track
- */
-const formatSong = track => ({
- id: track.id,
- name: track.name,
- artist: track.artists[0].name,
- albumCover: track.album.images[0].url,
- duration: track.duration_ms,
- uri: track.uri
-})
-
-/**
- * retrieves playlist from Spotify
- * @returns {{url: String, image: String, tracks: Array<{}>}} playlist information and tracks
- */
-const getPlaylist = () => {
- return spotifyApi.getPlaylist(process.env.SPOTIFY_USER, process.env.SPOTIFY_PLAYLIST)
- .then(data => {
- if (data.body.tracks.items.length === 0) {
- songs = [];
- return songs;
- }
- const playlistInfo = {
- url: data.body.external_urls.spotify,
- image: data.body.images[0].url,
- tracks: data.body.tracks.items.map(i => formatSong(i.track))
+var app = module.exports = loopback();
+app.start = function () {
+ // start the web server
+ return app.listen(function () {
+ app.emit('started');
+ var baseUrl = app.get('url').replace(/\/$/, '');
+ console.log('Web server listening at: %s', baseUrl);
+ if (app.get('loopback-component-explorer')) {
+ var explorerPath = app.get('loopback-component-explorer').mountPath;
+ console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
- songs = playlistInfo.tracks
- return songs;
- })
- .catch(err => console.log(err));
-}
-
-/**
- * checking to see if URI exists in songs array
- * @param {String} uri spotify track identifier
- * @return {Boolean} false if there is no duplicate
- */
-const isDup = uri => songs.some(song => song.uri === uri);
-
-app.use((req, res, next) => {
- res.header('Access-Control-Allow-Origin', '*');
- res.header('Access-Control-Allow-Methods', 'GET,POST');
- next();
+ });
+};
+
+// Bootstrap the application, configure models, datasources and middleware.
+// Sub-apps like REST API are mounted via boot scripts.
+boot(app, __dirname, function (err) {
+ if (err) throw err;
+ // start the server if `$ node server.js`
+ if (require.main === module)
+ app.io = require('socket.io')(app.start());
+ app.io.on('connection', function(socket) {
+ socket.on('room', (room) => {
+ socket.join(room);
+ app.io.in(room).emit('update', []);
+ });
+ });
});
-app.use(express.json());
-app.use('/', express.static('build'));
-app.use('/', express.static('public'));
-
-app.get('/', (req, res) => res.render('index', { songs }));
-app.get('/board', (req, res) => res.json(songs));
-
-const updatePlaylist = async (songToAdd = null) => {
- const songs = await getPlaylist();
- return spotifyApi.getMyCurrentPlayingTrack()
- .then(response => {
- const songCurrentlyPlaying = response.body.item;
- const isJukeboxOn = response.body.is_playing;
-
- if (songs[0].uri === songCurrentlyPlaying.uri) {
- lastPlayed = songs[0];
- songs.shift();
- }
- else if (songs[0].uri !== songCurrentlyPlaying.uri && lastPlayed.uri !== songCurrentlyPlaying.uri) {
- // Make song list match currently playing
- while (songs.length && songs[0].uri !== songCurrentlyPlaying.uri)
- songs.shift();
- }
- // If new songs to add
- if (songToAdd) songs.push(songToAdd);
-
- // If last song and no longer playing ? add default songs.
- if (songs.length === 1) {
- songs.push(...defaultSongs.filter(s => s.uri !== songCurrentlyPlaying.uri));
- }
-
- let tracks = [...songs].map(s => s.uri);
-
- return spotifyApi.replaceTracksInPlaylist(process.env.SPOTIFY_USER, process.env.SPOTIFY_PLAYLIST, tracks)
- .then(() => {
- if (!isJukeboxOn) {
- setTimeout(() => {
- spotifyApi.play({
- context_uri: `spotify:user:${process.env.SPOTIFY_USER}:playlist:${process.env.SPOTIFY_PLAYLIST}`,
- offset: {
- position: 1
- }
- })
- .catch(err => console.log(err))
- }, 5000);
- }
-
- if (lastPlayed.uri === songCurrentlyPlaying.uri) songs.unshift(lastPlayed);
- io.emit('update', songs);
- return songs;
- })
- .catch(err => ({ error: 'We couldn\'t add your song for some reason. Try again!', err}));
- })
- .catch(err => ({ error: 'SPACEBOX is turned off. Tell an instructor!', err}))
-}
-
-app.post('/api/request', (req, res) => {
- if (isDup(req.body.uri)) {
- res.json({ error: 'Duplicate song!' })
- }
- else {
- spotifyApi.getTrack(req.body.uri.slice(14))
- .then(track => {
- const trackData = formatSong(track.body);
- updatePlaylist(trackData)
- .then(songs => res.send(songs))
- })
- .catch(err => res.json({ error: 'Track doesn\'t exist! Try spotify:track:{SONG_ID}' }));
- }
-});
-
-//Custom routes
-app.get('/404', (req, res) => res.json({ message: 'Nothing is here. But thanks for checking!' }));
-
-app.use('/api', checkToken);
-app.get('/api/artist/:artist', (req, res) => {
- spotifyApi.searchArtists(req.params.artist)
- .then(data => {
- const items = data.body.artists.items;
- if (!items.length) res.send('Stop it');
- res.send(items[0])
- })
- .catch(err => console.log(err));
-})
-
-app.get('/api/playlist', async (req, res) => {
- const songs = await updatePlaylist();
- res.send(songs);
-});
-
-app.delete('/api/request', (req, res, next) => {
- spotifyApi.removeTracksFromPlaylist(process.env.SPOTIFY_USER, process.env.SPOTIFY_PLAYLIST, req.body.tracks)
- .then((response) => {
- io.emit('update', songs);
- res.send(response)
- })
- .catch(err => console.log(err))
-});
-
-module.exports = { server, checkToken };
\ No newline at end of file
diff --git a/server/utils/adminQueue.js b/server/utils/adminQueue.js
new file mode 100644
index 0000000..3d34605
--- /dev/null
+++ b/server/utils/adminQueue.js
@@ -0,0 +1,59 @@
+const app = require('../server');
+const {getSong} = require('../../server/utils/song');
+
+function addToDefaultSongs(id, uri) {
+ return new Promise((resolve, reject) => {
+ const {Song, Queue} = app.models;
+ Song.find({where: {uri: uri}})
+ .then(song => {
+ if (song[0]) {
+ let songId = song[0].id;
+ Queue.findById(id)
+ .then(queue => {
+ let defaultSongs = queue.defaultSongs;
+ defaultSongs.push(songId);
+ let updatedQueue = {
+ 'defaultSongs': defaultSongs,
+ 'id': id,
+ 'songIds': queue.songIds,
+ 'userId': queue.userId,
+ };
+ resolve(updatedQueue);
+ })
+ .catch(err => reject(err));
+ } else {
+ Queue.findById(id)
+ .then(queue => {
+ let userId = queue.userId;
+ let defaultSongs = queue.defaultSongs;
+ getSong(uri, userId)
+ .then(song => {
+ Song.find({where: {uri: uri}})
+ .then(song => {
+ let songId = song[0].id;
+ Queue.findById(id)
+ .then(queue => {
+ let defaultSongs = queue.defaultSongs;
+ defaultSongs.push(songId);
+ let updatedQueue = {
+ 'defaultSongs': defaultSongs,
+ 'id': id,
+ 'songIds': queue.songIds,
+ 'userId': queue.userId,
+ };
+ resolve(updatedQueue);
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ }
+ })
+ .catch(err => reject(err));
+ });
+}
+
+module.exports = {addToDefaultSongs};
diff --git a/server/utils/getMoreFromSpotify.js b/server/utils/getMoreFromSpotify.js
new file mode 100644
index 0000000..f4a1e34
--- /dev/null
+++ b/server/utils/getMoreFromSpotify.js
@@ -0,0 +1,25 @@
+var SpotifyWebApi = require('spotify-web-api-node');
+const { getAccessToken } = require('../../server/utils/playlist')
+
+const app = require('../server');
+
+function getMoreMusicFromSpotify(userId, query, types) {
+ return new Promise((resolve, reject) => {
+ getAccessToken(userId)
+ .then(accessToken => {
+ const spotifyApi = new SpotifyWebApi({ accessToken });
+ spotifyApi.search(query, types)
+ .then(res => {
+ let trackData = res.body.tracks.items.map(({ name, uri, album }) => ({
+ name,
+ uri,
+ artist: album.artists[0].name
+ }) )
+ resolve(trackData);
+ }
+ )
+ .catch(err => console.log('we caught an error in get more music', err))
+ })
+}
+)}
+module.exports = { getMoreMusicFromSpotify }
\ No newline at end of file
diff --git a/server/utils/player.js b/server/utils/player.js
new file mode 100644
index 0000000..ef1ef66
--- /dev/null
+++ b/server/utils/player.js
@@ -0,0 +1,75 @@
+const app = require('../server');
+const SpotifyWebApi = require('spotify-web-api-node');
+const { getAccessToken } = require('../../server/utils/playlist');
+
+function playCurrentSong(id) {
+ const { Queue, User } = app.models;
+ return new Promise((resolve, reject) => {
+ Queue.findById(id)
+ .then(queue => {
+ var userID = queue.userId;
+ // takes the userID from the queue and gets spotifyID and playlistID from that user
+ User.findById(userID)
+ .then((user) => {
+ var spotifyID = user.spotifyID
+ var playlistID = user.playlistID
+ getAccessToken(user.id)
+ .then(accessToken => {
+ const spotifyApi = new SpotifyWebApi({ accessToken });
+ spotifyApi.getMyCurrentPlayingTrack()
+ .then(response => {
+ const songCurrentlyPlaying = response.body.item;
+ const isJukeboxOn = response.body.is_playing;
+ spotifyApi.play({
+ context_uri: `spotify:playlist:${playlistID}`,
+ offset: {
+ position: 0
+ }
+ })
+ .then(() => resolve({ message: "Jukebox is on!" }))
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+}
+
+function pauseCurrentSong(id) {
+ const { Queue, User } = app.models;
+ return new Promise((resolve, reject) => {
+ Queue.findById(id)
+ .then(queue => {
+ var userID = queue.userId;
+ User.findById(userID)
+ .then((user) => {
+ var playlistID = user.playlistID
+ getAccessToken(user.id)
+ .then(accessToken => {
+ const spotifyApi = new SpotifyWebApi({ accessToken });
+ spotifyApi.getMyCurrentPlayingTrack()
+ .then(() => {
+ spotifyApi.pause({
+ context_uri: `spotify:playlist:${playlistID}`,
+ offset: {
+ position: 0
+ }
+ })
+ .then(() => resolve({ message: "Jukebox is on!" }))
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err));
+ })
+}
+
+module.exports = { playCurrentSong, pauseCurrentSong };
diff --git a/server/utils/playlist.js b/server/utils/playlist.js
new file mode 100644
index 0000000..197b2a6
--- /dev/null
+++ b/server/utils/playlist.js
@@ -0,0 +1,322 @@
+const app = require('../server')
+const SpotifyWebApi = require('spotify-web-api-node');
+
+function getAccessToken(userID = null) {
+ const spotifyApi = new SpotifyWebApi({
+ clientId: process.env.SPOTIFY_CLIENT_ID,
+ clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
+ redirectUri: process.env.SITE_URL || 'http://localhost:3000/auth',
+ });
+
+ return new Promise((resolve, reject) => {
+ if (!userID) {
+ reject('No user id provided');
+ return false;
+ }
+ const { User } = app.models;
+ User.findById(userID)
+ .then((user) => {
+ if (!user.spotifyRefreshToken) {
+ reject('No refresh token');
+ return false;
+ }
+ // get their refresh token and add accessToken
+ spotifyApi.setRefreshToken(user.spotifyRefreshToken);
+ spotifyApi.refreshAccessToken()
+ .then(({ body: { 'access_token': accessToken } }) => {
+ resolve(accessToken);
+ })
+ .catch(err => reject(err));
+ })
+ })
+}
+
+/**
+ *
+ * @param {String} id ID for queue you're searching for
+ * @returns {Promise} Resolves to array of objects with tracks on the playlist
+ */
+function getPlaylist(id) {
+ return new Promise((resolve, reject) => {
+ const { Queue, User } = app.models;
+ // finds the correct queue based on the queue ID that you put in
+ Queue.findById(id, { fields: { userId: true } })
+ .then((queue) => {
+ const userID = queue.userId;
+ // takes the userID from the queue and gets spotifyID and playlistID from that user
+ User.findById(userID)
+ .then((user) => {
+ const spotifyID = user.spotifyID
+ const playlistID = user.playlistID
+ getAccessToken(user.id)
+ .then(accessToken => {
+ const spotifyApi = new SpotifyWebApi({ accessToken });
+ spotifyApi.getPlaylist(spotifyID, playlistID)
+ .then(playlist => {
+ const formatSong = track => ({
+ id: track.id,
+ name: track.name,
+ artist: track.artists[0].name,
+ albumCover: track.album.images[0].url,
+ duration: track.duration_ms,
+ uri: track.uri
+ })
+ const tracks = playlist.body.tracks.items.map(i => formatSong(i.track))
+ const output = {
+ tracks: tracks,
+ userID: userID,
+ spotifyID: spotifyID,
+ playlistID: playlistID
+ }
+ resolve(output)
+ })
+ .catch(err => reject(err));
+ })
+ .catch(err => reject(err))
+ })
+ })
+ .catch(err => reject(err));
+ })
+}
+
+function removeCurrentlyPlaying(songs, songCurrentlyPlaying, queueId) {
+ return new Promise((resolve, reject) => {
+ const { Queue, Song } = app.models;
+ // update database so the queue matches song track from spotify
+ Queue.findById(queueId)
+ .then((queue) => {
+ // last played is first song in the queue
+ if (!queue.songIds.length) {
+ return resolve({
+ songs,
+ lastPlayed: undefined
+ })
+ }
+
+ let lastPlayed = queue.songIds[0];
+ Song.findById(lastPlayed)
+ .then((songObject) => {
+ let lastPlayedObject = songObject
+ if (songs[0].uri === songCurrentlyPlaying.uri) {
+ // update the queue so it matches songs(from spotify)
+ let songURIs = songs.map(s => s.uri)
+ getSongIds(songURIs)
+ .then((songIds) => {
+ const songIdArray = songIds
+ let newQueue = {
+ "defaultSongs": queue.defaultSongs,
+ "id": queue.id,
+ "songIds": songIdArray,
+ "userId": queue.userId
+ }
+ Queue.replaceOrCreate(newQueue)
+ // update lastplayed here (Do I still need to do this? duplicating the default above)
+ Queue.findById(queue.id)
+ .then((queue) => {
+ lastPlayed = queue.songIds[0]
+ // remove first song from spotify
+ songs.shift();
+ return resolve({
+ songs,
+ lastPlayed
+ })
+ })
+ })
+ .catch(err => ({ error: '', err }))
+ }
+ else if (songs[0].uri !== songCurrentlyPlaying.uri && lastPlayedObject.uri !== songCurrentlyPlaying.uri) {
+ // Make song list match currently playing
+ while (songs.length && songs[0].uri !== songCurrentlyPlaying.uri) songs.shift();
+ // update queue so it matches songs
+ let songURIs = [...songs].map(s => s.uri)
+ getSongIds(songURIs)
+ .then((songIds) => {
+ const songIdArray = songIds
+ let newQueue = {
+ "defaultSongs": queue.defaultSongs,
+ "id": queue.id,
+ "songIds": songIdArray,
+ "userId": queue.userId
+ }
+ Queue.replaceOrCreate(newQueue)
+ // update last playded it be song[0]
+ lastPlayed = newQueue.songIds[0]
+ // shift queue one more time so it doesn't have currently playing song
+ songs.shift()
+ songURIs = [...songs].map(s => s.uri)
+ getSongIds(songURIs)
+ .then((songIds) => {
+ const songIdArray = songIds
+ let newQueue = {
+ "defaultSongs": queue.defaultSongs,
+ "id": queue.id,
+ "songIds": songIdArray,
+ "userId": queue.userId
+ }
+ Queue.replaceOrCreate(newQueue)
+ return resolve({
+ songs,
+ lastPlayed
+ })
+ })
+ .catch(err => reject(err))
+ })
+ .catch(err => reject(err))
+ }
+ else {
+ return resolve({
+ songs,
+ lastPlayed
+ })
+ }
+ })
+ .catch(err => reject(err))
+ })
+ .catch(err => ({ error: 'couldnt find queue id', err }))
+ })
+ .catch(err => reject(err))
+}
+
+function addNewSong(songID, songs) {
+ return new Promise((resolve, reject) => {
+ const { Song } = app.models;
+ if (songID) {
+ Song.findById(songID)
+ .then((song) => {
+ if (songs.every((track) => track.uri !== song.uri)) {
+ songs.push(song)
+ resolve(songs)
+ }
+ else reject({ message: 'Duplicate song' })
+ })
+ .catch(err => reject(err))
+ }
+ else resolve(songs)
+ })
+}
+
+function getSongIds(songURIs) {
+ return new Promise((resolve, reject) => {
+ const { Song } = app.models;
+ Song.find({})
+ .then((songs) => {
+ let songIds = songURIs.map((uri) => {
+ return songs.filter((song) => song.uri == uri)
+ .map((song) => song.id)
+ })
+ songIds = songIds.map((id) => id[0])
+ resolve(songIds)
+ })
+ .catch(err => ({ error: 'Could not find matching song id', err }))
+ })
+ .catch(err => ({ error: 'Could not get song ids', err }))
+}
+
+function addDefaultSongsAndGetURIs(songs, id) {
+ return new Promise((resolve, reject) => {
+ const { Queue } = app.models;
+ let songURIs = songs.map(s => s.uri)
+ getSongIds(songURIs)
+ .then((songIds) => {
+ const justSongIds = songIds
+ Queue.findById(id)
+ .then((queue) => {
+ if (songs.length === 1) {
+ queue.default((err, defaultSongs) => {
+ let defaultSongIds = defaultSongs.map((song) => song.id)
+ let combinedSongIds = justSongIds.concat(defaultSongIds)
+ let defaultSongURIs = defaultSongs.map((song) => song.uri)
+ let combinedSongURIs = songURIs.concat(defaultSongURIs)
+ resolve({
+ songIds: combinedSongIds,
+ songURIs: combinedSongURIs
+ })
+ })
+ }
+ else {
+ resolve({
+ songIds: justSongIds,
+ songURIs: songURIs
+ })
+ }
+ })
+ .catch(err => ({ error: 'could not complete add default songs and get URIs function', err }))
+ })
+ .catch((err) => ({ error: 'could not complete getSongIds function within add default songs function', err }))
+ })
+}
+
+function updatePlaylist(id, songID = null) {
+ const { Queue, Song } = app.models;
+ return new Promise((resolve, reject) => {
+ getPlaylist(id)
+ .then((response) => {
+ let userID = response.userID
+ let spotifyID = response.spotifyID
+ let playlistID = response.playlistID
+ let tracks = response.tracks
+ getAccessToken(userID)
+ .then(accessToken => {
+ const spotifyApi = new SpotifyWebApi({ accessToken });
+ spotifyApi.getMyCurrentPlayingTrack()
+ .then((response) => {
+ // copying current playlist into a new array that we will mutate called songs
+ let songs = [...tracks]
+ const songCurrentlyPlaying = response.body.item;
+ removeCurrentlyPlaying(songs, songCurrentlyPlaying, id)
+ .then((response) => {
+ songs = response.songs
+ lastPlayed = response.lastPlayed
+ addNewSong(songID, songs)
+ .then((songs) => {
+ addDefaultSongsAndGetURIs(songs, id)
+ .then((response) => {
+ let songURIs = response.songURIs;
+ let songIds = response.songIds;
+ return spotifyApi.replaceTracksInPlaylist(spotifyID, playlistID, songURIs)
+ .then(async () => {
+ if (lastPlayed) {
+ await Song.findById(lastPlayed)
+ .then((lastPlayedSongObject) => {
+ if (lastPlayedSongObject.uri === songCurrentlyPlaying.uri) {
+ songIds.unshift(lastPlayed)
+ }
+ })
+ .catch(err => reject(err))
+ }
+ Queue.findById(id)
+ .then((singleQueue) => {
+ let queue = {
+ "defaultSongs": singleQueue.defaultSongs,
+ "id": id,
+ "songIds": songIds,
+ "userId": userID
+ }
+ Song.find({})
+ .then((songs) => {
+ let fullSongs = songIds.map((id) => {
+ return songs.filter((song) => song.id.toString() == id)
+ })
+ fullSongs = fullSongs.map((id) => id[0])
+ app.io.in(id).emit('update', fullSongs)
+ resolve(queue)
+ })
+ .catch(err => ({ error: 'could not get full song object', err }))
+ })
+ .catch(err => reject(err))
+ })
+ })
+ .catch(err => reject(err))
+ })
+ .catch(err => reject(err))
+ })
+ })
+ .catch(err => ({ error: 'SPACEBOX is turned off. Tell an instructor!', err }))
+ })
+ .catch(err => reject(err))
+ })
+ .catch(err => reject(err))
+ })
+}
+
+module.exports = { getPlaylist, updatePlaylist, getAccessToken, removeCurrentlyPlaying, addNewSong, addDefaultSongsAndGetURIs }
diff --git a/server/utils/song.js b/server/utils/song.js
new file mode 100644
index 0000000..35fd8b8
--- /dev/null
+++ b/server/utils/song.js
@@ -0,0 +1,72 @@
+var SpotifyWebApi = require('spotify-web-api-node');
+const { getAccessToken } = require('../../server/utils/playlist');
+const app = require('../server')
+
+ /**
+ * takes a track object and formats it
+ * @param {{}} track the track object
+ * @returns {{ id: String, name: String, artist: String, albumCover: String, duration: Number, uri: String }} formatted track
+ */
+ const formatSong = track => ({
+ name: track.name,
+ duration: track.duration_ms,
+ artist: track.album.artists[0].name,
+ uri:track.uri,
+ albumCover: track.album.images[0].url,
+ spotifyId:track.album.id
+ })
+
+function getSong(songUri, userID) {
+ return new Promise((resolve, reject) => {
+ if (songUri == undefined) {
+ resolve('Bad URI');
+ return false;
+ }
+ if (songUri === '' || songUri === []) {
+ resolve('No song URI');
+ return false;
+ }
+ if (userID == undefined) {
+ resolve('Bad userID');
+ return false;
+ }
+ if (userID === '' || userID === []) {
+ resolve('No userID');
+ return false;
+ }
+ let trackData;
+ const {Song} = app.models;
+ Song
+ .find({ where: { uri: songUri } })
+ .then(song => {
+ //the following checks if the song does not exist in Song model
+ //if no song, go to api and get song data
+ if(song==false){
+ getAccessToken(userID)
+ .then(accessToken => {
+ const spotifyApi = new SpotifyWebApi({ accessToken });
+
+ spotifyApi.getTrack(songUri.slice(14)) //returns a response object {}
+ .then(res => {
+ trackData = formatSong(res.body); //trackData is an object of a song
+ Song.create(trackData)
+ .then((songData) => resolve(songData))
+ .catch(err => console.log('HERE IS THE ERROR DETAILS:', err));
+ })
+ .catch(err => console.log('HERE IS THE ERROR DETAILS:', err));
+ })
+ .catch(err => console.log(err, " - THIS IS ERROR DETAILS"))
+
+ }else{
+
+ ////if song exists then return it
+ resolve(song)
+ }
+ })
+ })
+}
+
+module.exports = {
+ formatSong,
+ getSong
+}
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100755
index 0000000..b8596af
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,25 @@
+import React, { Component } from 'react';
+import {
+ HashRouter as Router,
+ Route, Switch
+} from 'react-router-dom';
+
+import HomeContainer from './containers/HomeContainer';
+import AdminContainer from './containers/AdminContainer';
+import LoginContainer from './containers/Logincontainer';
+import RoomContainer from './containers/RoomContainer';
+
+export default class Routes extends Component {
+ render() {
+ return (
+
Recently Playing
} +// Recently Playing
} -