diff --git a/build.gradle b/build.gradle index 1b1b83c..4d9d97f 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,7 @@ dependencies { providedCompile 'javax.servlet:javax.servlet-api:3.1.0' testCompile 'junit:junit:4.12' + testCompile 'com.jdblabs:db-migrate.groovy:0.2.5' testRuntime 'com.h2database:h2:1.4.186' } diff --git a/resources/test/testdb.init.sql b/resources/test/testdb.init.sql index 998aef2..cae6a25 100644 --- a/resources/test/testdb.init.sql +++ b/resources/test/testdb.init.sql @@ -14,20 +14,20 @@ INSERT INTO songs (name, artists) VALUES ('Glorious', 'Martha Munizzi'), ('Rez Power', 'Israel Houghton'); -INSERT INTO performances (service_id, song_id, pianist, organist, bassist, drummer, guitarist, leader) VALUES -(1, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), -(1, 2, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), -(1, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), -(2, 2, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), -(2, 3, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), -(2, 4, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), -(3, 1, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood'), -(3, 2, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood'), -(4, 3, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), -(5, 4, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Tony Bagliore', 'Rachel Wood'), -(6, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), -(7, 2, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), -(8, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'); +INSERT INTO performances (service_id, song_id, rank, pianist, organist, bassist, drummer, guitarist, leader) VALUES +(1, 1, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), +(1, 2, 2, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), +(1, 3, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), +(2, 2, 1, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), +(2, 3, 2, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), +(2, 4, 3, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), +(3, 1, 0, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood'), +(3, 2, 0, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood'), +(4, 3, 0, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'), +(5, 4, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Tony Bagliore', 'Rachel Wood'), +(6, 1, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), +(7, 2, 1, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'), +(8, 3, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'); INSERT INTO users (username, pwd, role) VALUES ('admin', '', 'admin'), diff --git a/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy b/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy index 19548a3..7bd0e88 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy @@ -152,10 +152,10 @@ public class NLSongsDB { public Performance create(Performance perf) { // TODO: handle constraint violation (same service and song ids) sql.executeInsert( - "INSERT INTO performances (service_id, song_id, pianist, " + + "INSERT INTO performances (service_id, song_id, rank, pianist, " + "organist, bassist, drummer, guitarist, leader) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?)", [perf.serviceId, perf.songId, - perf.pianist, perf.organist, perf.bassist, perf.drummer, + perf.rank, perf.pianist, perf.organist, perf.bassist, perf.drummer, perf.guitarist, perf.leader]) return perf } @@ -163,10 +163,11 @@ public class NLSongsDB { // TODO: handle constraint violation (same service and song ids) return sql.executeUpdate( "UPDATE performances SET pianist = ?, organist = ?, " + - "bassist = ?, drummer = ?, guitarist = ?, leader = ? " + - "WHERE service_id = ? AND song_id = ?", + "bassist = ?, drummer = ?, guitarist = ?, leader = ?, " + + "rank = ? WHERE service_id = ? AND song_id = ?", [perf.pianist, perf.organist, perf.bassist, perf.drummer, - perf.guitarist, perf.leader, perf.serviceId, perf.songId]) } + perf.guitarist, perf.leader, perf.rank, perf.serviceId, + perf.songId]) } public int delete(Performance perf) { sql.execute( diff --git a/src/main/groovy/com/jdbernard/nlsongs/model/Performance.groovy b/src/main/groovy/com/jdbernard/nlsongs/model/Performance.groovy index deef1a9..b35dcd1 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/model/Performance.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/model/Performance.groovy @@ -4,6 +4,7 @@ public class Performance implements Serializable { int serviceId int songId + int rank String pianist String organist String bassist @@ -19,6 +20,7 @@ public class Performance implements Serializable { return (this.serviceId == that.serviceId && this.songId == that.songId && + this.rank == that.rank && this.pianist == that.pianist && this.organist == that.organist && this.bassist == that.bassist && @@ -27,5 +29,5 @@ public class Performance implements Serializable { this.leader == that.leader) } @Override String toString() { - return "($serviceId, $songId): $leader - $pianist" } + return "($serviceId, $songId)-$rank: $leader - $pianist" } } diff --git a/src/main/sql/20170209113022-create-schema-down.sql b/src/main/sql/20170209113022-create-schema-down.sql new file mode 100644 index 0000000..51c0945 --- /dev/null +++ b/src/main/sql/20170209113022-create-schema-down.sql @@ -0,0 +1,9 @@ +-- # New Life Songs DB +-- @author Jonathan Bernard +-- +-- PostgreSQL database un-creation sript. +DROP TABLE performances; +DROP TABLE services; +DROP TABLE songs; +DROP TABLE tokens; +DROP TABLE users; diff --git a/src/main/sql/create-tables.sql b/src/main/sql/20170209113022-create-schema-up.sql similarity index 79% rename from src/main/sql/create-tables.sql rename to src/main/sql/20170209113022-create-schema-up.sql index fab82da..7ea3cea 100644 --- a/src/main/sql/create-tables.sql +++ b/src/main/sql/20170209113022-create-schema-up.sql @@ -4,8 +4,7 @@ -- PostgreSQL database creation sript. -- Services table -DROP TABLE IF EXISTS services; -CREATE TABLE IF NOT EXISTS services ( +CREATE TABLE services ( id SERIAL, date DATE NOT NULL, service_type VARCHAR(16) DEFAULT NULL, @@ -13,10 +12,8 @@ CREATE TABLE IF NOT EXISTS services ( CONSTRAINT uc_serviceTypeAndDate UNIQUE (date, service_type), PRIMARY KEY (id)); - -- Songs table -DROP TABLE IF EXISTS songs; -CREATE TABLE IF NOT EXISTS songs ( +CREATE TABLE songs ( id SERIAL, name VARCHAR(128) NOT NULL, artists VARCHAR(256) DEFAULT NULL, @@ -25,8 +22,7 @@ CREATE TABLE IF NOT EXISTS songs ( -- performances table -DROP TABLE IF EXISTS performances; -CREATE TABLE IF NOT EXISTS performances ( +CREATE TABLE performances ( service_id INTEGER NOT NULL, song_id INTEGER NOT NULL, pianist VARCHAR(64) DEFAULT NULL, @@ -40,16 +36,16 @@ CREATE TABLE IF NOT EXISTS performances ( FOREIGN KEY (song_id) REFERENCES songs (id) ON DELETE CASCADE); -DROP TABLE IF EXISTS users; -CREATE TABLE IF NOT EXISTS users ( +-- Users table +CREATE TABLE users ( id SERIAL, username VARCHAR(64) UNIQUE NOT NULL, pwd VARCHAR(80), role VARCHAR(16) NOT NULL, PRIMARY KEY (id)); -DROP TABLE IF EXISTS tokens; -CREATE TABLE IF NOT EXISTS tokens ( +-- Tokens table +CREATE TABLE tokens ( token VARCHAR(64), user_id INTEGER NOT NULL, expires TIMESTAMP NOT NULL, diff --git a/src/main/sql/20170209113258-add-song-sequence-down.sql b/src/main/sql/20170209113258-add-song-sequence-down.sql new file mode 100644 index 0000000..583d998 --- /dev/null +++ b/src/main/sql/20170209113258-add-song-sequence-down.sql @@ -0,0 +1,5 @@ +-- # New Life Songs DB +-- @author Jonathan Bernard +-- +-- Remove performances.rank +ALTER TABLE performances DROP COLUMN rank; diff --git a/src/main/sql/20170209113258-add-song-sequence-up.sql b/src/main/sql/20170209113258-add-song-sequence-up.sql new file mode 100644 index 0000000..4dd372d --- /dev/null +++ b/src/main/sql/20170209113258-add-song-sequence-up.sql @@ -0,0 +1,6 @@ +-- # New Life Songs DB +-- @author Jonathan Bernard +-- +-- Add performances.rank: the rank of the performance in the service, aka. the +-- "track number" if the service were an album. +ALTER TABLE performances ADD COLUMN rank integer NOT NULL DEFAULT 0; diff --git a/src/main/sql/create-schema.postgre.sql b/src/main/sql/create-schema.postgre.sql deleted file mode 100644 index 2ca8266..0000000 --- a/src/main/sql/create-schema.postgre.sql +++ /dev/null @@ -1,8 +0,0 @@ --- DROP DATABASE IF EXISTS nlsongs; -CREATE DATABASE nlsongs - ENCODING = 'UTF8' - LC_COLLATE = 'en_US.UTF-8' - LC_CTYPE = 'en_US.UTF-8' - CONNECTION LIMIT = 1; - -\c nlsongs diff --git a/src/main/sql/drop-tables.sql b/src/main/sql/drop-tables.sql deleted file mode 100644 index 1b7cd8c..0000000 --- a/src/main/sql/drop-tables.sql +++ /dev/null @@ -1,5 +0,0 @@ -DROP TABLE tokens; -DROP TABLE users; -DROP TABLE performances; -DROP TABLE songs; -DROP TABLE services; diff --git a/src/main/webapp/service/index.gsp b/src/main/webapp/service/index.gsp index 15e9fc1..eeab163 100644 --- a/src/main/webapp/service/index.gsp +++ b/src/main/webapp/service/index.gsp @@ -64,7 +64,7 @@ if (!service) { response.sendError(response.SC_NOT_FOUND); return } <% songsDB.findPerformancesForServiceId(service.id). collect { [perf: it, song: songsDB.findSong(it.songId)] }. - sort { it.song.name }.each { row -> %> + sort { it.song.name }.sort { it.perf.rank }.each { row -> %> <%= row.song.name %> diff --git a/src/test/groovy/com/jdbernard/nlsongs/service/NLSongsDBTest.groovy b/src/test/groovy/com/jdbernard/nlsongs/service/NLSongsDBTest.groovy index 8812205..985d9c5 100644 --- a/src/test/groovy/com/jdbernard/nlsongs/service/NLSongsDBTest.groovy +++ b/src/test/groovy/com/jdbernard/nlsongs/service/NLSongsDBTest.groovy @@ -3,6 +3,7 @@ package com.jdbernard.nlsongs.service import com.jdbernard.nlsongs.db.NLSongsDB import com.jdbernard.nlsongs.model.* import com.jdbernard.nlsongs.servlet.NLSongsContext +import com.jdblabs.dbmigrate.DbMigrate import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource @@ -24,8 +25,9 @@ import org.slf4j.LoggerFactory public class NLSongsDBTest { - static NLSongsDB songsDB; + static NLSongsDB songsDB static Sql sql + static DbMigrate dbmigrate static Logger log = LoggerFactory.getLogger(NLSongsDBTest) def dateFormat @@ -61,23 +63,23 @@ public class NLSongsDBTest { new Song(id: it[0], name: it[1], artists: it[2]) } this.performances = [ - [1, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'], - [1, 2, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'], - [1, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'], - [2, 2, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'], - [2, 3, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'], - [2, 4, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'], - [3, 1, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood'], - [3, 2, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood'], - [4, 3, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood'], - [5, 4, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Tony Bagliore', 'Rachel Wood'], - [6, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'], - [7, 2, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'], - [8, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood'] ].collect { + [1, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood', 1], + [1, 2, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood', 2], + [1, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood', 3], + [2, 2, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood', 1], + [2, 3, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood', 2], + [2, 4, 'Trevor Delano', 'Connie Bernard', 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood', 3], + [3, 1, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood', 0], + [3, 2, 'Rachel Wood', 'Krista Hatcher', 'Jonathan Bernard', 'Jared Wood', 'Tony Bagliore', 'Rachel Wood', 0], + [4, 3, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser', 'Rachel Wood', 0], + [5, 4, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Tony Bagliore', 'Rachel Wood', 1], + [6, 1, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood', 1], + [7, 2, 'Trevor Delano', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood', 1], + [8, 3, 'Jared Wood', null, 'Jonathan Bernard', 'Christian Thompson', 'Andrew Fraiser; Tony Bagliore', 'Rachel Wood', 1] ].collect { new Performance(serviceId: it[0], songId: it[1], pianist: it[2], organist: it[3], bassist: it[4], drummer: it[5], - guitarist: it[6], leader: it[7]) } + guitarist: it[6], leader: it[7], rank: it[8]) } } @BeforeClass @@ -90,8 +92,13 @@ public class NLSongsDBTest { HikariDataSource dataSource = new HikariDataSource(hcfg) // Create NLSongsDB - this.songsDB = new NLSongsDB(dataSource) - this.sql = new Sql(dataSource) + NLSongsDBTest.songsDB = new NLSongsDB(dataSource) + NLSongsDBTest.sql = new Sql(dataSource) + + // Setup our DB migration tool + NLSongsDBTest.dbmigrate = new DbMigrate( + migrationsDir: new File('src/main/sql'), + sql: NLSongsDBTest.sql) // Set NLSongsContext NLSongsContext.songsDB = songsDB } @@ -103,18 +110,18 @@ public class NLSongsDBTest { @Before public void initData() { - // Get the DB Schema and test data. - File createSchemaSql = new File("src/main/sql/create-tables.sql") - File testDataSql = new File("resources/test/testdb.init.sql") - - // Create the DB Schema - sql.execute(createSchemaSql.text) + // Create the DB schema + dbmigrate.up() // Populate the DB with test data. + File testDataSql = new File("resources/test/testdb.init.sql") sql.execute(testDataSql.text) } - /// ### Services + @After + public void destroyData() { + dbmigrate.down(Integer.MAX_VALUE) } + /// ### Services @Test public void shouldCreateService() { def service = new Service( date: new Date(), serviceType: ServiceType.SUN_AM) @@ -178,8 +185,8 @@ public class NLSongsDBTest { assertCollectionsEqual( performances.findAll { it.serviceId != 1 }, songsDB.findAllPerformances()) } - /// ### Songs + /// ### Songs @Test public void shoudCreateSong() { def song = new Song(name: "Test Song", artists: ["Bob Sam"]) def newSong = songsDB.create(song) @@ -247,5 +254,10 @@ public class NLSongsDBTest { log.info("C2: $c2") assertEquals(c1.size(), c2.size()) + c1.each { + def isPresent = c2.contains(it) + if (!isPresent) log.info("$it is not within $c2.") + assertTrue(isPresent) } + assertTrue(c1.every { c2.contains(it) }) } }