diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/assets/index.html | 25 | ||||
-rw-r--r-- | src/assets/thanks.html | 11 | ||||
-rw-r--r-- | src/index.js | 207 | ||||
-rw-r--r-- | src/templates/search_results.html | 34 | ||||
-rw-r--r-- | src/templates/submit_song_new.html | 29 | ||||
-rw-r--r-- | src/templates/submit_song_picklist.html | 15 | ||||
-rw-r--r-- | src/utils.js | 81 |
7 files changed, 402 insertions, 0 deletions
diff --git a/src/assets/index.html b/src/assets/index.html new file mode 100644 index 0000000..ada309b --- /dev/null +++ b/src/assets/index.html @@ -0,0 +1,25 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Solfege search</title> + </head> + <body> + <header> + <h1>Solfege search</h1> + </header> + <main> + <p>Enter some movable-do solfege syllables to search for:</p> + <p> + <form action="/search" method="get"> + <input type="text" autofocus name="q"> + <input type="submit" value="Search"> + </form> + </p> + <p>The search engine will recognize different forms, such as ti and si, and will understand both <kbd>+</kbd> and <kbd>#</kbd> as sharp, and <kbd>-</kbd> and <kbd>b</kbd> as flat.</p> + <!-- ♯♭ --> + </main> + <footer> + <p>By <a href="https://www.benburwell.com/">Ben Burwell</a> | <a href="https://github.com/benburwell/solfege">GitHub</a> | © 2015</p> + </footer> + </body> +</html> diff --git a/src/assets/thanks.html b/src/assets/thanks.html new file mode 100644 index 0000000..e6f6ff4 --- /dev/null +++ b/src/assets/thanks.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Thanks for being awesome!</title> + </head> + <body> + <h1>You rock!</h1> + <p>Thanks for helping out the solfege project! You gain ∞ life points! :D</p> + <p><a href="/">Head home</a></p> + </body> +</html> diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7edcfdb --- /dev/null +++ b/src/index.js @@ -0,0 +1,207 @@ +// require built-in modules +var fs = require('fs'); +var path = require('path'); + +// require external libs +var Hapi = require('hapi'); + +// require custom libs +var utils = require('./utils'); + +var server = new Hapi.Server(); +server.connection({ + host: 'localhost', + port: process.env.PORT || 8000 +}); + +server.views({ + engines: { + html: require('handlebars') + }, + path: path.join(__dirname, 'templates') +}); + +server.register({ + register: require('hapi-postgres'), + options: { + uri: process.env.DATABASE_URL || 'postgres://postgres:postgres@127.0.0.1/solfege' + } +}, function(err) { + if (err) { + console.error('Failed to load hapi-postgres', err); + } +}); + +server.method('search', function(query, next) { + var client = server.plugins['hapi-postgres'].client; + var done = server.plugins['hapi-postgres'].done; + + var norm = utils.normalize(query); + + var sql = 'SELECT title, artist_name FROM songs JOIN phrase_song ON (songs.song_id = phrase_song.song_id) WHERE phrase_song.phrase_id IN (SELECT phrase_id FROM phrases WHERE solfege LIKE \'%' + norm + '%\') GROUP BY songs.song_id;'; + return client.query(sql, function(err, results) { + done(); + + if (err) { + next(err); + } else { + next(null, results.rows); + } + }); +}); + +server.method('getSongsWithSimilarTitle', function(title, next) { + var client = server.plugins['hapi-postgres'].client; + var done = server.plugins['hapi-postgres'].done; + + var sql = 'SELECT song_id, title, artist_name FROM songs WHERE title @@ plainto_tsquery(\'' + title + '\');'; + return client.query(sql, function(err, results) { + done(); + if (err) { + next(err); + } else { + next(null, results.rows); + } + }); +}); + +server.method('addSongWithPhrase', function(options, next) { + var client = server.plugins['hapi-postgres'].client; + var done = server.plugins['hapi-postgres'].done; + + if (!options.title || !options.artist_name || !options.solfege) { + done(); + return next(new Error('Missing required title, artist_name, or solfege')); + } + + options.solfege = utils.normalize(options.solfege); + var sql = 'INSERT INTO songs (title, artist_name) VALUES (\'' + options.title + '\', \'' + options.artist_name + '\'); INSERT INTO phrases (solfege) VALUES (\'' + options.solfege + '\'); INSERT INTO phrase_song (song_id, phrase_id) VALUES ((SELECT CURRVAL(\'songs_song_id_seq\')), (SELECT CURRVAL(\'phrases_phrase_id_seq\')));'; + return client.query(sql, function(err, results) { + done(); + if (err) { + next(err); + } else { + next(null); + } + }); +}); + +server.method('addPhraseToSong', function(options, next) { + var client = server.plugins['hapi-postgres'].client; + var done = server.plugins['hapi-postgres'].done; + + if (!options.song_id || !options.phrase) { + done(); + return next(new Error('Missing required song_id or phrase')); + } else { + var solfege = utils.normalize(options.phrase); + var sql = 'INSERT INTO phrases (solfege) VALUES (\'' + solfege + '\'); INSERT INTO phrase_song (song_id, phrase_id) VALUES (' + options.song_id + ', (SELECT phrase_id FROM phrases WHERE solfege=\'' + solfege + '\'));'; + return client.query(sql, function(err, results) { + done(); + + if (err) { + next(new Error('Error adding phrase to song')); + } else { + next(null); + } + }); + } +}); + +// search results +server.route({ + method: 'GET', + path: '/search', + handler: function(request, reply) { + + var q = request.query.q; + + server.methods.search(request.query.q, function(err, results) { + if (err) { + reply('Error processing query: ' + err); + } else { + reply.view('search_results', { + original_query: q, + normalized_query: utils.normalize(q), + denormalized_query: utils.denormalize(utils.normalize(q)), + results: results + }); + } + }); + } +}); + +// submit a song +server.route({ + method: 'GET', + path: '/submit_song', + handler: function(request, reply) { + server.methods.getSongsWithSimilarTitle(request.query.title, function(err, songs) { + if (err) { + console.error(err) + reply('Error'); + } else { + if (songs.length > 0 && request.query.new != 1) { + reply.view('submit_song_picklist', { phrase: request.query.phrase, songs: songs }); + } else { + reply.view('submit_song_new', { + title: request.query.title, + phrase: request.query.phrase + }); + } + } + }); + } +}); + +server.route({ + method: 'POST', + path: '/submit_song', + handler: function(request, reply) { + server.methods.addSongWithPhrase({ + title: request.payload.title, + artist_name: request.payload.artist, + solfege: request.payload.phrase + }, function(err) { + if (err) { + console.log(err); + reply('Error processing request'); + } else { + reply.redirect('/thanks.html'); + } + }); + } +}); + +server.route({ + method: 'GET', + path: '/add_phrase', + handler: function(request, reply) { + server.methods.addPhraseToSong({ + song_id: request.query.id, + phrase: request.query.phrase + }, function(err) { + if (err) { + console.error(err); + reply('Error processing request'); + } else { + reply.redirect('/thanks.html'); + } + }); + } +}); + +// serve static files +server.route({ + method: 'GET', + path: '/{filename*}', + handler: { + directory: { + path: path.join(__dirname, 'assets') + } + } +}); + +server.start(function() { + console.log('Server running!'); +}); diff --git a/src/templates/search_results.html b/src/templates/search_results.html new file mode 100644 index 0000000..9bac74f --- /dev/null +++ b/src/templates/search_results.html @@ -0,0 +1,34 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Solfege search results - {{original_query}}</title> + </head> + <body> + <!-- Normalized: {{normalized_query}} --> + <!-- Denormalized: {{denormalized_query}} --> + + <h1>Solfege search results</h1> + <p> + <form action="/search" method="get"> + <input type="text" name="q" value="{{original_query}}"> + <input type="submit" value="Search"> + </form> + </p> + + <ol> + {{#each results}} + <li><b>{{title}} - {{artist_name}}</b></li> + {{/each}} + </ol> + + <h2>None of these right? Remebered the song?</h2> + <p>Submit the song this phrase is from!</p> + <p> + <form action="/submit_song"> + <input type="hidden" name="phrase" value="{{denormalized_query}}"> + <input type="text" name="title" placeholder="Song title"> + <input type="submit" value="Improve the world"> + </form> + </p> + </body> +</html> diff --git a/src/templates/submit_song_new.html b/src/templates/submit_song_new.html new file mode 100644 index 0000000..29d7529 --- /dev/null +++ b/src/templates/submit_song_new.html @@ -0,0 +1,29 @@ +<!doctype html> +<html> + <head> + <title>Submit a new song</title> + </head> + <body> + <h1>Submit a new song</h1> + <p>Tell us about the song (all fields required):</p> + <form action="/submit_song" method="post"> + <table> + <tr> + <td>Title:</td> + <td><input type="text" name="title" value="{{title}}"></td> + </tr> + <tr> + <td>Artist:</td> + <td><input type="text" name="artist" value="{{artist}}"></td> + </tr> + <tr> + <td>Phrase:</td> + <td><input type="text" name="phrase" value="{{phrase}}"></td> + </tr> + <tr> + <td colspan="2"><input type="submit" value="Win at life"></td> + </tr> + </table> + </form> + </body> +</html> diff --git a/src/templates/submit_song_picklist.html b/src/templates/submit_song_picklist.html new file mode 100644 index 0000000..b56c43f --- /dev/null +++ b/src/templates/submit_song_picklist.html @@ -0,0 +1,15 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Submit a song</title> + </head> + <body> + <h1>Did you mean one of these?</h1> + <ol> + {{#each songs}} + <li><a href="/add_phrase?phrase={{../phrase}}&id={{song_id}}">{{title}} by {{artist_name}}</a></li> + {{/each}} + </ol> + <a href="/submit_song?phrase={{phrase}}&title={{title}}&new=1">Nope, it's none of these</a> + </body> +</html> diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..4dc3458 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,81 @@ +module.exports = { + // Take some solfege input and normalize it + normalize: function(str) { + var tokens = str.toLowerCase().split(/\s+/); + var normalized = ''; + var table = { + 'do': 'a', + 'do+': 'b', + 'do#': 'b', + 'doh': 'a', + 'doh+': 'b', + 'doh#': 'b', + 're-': 'b', + 'reb': 'b', + 're': 'c', + 're+': 'd', + 're#': 'd', + 'mi-': 'd', + 'mib': 'd', + 'mi': 'e', + 'fa': 'f', + 'fa+': 'g', + 'fa#': 'g', + 'so-': 'g', + 'sob': 'g', + 'so': 'h', + 'so+': 'i', + 'so#': 'i', + 'sol-': 'g', + 'solb': 'g', + 'sol': 'h', + 'sol+': 'i', + 'sol#': 'i', + 'la-': 'i', + 'lab': 'i', + 'la': 'j', + 'la+': 'k', + 'la#': 'k', + 'ti-': 'k', + 'tib': 'k', + 'ti': 'l', + 'si-': 'k', + 'sib': 'k', + 'si': 'l' + }; + + for (var i = 0; i < tokens.length; i++) { + if (table[tokens[i]]) { + normalized += table[tokens[i]]; + } + } + + return normalized; + }, + + // Take some solfege from the DB and translate it back + // to something readable + denormalize: function(str) { + var table = { + 'a': 'do', + 'b': 'do+', + 'c': 're', + 'd': 're+', + 'e': 'mi', + 'f': 'fa', + 'g': 'fa+', + 'h': 'so', + 'i': 'so+', + 'j': 'la', + 'k': 'la+', + 'l': 'ti' + }; + var ret = ''; + + for (var i = 0; i < str.length; i++) { + ret += table[str.charAt(i)] + ' '; + } + + return ret.trim(); + } +}; |