From 05d708f563c75929e6d4f52dc9e659c663c4e3a7 Mon Sep 17 00:00:00 2001 From: Tim Winters Date: Mon, 7 Oct 2019 09:36:35 -0400 Subject: [PATCH 1/6] Initial commit --- .gitignore | 3 + package.json | 36 ++++++++ public/favicon.png | Bin 0 -> 3127 bytes public/global.css | 141 ++++++++++++++++++++++++++++++ public/index.html | 25 ++++++ public/queue.css | 15 ++++ public/queue.html | 51 +++++++++++ public/queue.js | 40 +++++++++ rollup.config.js | 50 +++++++++++ server.js | 209 +++++++++++++++++++++++++++++++++++++++++++++ src/App.svelte | 122 ++++++++++++++++++++++++++ src/main.js | 10 +++ 12 files changed, 702 insertions(+) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 public/favicon.png create mode 100644 public/global.css create mode 100644 public/index.html create mode 100644 public/queue.css create mode 100644 public/queue.html create mode 100644 public/queue.js create mode 100644 rollup.config.js create mode 100644 server.js create mode 100644 src/App.svelte create mode 100644 src/main.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..849d10a90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +node_modules +public/bundle.* diff --git a/package.json b/package.json new file mode 100644 index 000000000..5eec1074f --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "svelte-app", + "version": "1.0.0", + "devDependencies": { + "npm-run-all": "^4.1.5", + "rollup": "^1.12.0", + "rollup-plugin-commonjs": "^10.0.0", + "rollup-plugin-livereload": "^1.0.0", + "rollup-plugin-node-resolve": "^5.2.0", + "rollup-plugin-svelte": "^5.0.3", + "rollup-plugin-terser": "^4.0.4", + "svelte": "^3.12.1" + }, + "dependencies": { + "body-parser": "^1.19.0", + "dotenv": "^8.1.0", + "express": "^4.17.1", + "express-session": "^1.16.2", + "lowdb": "^1.0.0", + "passport": "^0.4.0", + "passport-spotify": "^1.1.0", + "request": "^2.88.0", + "serve-favicon": "^2.5.0", + "sirv-cli": "^0.4.4", + "sveltestrap": "^3.2.4" + }, + "scripts": { + "prestart-dev": "npm run build", + "build": "rollup -c", + "autobuild": "rollup -c -w", + "dev": "run-p start:dev autobuild", + "start": "sirv public --single", + "start:dev": "sirv public --single --dev", + "start-dev": "nodemon --ignore db.json server.js" + } +} diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6f5eb5a2f1f1c882d265cf479de25caa925645 GIT binary patch literal 3127 zcmV-749N3|P)i z7)}s4L53SJCkR}iVi00SFk;`MXX*#X*kkwKs@nFGS}c;=?XFjU|G$3t^5sjIVS2G+ zw)WGF83CpoGXhLGW(1gW%uV|X7>1P6VhCX=Ux)Lb!*DZ%@I3!{Gsf7d?gtIQ%nQiK z3%(LUSkBji;C5Rfgd6$VsF@H`Pk@xtY6t<>FNR-pD}=C~$?)9pdm3XZ36N5PNWYjb z$xd$yNQR9N!dfj-Vd@BwQo^FIIWPPmT&sZyQ$v81(sCBV=PGy{0wltEjB%~h157*t zvbe_!{=I_783x!0t1-r#-d{Y?ae$Q4N_Nd^Ui^@y(%)Gjou6y<3^XJdu{rmUf-Me?)zZ>9OR&6U5H*cK; z$gUlB{g0O4gN0sLSO|Of?hU(l?;h(jA3uH!Z{EBKuV23ouU@^Y6#%v+QG;>e*E}%?wlu-NT4DG zs)z)7WbLr)vGAu(ohrKc^em@OpO&f~6_>E61n_e0_V3@{U3^O;j{`^mNCJUj_>;7v zsMs6Hu3g7+@v+lSo;=yTYFqq}jZmQ-BK8K{C4kqi_i*jBaQE(Au0607V-zKeT;EPg zX(`vrn=L+e74+-Tqeok@_`tDa$G9I|$nTU5H*2V8@y()n*zqM?J1G!-1aX;CfDC9B zTnJ#j_%*n8Qb1)re*Bno7g0RG{Eb;IK14irJYJp$5Z6ac9~b_P?+5t~95~SRG$g?1 znFJ7p$xV&GZ18m~79TGRdfsc-BcX$9yXTR*n)mPD@1~O(_?cT$ZvFPucRmGlq&se0 zKrcUf^k}4hM*biEJOWKzz!qQe;CB_ZtSOO9Owg#lZAc=s65^rb{fZe(TYu_rk!wKkEf}RIt=#Om( zR8mN`DM<^xj~59euMMspBolVN zAPTr8sSDI104orIAdmL$uOXn*6hga1G+0WD0E?UtabxC#VC~vf3|10|phW;yQ3CY8 z2CM=)ErF;xq-YJ5G|um}>*1#E+O_Mu|Nr#qQ&G1P-NMq@f?@*XUcSbV?tX=)ilM-Q zBZP|!Bpv0V;#ojKcpc7$=eqO;#Uy~#?^kNI{vSZfLx&DEt~LTmaKWXcx=joubklI<*Aw z>LtMaQ7DR<1I2LkWvwyu#Rwn~;ezT}_g(@5l3h?W%-a86Y-t#O1PubP+z<%?V5D(U zy57A6{h+{?kOZp7&WKZR+=sznMJ}+Dnpo=C_0%R_x_t~J5T?E_{+))l5v1%52>)d-`iiZyx|5!%M2Fb2dU zW3~MwwpEH9Rhue+k$UIOoo($Ds!NbOyMR36fRHu;*15(YcA7siIZk#%JWz>P!qX1?IUojG&nKR>^gArBt2 zit(ETyZ=@V&7mv_Fi4bABcnwP+jzQuHcfU&BrAV91u-rFvEi7y-KnWsvHH=d2 zgAk(GKm_S8RcTJ>2N3~&Hbwp{Z3NF_Xeh}g4Eke)V&dY{W(3&b1j9t4yK_aYJisZZ{1rcU5- z;eD>K;ndPq&B-8yA_S0F!4ThA&{1{x)H<#?k9a#6Pc6L?V^s0``ynL&D;p(!Nmx`Y zFkHex{4p!Ggm^@DlehW}iHHVi}~u=$&N? z(NEBLQ#UxxAkdW>X9LnqUr#t4Lu0=9L8&o>JsqTtT5|%gb3QA~hr0pED71+iFFr)dZ=Q=E6ng{NE{Z~0)C?deO#?Aj zSDQ$z#TeC2T^|=}6GBo-&$;E{HL3!q3Z-szuf)O=G#zDjin4SSP%o%6+2IT#sLjQa ziyxFFz~LMjWY+_a5H!U6%a<=b7QVP^ z*90a62;bVq{?@)P6^DWd^Yilq4|YTV2Nw!Yu;a1lPI-sxR)rf@Fe5DhDP7FH zZZ%4S*1C30P;|O+jB!1;m|rXT90Sm5*RBbQN`PKu+hDD*S^yE(CdtSfg=z>u$cIj> z + + + + + + Svelte app + + + + + + + + + + + + + + + diff --git a/public/queue.css b/public/queue.css new file mode 100644 index 000000000..7f0792ae7 --- /dev/null +++ b/public/queue.css @@ -0,0 +1,15 @@ +body { + background: #181818; +} +table { + border-collapse: collapse; + background: inherit !important; +} + +td { + padding: .5em; +} + +.fas:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/public/queue.html b/public/queue.html new file mode 100644 index 000000000..4cc78b53a --- /dev/null +++ b/public/queue.html @@ -0,0 +1,51 @@ + + + + + CS4241 Assignment 2 + + + + + + + + + + + + + + +
+ + + + + + + + + +
TitleArtistStart Time
+
+
+ + + + diff --git a/public/queue.js b/public/queue.js new file mode 100644 index 000000000..349a4bbba --- /dev/null +++ b/public/queue.js @@ -0,0 +1,40 @@ +window.onload = function () { + fetch('/queue') + .then((res) => res.json()) + .then((data) => { + $('.results-table tbody').remove() + $('.results-table thead').css('visibility', 'visible') + let tbody = $('').appendTo($('.results-table')) + for (let track of data) { + let date = new Date(track.startTime); + let timestamp = `${date.getHours()}:${date.getMinutes()}` + $('').appendTo(tbody) + .append(`${track.ownedByUser ? '' : ''}`) + .append(`${track.title}`) + .append(`${track.artist}`) + .append(`${timestamp}`) + .append(`${track.id}`) + } + }) + $(document).on('click', '.fa-trash-alt', (e) => { + let $this = e.target.parentElement.parentElement + $.post({ + url: '/delete', + data: { + id: $this.children[4].innerHTML + }, + credentials: 'include' + }, () => { + // $($this).find("td").fadeOut('slow', () => $(this).parent().remove()) + $($this).fadeOut('slow', () => $(this).remove()) + }).fail((response) => { + let message = response.responseJSON.message; + console.log(message) + let snackbar = $("#snackbar") + snackbar.html(`Could not delete song: ${message}`) + snackbar.addClass("show") + // After 3 seconds, remove the show class from DIV + setTimeout(() => snackbar.removeClass("show"), 3000); + }) + }) +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 000000000..e5b94ec98 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,50 @@ +import svelte from 'rollup-plugin-svelte'; +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import livereload from 'rollup-plugin-livereload'; +import { terser } from 'rollup-plugin-terser'; + +const production = !process.env.ROLLUP_WATCH; + +export default { + input: 'src/main.js', + output: { + sourcemap: true, + format: 'iife', + name: 'app', + file: 'public/bundle.js' + }, + plugins: [ + svelte({ + // enable run-time checks when not in production + dev: !production, + // we'll extract any component CSS out into + // a separate file — better for performance + css: css => { + css.write('public/bundle.css'); + } + }), + + // If you have external dependencies installed from + // npm, you'll most likely need these plugins. In + // some cases you'll need additional configuration — + // consult the documentation for details: + // https://github.com/rollup/rollup-plugin-commonjs + resolve({ + browser: true, + dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/') + }), + commonjs(), + + // Watch the `public` directory and refresh the + // browser on changes when not in production + !production && livereload('public'), + + // If we're building for production (npm run build + // instead of npm run dev), minify + production && terser() + ], + watch: { + clearScreen: false + } +}; diff --git a/server.js b/server.js new file mode 100644 index 000000000..dca3deba3 --- /dev/null +++ b/server.js @@ -0,0 +1,209 @@ +require('dotenv').config(); +require('fs'); +require('http'); +require('mime'); +const + bodyparser = require('body-parser'), + express = require('express'), + favicon = require('serve-favicon'), + FileSync = require('lowdb/adapters/FileSync'), + low = require('lowdb'), + passport = require('passport'), + path = require('path'), + querystring = require('querystring'), + request = require('request'), + session = require('express-session'), + SpotifyStrategy = require('passport-spotify').Strategy, + adapter = new FileSync('db.json'), + app = express(), + db = low(adapter), + port = 3000, + client_id = process.env.client_id, + client_secret = process.env.client_secret, + redirect_uri = process.env.spotify_callback; + +passport.use( + new SpotifyStrategy({ + clientID: client_id, + clientSecret: client_secret, + callbackURL: redirect_uri, + }, + function(accessToken, refreshToken, expires_in, profile, done) { + let stored = db.get('users').find({ + id: profile.id, + }); + + if (stored.value()) { + stored.assign({ + token: accessToken, + }).write(); + } else { + db.get('users').push({ + id: profile.id, + name: profile.display_name, + token: accessToken, + }).write(); + } + process.nextTick(() => done(null, profile)); + }, + ), +); + +passport.serializeUser(function(user, done) { + done(null, { + id: user.id, + name: user.name, + }); +}); + +passport.deserializeUser(function(user, done) { + let user_ = db.get('users').find({ + id: user.id, + }).value(); + if (user_) { + done(null, user_); + } else { + done(null, false); + } +}); + +app.use(bodyparser.json()); +app.use(bodyparser.urlencoded({ + extended: true, +})); +app.use(express.static('public')); +app.use(favicon(path.join(__dirname, 'public', 'favicon.png'))); +app.use(session({ + secret: 'spotify', + resave: false, + saveUninitialized: true, +})); +app.use(passport.initialize()); +app.use(passport.session()); + +app.get('/login', passport.authenticate('spotify'), function(req, res) { + // The request will be redirected to spotify for authentication, so this + // function will not be called. +}); + +app.get('/search', (req, res) => { + if (!req.user) { + req.user = { + token: 'invalid', + }; + } + let options = { + url: `https://api.spotify.com/v1/search?${querystring.stringify( + req.query)}&type=track`, + headers: { + Authorization: `Bearer ${req.user.token}`, + }, + json: true, + }; + console.log(options.url); + request.get(options, (err, response, body) => { + if (err) { + console.log(err); + } + res.json(body); + }); + +}); + +app.post('/add', (req, res) => { + request.get({ + url: `https://api.spotify.com/v1/tracks/${req.body.id}`, + headers: { + Authorization: `Bearer ${req.user.token}`, + }, + json: true, + }, (err, response, body) => { + if (err) { + console.log(err); + } + let song = body; + console.log(db.get('songs').find({ + id: song.id.trim(), + }).value() != null); + if (db.get('songs').find({ + id: song.id.trim(), + }).value() != null) { + res.status(200).send({ + message: 'The song is already in the queue', + }); + } else { + let queue = db.get('songs').value(); + db.get('songs').push({ + id: song.id.trim(), + title: song.name.trim(), + artist: song.artists[0].name.trim(), + album: song.album.name.trim(), + adder: req.user.id.trim(), + duration: parseInt(song.duration_ms), + startTime: queue[queue.length - 1] ? + queue[queue.length - 1].startTime + + queue[queue.length - 1].duration : + new Date().getTime(), + }).write(); + res.status(200).send({ + message: `Added ${song.name} to queue`, + }); + } + }); +}); + +app.get('/queue', (req, res) => { + let songs = db.get('songs').cloneDeep().value(); + songs.forEach((song) => { + if (req.user && song.adder === req.user.id) { + song.ownedByUser = true; + } + }); + res.json(songs); + +}); + +app.post('/delete', (req, res) => { + if (!req.user) { + res.status(403).send({ + message: 'You are not logged in', + }); + return; + } + if (!db.get('songs').has({ + id: req.body.id, + })) { + res.status(404).send({ + message: 'Song not in queue', + }); + return; + } + let song = db.get('songs').find({ + id: req.body.id, + }).value(); + if (song.adder === req.user.id) { + // remove songs + db.get('songs').remove({ + id: req.body.id, + adder: req.user.id, + }).write(); + res.status(200).send({ + message: 'Removed song from queue', + }); + return; + } + res.status(403).send({ + message: 'Unauthorized access', + }); +}); + +app.get('/callback', passport.authenticate('spotify', { + failureRedirect: '/login', + }), + function(req, res) { + res.redirect('/'); + }, +); + +app.listen(process.env.PORT || port, + () => console.log(`Listening on port ${port}`)); diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 000000000..d070effcc --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,122 @@ + + + + + + Spotify Jukebox + + + + + +
+
+
+ + +
+
+ +
+ + + + + + + + + + {#each searchResults as song} + + + + + + + + + {:else} + {/each} +
TitleArtistAlbumLength
{song.name}{song.artists[0].name}{song.album.name}{moment(song.duration_ms).format("m:ss")}
+
+
+
{snackbar.message}
diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..d6cacbbb0 --- /dev/null +++ b/src/main.js @@ -0,0 +1,10 @@ +import App from './App.svelte'; + +const app = new App({ + target: document.body, + props: { + name: 'world' + } +}); + +export default app; \ No newline at end of file From 50b836d0788f03b540ea7e3dd53d13e34f71e3d3 Mon Sep 17 00:00:00 2001 From: Tim Winters Date: Mon, 7 Oct 2019 09:51:48 -0400 Subject: [PATCH 2/6] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5eec1074f..5cd4d74df 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "rollup -c", "autobuild": "rollup -c -w", "dev": "run-p start:dev autobuild", - "start": "sirv public --single", + "start": "node server.js", "start:dev": "sirv public --single --dev", "start-dev": "nodemon --ignore db.json server.js" } From 999c139d137b8ee780019cc40be14ec24f5d26b2 Mon Sep 17 00:00:00 2001 From: Tim Winters Date: Mon, 7 Oct 2019 10:03:02 -0400 Subject: [PATCH 3/6] Add database --- db.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 db.json diff --git a/db.json b/db.json new file mode 100644 index 000000000..0965817a3 --- /dev/null +++ b/db.json @@ -0,0 +1,24 @@ +{ + "songs": [ + { + "id": "4sPmO7WMQUAf45kwMOtONw", + "title": "Hello", + "artist": "Adele", + "album": "25", + "adder": "1270753891", + "duration": 295493, + "startTime": 1568593740810 + }, + { + "id": "6dGnYIeXmHdcikdzNNDMm2", + "title": "Here Comes The Sun - Remastered 2009", + "artist": "The Beatles", + "album": "Abbey Road (Remastered)", + "adder": "12169093646", + "duration": 185733, + "startTime": 1568594296249 + } + ], + "users": [ + ], +} From e9379b289d144c363cc56638e2733db297f0aa22 Mon Sep 17 00:00:00 2001 From: Tim Winters Date: Mon, 7 Oct 2019 10:07:32 -0400 Subject: [PATCH 4/6] Run build on start --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cd4d74df..79778d491 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "rollup -c", "autobuild": "rollup -c -w", "dev": "run-p start:dev autobuild", - "start": "node server.js", + "start": "npm run build && node server.js", "start:dev": "sirv public --single --dev", "start-dev": "nodemon --ignore db.json server.js" } From 661689f1a505950a366e1ec011a63ef24591c6e7 Mon Sep 17 00:00:00 2001 From: Tim Winters Date: Mon, 7 Oct 2019 10:11:36 -0400 Subject: [PATCH 5/6] Fix db.json format --- db.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db.json b/db.json index 0965817a3..c79064659 100644 --- a/db.json +++ b/db.json @@ -20,5 +20,5 @@ } ], "users": [ - ], + ] } From 3286a0f5f42def3359f8185eaa55bc63b4a418b0 Mon Sep 17 00:00:00 2001 From: Tim Winters Date: Mon, 7 Oct 2019 10:21:43 -0400 Subject: [PATCH 6/6] Update readme --- README.md | 43 +++++++------------------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 5eb349e1a..ca92efeb0 100755 --- a/README.md +++ b/README.md @@ -1,38 +1,9 @@ -Assignment 5 - Databases and/or Components -=== +## Spotify Jukebox -Due: October 7th, by 11:59 AM. +http://a5-archduketim.glitch.me -For this assignment you will complete one of the following tasks, based on your prior experience with the various technologies involved. - -1. Rework the server component from Assignment #3 to use MongoDB or some other NoSQL database (like CouchDB). You can remove Passport authentication if you choose, although this might be as much work as simply changing your Passport calls to use MongoDB. -2. Rework the client component from Assignment #3 to use Svelte in some capacity. -3. Rework the client component from Assignmeent #3 to use React in some capacity. - -For 2 and 3, make sure to look at [the notes from lecture 10](https://github.com/cs4241-19a/materials/blob/master/lecture10.markdown). - -This is really a chance for you to experiment with some additional web technologies that the prior assignments haven't covered yet: non-flatfile databases and web component frameworks. - -This project can be implemented on any hosting service (Glitch, DigitalOcean, Heroku etc.), however, you must include all files in your GitHub repo so that the course staff can view them; these files are not available for viewing in many hosting systems. - -Deliverables ---- - -Do the following to complete this assignment: - -1. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page on Glitch/Heroku/etc., it displays correctly. -4. Ensure that your project has the proper naming scheme `a5-yourname` so we can find it. -5. Fork this repository and modify the README to the specifications below. Be sure to add *all* project files. -6. Create and submit a Pull Request to the original repo. Name the pull request using the following template: `a5-gitname-firstname-lastname`. - -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- - -## Your Web Application Title - -your hosting link e.g. http://a5-charlieroberts.glitch.me - -Include a very brief summary of your project here and what you changed / added to assignment #3. Briefly (3–4 sentences) answer hte folloiwn question: did the new technology improve or hinder the development experience? - -Unlike previous assignments, this assignment will be solely graded on whether or not you successfully complete it. Partial credit will be generously given. \ No newline at end of file +For this assignment I reworked the main page of my a3 application to use svelte instead of using jquery/js to modify + the DOM. I think svelte definitely made creating the website easier because I had a lot of DOM modification when a + user searches and the table needs to be populated. With svelte, I can just use an `{:each}` block to dynamically + generate the html. In addition, the ability to use variables in the html code is a really cool feature that makes + changing classes super easer.