From a4279342d0d6c3ad92b04afff4c306ce55f10afc Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sat, 1 Aug 2015 22:28:12 -0400 Subject: start this puppy up --- .travis.yml | 5 ++ package.json | 21 +++++ src/utils.js | 106 ++++++++++++++++++++++++ test/test_denormalize.js | 56 +++++++++++++ test/test_normalize.js | 207 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 395 insertions(+) create mode 100644 .travis.yml create mode 100644 package.json create mode 100644 src/utils.js create mode 100644 test/test_denormalize.js create mode 100644 test/test_normalize.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7ce05b5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.12" +script: + - npm test diff --git a/package.json b/package.json new file mode 100644 index 0000000..876842d --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "solfege", + "version": "0.0.1", + "description": "A solfege database", + "main": "src/index.js", + "scripts": { + "test": "./node_modules/.bin/mocha -u tdd --reporter spec" + }, + "keywords": [ + "solfege" + ], + "author": "Ben Burwell ", + "license": "MIT", + "dependencies": { + "pg": "^4.4.1" + }, + "devDependencies": { + "mocha": "^2.2.5", + "should": "^7.0.2" + } +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..f2d1dc1 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,106 @@ +var fs = require('fs'); + +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(); + }, + + // Initialize the SQL database + initializeDatabase: function(db, done) { + + // Fetch the SQL we need to run + fs.readFile('../db/schema.sql', function(err, sql) { + if (err) { + return console.error('Error reading SQL from file', err); + } + + // Execute the query + db.query(sql, function(err, result) { + + if (err) { + console.error('Error executing SQL query', err); + } + + // Run the callback + done(); + }); + }); + } +}; diff --git a/test/test_denormalize.js b/test/test_denormalize.js new file mode 100644 index 0000000..5ac6464 --- /dev/null +++ b/test/test_denormalize.js @@ -0,0 +1,56 @@ +var should = require('should'); +var denormalize = require('../src/utils').denormalize; + +describe('Denormalize', function() { + it('should denormalize a', function() { + denormalize('a').should.be.exactly('do'); + }); + + it('should denormalize b', function() { + denormalize('b').should.be.exactly('do+'); + }); + + it('should denormalize c', function() { + denormalize('c').should.be.exactly('re'); + }); + + it('should denormalize d', function() { + denormalize('d').should.be.exactly('re+'); + }); + + it('should denormalize e', function() { + denormalize('e').should.be.exactly('mi'); + }); + + it('should denormalize f', function() { + denormalize('f').should.be.exactly('fa'); + }); + + it('should denormalize g', function() { + denormalize('g').should.be.exactly('fa+'); + }); + + it('should denormalize h', function() { + denormalize('h').should.be.exactly('so'); + }); + + it('should denormalize i', function() { + denormalize('i').should.be.exactly('so+'); + }); + + it('should denormalize j', function() { + denormalize('j').should.be.exactly('la'); + }); + + it('should denormalize k', function() { + denormalize('k').should.be.exactly('la+'); + }); + + it('should denormalize l', function() { + denormalize('l').should.be.exactly('ti'); + }); + + it('should denormalize multiple syllables', function() { + denormalize('ace').should.be.exactly('do re mi'); + }); +}); diff --git a/test/test_normalize.js b/test/test_normalize.js new file mode 100644 index 0000000..cfc7d15 --- /dev/null +++ b/test/test_normalize.js @@ -0,0 +1,207 @@ +var should = require('should'); +var normalize = require('../src/utils').normalize; + +describe('Normalize', function() { + + describe('Uppercasing', function() { + it('should normalize uppercase input', function() { + normalize('DO').should.be.exactly('a'); + }); + }); + + describe('Basic tests', function() { + + describe('A', function() { + it('should convert do to a', function() { + normalize('do').should.be.exactly('a'); + }); + + it('should convert doh to a', function() { + normalize('doh').should.be.exactly('a'); + }); + }); + + describe('B', function() { + it('should convert do+ to b', function() { + normalize('do+').should.be.exactly('b'); + }); + + it('should convert do# to b', function() { + normalize('do#').should.be.exactly('b'); + }); + + it('should convert doh+ to b', function() { + normalize('doh+').should.be.exactly('b'); + }); + + it('should convert doh# to b', function() { + normalize('doh#').should.be.exactly('b'); + }); + + it('should convert re- to b', function() { + normalize('re-').should.be.exactly('b'); + }); + + it('should convert reb to b', function() { + normalize('reb').should.be.exactly('b'); + }); + }); + + describe('C', function() { + it('should convert re to c', function() { + normalize('re').should.be.exactly('c'); + }); + }); + + describe('D', function() { + it('should convert re+ to d', function() { + normalize('re+').should.be.exactly('d'); + }); + + it('should convert re# to d', function() { + normalize('re#').should.be.exactly('d'); + }); + + it('should convert mi- to d', function() { + normalize('mi-').should.be.exactly('d'); + }); + + it('should convert mib to d', function() { + normalize('mib').should.be.exactly('d'); + }); + }); + + describe('E', function() { + it('should convert mi to e', function() { + normalize('mi').should.be.exactly('e'); + }); + }); + + describe('F', function() { + it('should convert fa to f', function() { + normalize('fa').should.be.exactly('f'); + }); + }); + + describe('G', function() { + it('should convert fa+ to g', function() { + normalize('fa+').should.be.exactly('g'); + }); + + it('should convert fa# to g', function() { + normalize('fa#').should.be.exactly('g'); + }); + + it('should convert so- to g', function() { + normalize('so-').should.be.exactly('g'); + }); + + it('should convert sob to g', function() { + normalize('sob').should.be.exactly('g'); + }); + + it('should convert sol- to g', function() { + normalize('sol-').should.be.exactly('g'); + }); + + it('should convert solb to g', function() { + normalize('solb').should.be.exactly('g'); + }); + }); + + describe('H', function() { + it('should convert so to h', function() { + normalize('so').should.be.exactly('h'); + }); + + it('should convert sol to h', function() { + normalize('sol').should.be.exactly('h'); + }); + }); + + describe('I', function() { + it('should convert so+ to i', function() { + normalize('so+').should.be.exactly('i'); + }); + + it('should convert so# to i', function() { + normalize('so#').should.be.exactly('i'); + }); + + it('should convert sol+ to i', function() { + normalize('sol+').should.be.exactly('i'); + }); + + it('should convert sol# to i', function() { + normalize('sol#').should.be.exactly('i'); + }); + + it('should convert la- to i', function() { + normalize('la-').should.be.exactly('i'); + }); + + it('should convert lab to i', function() { + normalize('lab').should.be.exactly('i'); + }); + }); + + describe('J', function() { + it('should convert la to j', function() { + normalize('la').should.be.exactly('j'); + }); + }); + + describe('K', function() { + it('should convert la+ to k', function() { + normalize('la+').should.be.exactly('k'); + }); + + it('should convert la# to k', function() { + normalize('la#').should.be.exactly('k'); + }); + + it('should convert ti- to k', function() { + normalize('ti-').should.be.exactly('k'); + }); + + it('should convert tib to k', function() { + normalize('tib').should.be.exactly('k'); + }); + + it('should convert si- to k', function() { + normalize('si-').should.be.exactly('k'); + }); + + it('should convert sib to k', function() { + normalize('sib').should.be.exactly('k'); + }); + }); + + describe('L', function() { + it('should convert ti to l', function() { + normalize('ti').should.be.exactly('l'); + }); + + it('should convert si to l', function() { + normalize('si').should.be.exactly('l'); + }); + }); + + }); + + describe('Challenging input', function() { + it('should correctly interpret multiple syllables', function() { + normalize('do re mi').should.be.exactly('ace'); + }); + + it('should correctly tokenize unusual spacing', function() { + normalize('do re mi').should.be.exactly('ace'); + normalize('do\ + re mi').should.be.exactly('ace'); + }); + + it('should ignore non-tokenizable input', function() { + normalize('do asdn3 aweh fnhi re 2hha 1nnua mi').should.be.exactly('ace'); + }); + }); +}); -- cgit v1.2.3 From d35ce687049062e47c7154151f048e446bbb077b Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sun, 2 Aug 2015 17:29:12 -0400 Subject: rename test files --- test/denormalize.js | 56 +++++++++++++ test/normalize.js | 207 +++++++++++++++++++++++++++++++++++++++++++++++ test/test_denormalize.js | 56 ------------- test/test_normalize.js | 207 ----------------------------------------------- 4 files changed, 263 insertions(+), 263 deletions(-) create mode 100644 test/denormalize.js create mode 100644 test/normalize.js delete mode 100644 test/test_denormalize.js delete mode 100644 test/test_normalize.js diff --git a/test/denormalize.js b/test/denormalize.js new file mode 100644 index 0000000..5ac6464 --- /dev/null +++ b/test/denormalize.js @@ -0,0 +1,56 @@ +var should = require('should'); +var denormalize = require('../src/utils').denormalize; + +describe('Denormalize', function() { + it('should denormalize a', function() { + denormalize('a').should.be.exactly('do'); + }); + + it('should denormalize b', function() { + denormalize('b').should.be.exactly('do+'); + }); + + it('should denormalize c', function() { + denormalize('c').should.be.exactly('re'); + }); + + it('should denormalize d', function() { + denormalize('d').should.be.exactly('re+'); + }); + + it('should denormalize e', function() { + denormalize('e').should.be.exactly('mi'); + }); + + it('should denormalize f', function() { + denormalize('f').should.be.exactly('fa'); + }); + + it('should denormalize g', function() { + denormalize('g').should.be.exactly('fa+'); + }); + + it('should denormalize h', function() { + denormalize('h').should.be.exactly('so'); + }); + + it('should denormalize i', function() { + denormalize('i').should.be.exactly('so+'); + }); + + it('should denormalize j', function() { + denormalize('j').should.be.exactly('la'); + }); + + it('should denormalize k', function() { + denormalize('k').should.be.exactly('la+'); + }); + + it('should denormalize l', function() { + denormalize('l').should.be.exactly('ti'); + }); + + it('should denormalize multiple syllables', function() { + denormalize('ace').should.be.exactly('do re mi'); + }); +}); diff --git a/test/normalize.js b/test/normalize.js new file mode 100644 index 0000000..cfc7d15 --- /dev/null +++ b/test/normalize.js @@ -0,0 +1,207 @@ +var should = require('should'); +var normalize = require('../src/utils').normalize; + +describe('Normalize', function() { + + describe('Uppercasing', function() { + it('should normalize uppercase input', function() { + normalize('DO').should.be.exactly('a'); + }); + }); + + describe('Basic tests', function() { + + describe('A', function() { + it('should convert do to a', function() { + normalize('do').should.be.exactly('a'); + }); + + it('should convert doh to a', function() { + normalize('doh').should.be.exactly('a'); + }); + }); + + describe('B', function() { + it('should convert do+ to b', function() { + normalize('do+').should.be.exactly('b'); + }); + + it('should convert do# to b', function() { + normalize('do#').should.be.exactly('b'); + }); + + it('should convert doh+ to b', function() { + normalize('doh+').should.be.exactly('b'); + }); + + it('should convert doh# to b', function() { + normalize('doh#').should.be.exactly('b'); + }); + + it('should convert re- to b', function() { + normalize('re-').should.be.exactly('b'); + }); + + it('should convert reb to b', function() { + normalize('reb').should.be.exactly('b'); + }); + }); + + describe('C', function() { + it('should convert re to c', function() { + normalize('re').should.be.exactly('c'); + }); + }); + + describe('D', function() { + it('should convert re+ to d', function() { + normalize('re+').should.be.exactly('d'); + }); + + it('should convert re# to d', function() { + normalize('re#').should.be.exactly('d'); + }); + + it('should convert mi- to d', function() { + normalize('mi-').should.be.exactly('d'); + }); + + it('should convert mib to d', function() { + normalize('mib').should.be.exactly('d'); + }); + }); + + describe('E', function() { + it('should convert mi to e', function() { + normalize('mi').should.be.exactly('e'); + }); + }); + + describe('F', function() { + it('should convert fa to f', function() { + normalize('fa').should.be.exactly('f'); + }); + }); + + describe('G', function() { + it('should convert fa+ to g', function() { + normalize('fa+').should.be.exactly('g'); + }); + + it('should convert fa# to g', function() { + normalize('fa#').should.be.exactly('g'); + }); + + it('should convert so- to g', function() { + normalize('so-').should.be.exactly('g'); + }); + + it('should convert sob to g', function() { + normalize('sob').should.be.exactly('g'); + }); + + it('should convert sol- to g', function() { + normalize('sol-').should.be.exactly('g'); + }); + + it('should convert solb to g', function() { + normalize('solb').should.be.exactly('g'); + }); + }); + + describe('H', function() { + it('should convert so to h', function() { + normalize('so').should.be.exactly('h'); + }); + + it('should convert sol to h', function() { + normalize('sol').should.be.exactly('h'); + }); + }); + + describe('I', function() { + it('should convert so+ to i', function() { + normalize('so+').should.be.exactly('i'); + }); + + it('should convert so# to i', function() { + normalize('so#').should.be.exactly('i'); + }); + + it('should convert sol+ to i', function() { + normalize('sol+').should.be.exactly('i'); + }); + + it('should convert sol# to i', function() { + normalize('sol#').should.be.exactly('i'); + }); + + it('should convert la- to i', function() { + normalize('la-').should.be.exactly('i'); + }); + + it('should convert lab to i', function() { + normalize('lab').should.be.exactly('i'); + }); + }); + + describe('J', function() { + it('should convert la to j', function() { + normalize('la').should.be.exactly('j'); + }); + }); + + describe('K', function() { + it('should convert la+ to k', function() { + normalize('la+').should.be.exactly('k'); + }); + + it('should convert la# to k', function() { + normalize('la#').should.be.exactly('k'); + }); + + it('should convert ti- to k', function() { + normalize('ti-').should.be.exactly('k'); + }); + + it('should convert tib to k', function() { + normalize('tib').should.be.exactly('k'); + }); + + it('should convert si- to k', function() { + normalize('si-').should.be.exactly('k'); + }); + + it('should convert sib to k', function() { + normalize('sib').should.be.exactly('k'); + }); + }); + + describe('L', function() { + it('should convert ti to l', function() { + normalize('ti').should.be.exactly('l'); + }); + + it('should convert si to l', function() { + normalize('si').should.be.exactly('l'); + }); + }); + + }); + + describe('Challenging input', function() { + it('should correctly interpret multiple syllables', function() { + normalize('do re mi').should.be.exactly('ace'); + }); + + it('should correctly tokenize unusual spacing', function() { + normalize('do re mi').should.be.exactly('ace'); + normalize('do\ + re mi').should.be.exactly('ace'); + }); + + it('should ignore non-tokenizable input', function() { + normalize('do asdn3 aweh fnhi re 2hha 1nnua mi').should.be.exactly('ace'); + }); + }); +}); diff --git a/test/test_denormalize.js b/test/test_denormalize.js deleted file mode 100644 index 5ac6464..0000000 --- a/test/test_denormalize.js +++ /dev/null @@ -1,56 +0,0 @@ -var should = require('should'); -var denormalize = require('../src/utils').denormalize; - -describe('Denormalize', function() { - it('should denormalize a', function() { - denormalize('a').should.be.exactly('do'); - }); - - it('should denormalize b', function() { - denormalize('b').should.be.exactly('do+'); - }); - - it('should denormalize c', function() { - denormalize('c').should.be.exactly('re'); - }); - - it('should denormalize d', function() { - denormalize('d').should.be.exactly('re+'); - }); - - it('should denormalize e', function() { - denormalize('e').should.be.exactly('mi'); - }); - - it('should denormalize f', function() { - denormalize('f').should.be.exactly('fa'); - }); - - it('should denormalize g', function() { - denormalize('g').should.be.exactly('fa+'); - }); - - it('should denormalize h', function() { - denormalize('h').should.be.exactly('so'); - }); - - it('should denormalize i', function() { - denormalize('i').should.be.exactly('so+'); - }); - - it('should denormalize j', function() { - denormalize('j').should.be.exactly('la'); - }); - - it('should denormalize k', function() { - denormalize('k').should.be.exactly('la+'); - }); - - it('should denormalize l', function() { - denormalize('l').should.be.exactly('ti'); - }); - - it('should denormalize multiple syllables', function() { - denormalize('ace').should.be.exactly('do re mi'); - }); -}); diff --git a/test/test_normalize.js b/test/test_normalize.js deleted file mode 100644 index cfc7d15..0000000 --- a/test/test_normalize.js +++ /dev/null @@ -1,207 +0,0 @@ -var should = require('should'); -var normalize = require('../src/utils').normalize; - -describe('Normalize', function() { - - describe('Uppercasing', function() { - it('should normalize uppercase input', function() { - normalize('DO').should.be.exactly('a'); - }); - }); - - describe('Basic tests', function() { - - describe('A', function() { - it('should convert do to a', function() { - normalize('do').should.be.exactly('a'); - }); - - it('should convert doh to a', function() { - normalize('doh').should.be.exactly('a'); - }); - }); - - describe('B', function() { - it('should convert do+ to b', function() { - normalize('do+').should.be.exactly('b'); - }); - - it('should convert do# to b', function() { - normalize('do#').should.be.exactly('b'); - }); - - it('should convert doh+ to b', function() { - normalize('doh+').should.be.exactly('b'); - }); - - it('should convert doh# to b', function() { - normalize('doh#').should.be.exactly('b'); - }); - - it('should convert re- to b', function() { - normalize('re-').should.be.exactly('b'); - }); - - it('should convert reb to b', function() { - normalize('reb').should.be.exactly('b'); - }); - }); - - describe('C', function() { - it('should convert re to c', function() { - normalize('re').should.be.exactly('c'); - }); - }); - - describe('D', function() { - it('should convert re+ to d', function() { - normalize('re+').should.be.exactly('d'); - }); - - it('should convert re# to d', function() { - normalize('re#').should.be.exactly('d'); - }); - - it('should convert mi- to d', function() { - normalize('mi-').should.be.exactly('d'); - }); - - it('should convert mib to d', function() { - normalize('mib').should.be.exactly('d'); - }); - }); - - describe('E', function() { - it('should convert mi to e', function() { - normalize('mi').should.be.exactly('e'); - }); - }); - - describe('F', function() { - it('should convert fa to f', function() { - normalize('fa').should.be.exactly('f'); - }); - }); - - describe('G', function() { - it('should convert fa+ to g', function() { - normalize('fa+').should.be.exactly('g'); - }); - - it('should convert fa# to g', function() { - normalize('fa#').should.be.exactly('g'); - }); - - it('should convert so- to g', function() { - normalize('so-').should.be.exactly('g'); - }); - - it('should convert sob to g', function() { - normalize('sob').should.be.exactly('g'); - }); - - it('should convert sol- to g', function() { - normalize('sol-').should.be.exactly('g'); - }); - - it('should convert solb to g', function() { - normalize('solb').should.be.exactly('g'); - }); - }); - - describe('H', function() { - it('should convert so to h', function() { - normalize('so').should.be.exactly('h'); - }); - - it('should convert sol to h', function() { - normalize('sol').should.be.exactly('h'); - }); - }); - - describe('I', function() { - it('should convert so+ to i', function() { - normalize('so+').should.be.exactly('i'); - }); - - it('should convert so# to i', function() { - normalize('so#').should.be.exactly('i'); - }); - - it('should convert sol+ to i', function() { - normalize('sol+').should.be.exactly('i'); - }); - - it('should convert sol# to i', function() { - normalize('sol#').should.be.exactly('i'); - }); - - it('should convert la- to i', function() { - normalize('la-').should.be.exactly('i'); - }); - - it('should convert lab to i', function() { - normalize('lab').should.be.exactly('i'); - }); - }); - - describe('J', function() { - it('should convert la to j', function() { - normalize('la').should.be.exactly('j'); - }); - }); - - describe('K', function() { - it('should convert la+ to k', function() { - normalize('la+').should.be.exactly('k'); - }); - - it('should convert la# to k', function() { - normalize('la#').should.be.exactly('k'); - }); - - it('should convert ti- to k', function() { - normalize('ti-').should.be.exactly('k'); - }); - - it('should convert tib to k', function() { - normalize('tib').should.be.exactly('k'); - }); - - it('should convert si- to k', function() { - normalize('si-').should.be.exactly('k'); - }); - - it('should convert sib to k', function() { - normalize('sib').should.be.exactly('k'); - }); - }); - - describe('L', function() { - it('should convert ti to l', function() { - normalize('ti').should.be.exactly('l'); - }); - - it('should convert si to l', function() { - normalize('si').should.be.exactly('l'); - }); - }); - - }); - - describe('Challenging input', function() { - it('should correctly interpret multiple syllables', function() { - normalize('do re mi').should.be.exactly('ace'); - }); - - it('should correctly tokenize unusual spacing', function() { - normalize('do re mi').should.be.exactly('ace'); - normalize('do\ - re mi').should.be.exactly('ace'); - }); - - it('should ignore non-tokenizable input', function() { - normalize('do asdn3 aweh fnhi re 2hha 1nnua mi').should.be.exactly('ace'); - }); - }); -}); -- cgit v1.2.3 From 0fb9fa6be86ced26feefd07a310371ee7d9b6185 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sun, 2 Aug 2015 17:29:36 -0400 Subject: add travis ci status to readme --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index a3721fb..6ee70e9 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,8 @@ Solfège ======= +![Travis-CI Status](https://api.travis-ci.org/benburwell/solfege.svg) + [Solfège](https://en.wikipedia.org/wiki/Solf%C3%A8ge) is a system of assigning syllables to musical pitches. This project attempts to enable people to find songs that they just can't quite remember the words for by searching for a solfege phrase. The solfège scale is: do, re, mi, fa, so, la, ti. In order to simplify searching, a "movable do" is used. This means that do does not always correspond to C; rather, it corresponds to the root of the key that the song is written in. -- cgit v1.2.3 From 2cde3ec173c57d24e6fb8a31700a7db4eb31564d Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sun, 2 Aug 2015 17:30:10 -0400 Subject: add app.json for heroku --- app.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app.json diff --git a/app.json b/app.json new file mode 100644 index 0000000..4d1b3f2 --- /dev/null +++ b/app.json @@ -0,0 +1,5 @@ +{ + "addons": [ + "heroku-postgresql:hobby-dev" + ] +} -- cgit v1.2.3 From 3d0f989cee9d7c426c759588539dd06c14fa70ea Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sun, 2 Aug 2015 17:31:36 -0400 Subject: let's get v1 going here --- db/schema.sql | 36 ++++++ package.json | 4 +- src/assets/index.html | 25 ++++ src/assets/thanks.html | 11 ++ src/index.js | 207 ++++++++++++++++++++++++++++++++ src/templates/search_results.html | 34 ++++++ src/templates/submit_song_new.html | 29 +++++ src/templates/submit_song_picklist.html | 15 +++ src/utils.js | 25 ---- 9 files changed, 360 insertions(+), 26 deletions(-) create mode 100644 db/schema.sql create mode 100644 src/assets/index.html create mode 100644 src/assets/thanks.html create mode 100644 src/index.js create mode 100644 src/templates/search_results.html create mode 100644 src/templates/submit_song_new.html create mode 100644 src/templates/submit_song_picklist.html diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..a7509da --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,36 @@ +-- Create the phrase table +-- This will hold the phrases with an associated ID +-- Phrases will be varchars of solfege syllables normalized as: +-- a = do +-- b = do+/re- +-- c = re +-- d = re+/mi- +-- e = mi +-- f = fa +-- g = fa+/so- +-- h = so +-- i = so+/la- +-- j = la +-- k = la+/ti- +-- l = ti +CREATE TABLE IF NOT EXISTS phrases ( + phrase_id BIGSERIAL PRIMARY KEY, + solfege VARCHAR(30) NOT NULL UNIQUE +); + +-- Create a table that allows us to have a many-to-many +-- relationship between phrases and songs (i.e., a phrase can be +-- contained within multiple songs, and a song can have multiple phrases). +CREATE TABLE IF NOT EXISTS phrase_song ( + phrase_id BIGINT NOT NULL, + song_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS songs ( + song_id BIGSERIAL PRIMARY KEY, + title VARCHAR(255), + artist_name VARCHAR(255) +); + +-- Now we need an index that will allow us to search more quickly for phrases +CREATE UNIQUE INDEX phrases_solfege ON phrases (solfege); diff --git a/package.json b/package.json index 876842d..fb3d90b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "author": "Ben Burwell ", "license": "MIT", "dependencies": { - "pg": "^4.4.1" + "handlebars": "^3.0.3", + "hapi": "^8.8.1", + "hapi-postgres": "^1.0.2" }, "devDependencies": { "mocha": "^2.2.5", 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 @@ + + + + Solfege search + + +
+

Solfege search

+
+
+

Enter some movable-do solfege syllables to search for:

+

+

+ + +
+

+

The search engine will recognize different forms, such as ti and si, and will understand both + and # as sharp, and - and b as flat.

+ +
+ + + 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 @@ + + + + Thanks for being awesome! + + +

You rock!

+

Thanks for helping out the solfege project! You gain ∞ life points! :D

+

Head home

+ + 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 @@ + + + + Solfege search results - {{original_query}} + + + + + +

Solfege search results

+

+

+ + +
+

+ +
    + {{#each results}} +
  1. {{title}} - {{artist_name}}
  2. + {{/each}} +
+ +

None of these right? Remebered the song?

+

Submit the song this phrase is from!

+

+

+ + + +
+

+ + 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 @@ + + + + Submit a new song + + +

Submit a new song

+

Tell us about the song (all fields required):

+
+ + + + + + + + + + + + + + + + +
Title:
Artist:
Phrase:
+
+ + 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 @@ + + + + Submit a song + + +

Did you mean one of these?

+
    + {{#each songs}} +
  1. {{title}} by {{artist_name}}
  2. + {{/each}} +
+ Nope, it's none of these + + diff --git a/src/utils.js b/src/utils.js index f2d1dc1..4dc3458 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,4 @@ -var fs = require('fs'); - module.exports = { - // Take some solfege input and normalize it normalize: function(str) { var tokens = str.toLowerCase().split(/\s+/); @@ -80,27 +77,5 @@ module.exports = { } return ret.trim(); - }, - - // Initialize the SQL database - initializeDatabase: function(db, done) { - - // Fetch the SQL we need to run - fs.readFile('../db/schema.sql', function(err, sql) { - if (err) { - return console.error('Error reading SQL from file', err); - } - - // Execute the query - db.query(sql, function(err, result) { - - if (err) { - console.error('Error executing SQL query', err); - } - - // Run the callback - done(); - }); - }); } }; -- cgit v1.2.3