Compare commits

..

No commits in common. "main" and "v2.3" have entirely different histories.
main ... v2.3

65 changed files with 314 additions and 364 deletions

View File

@ -1,14 +0,0 @@
branch-defaults:
master:
environment: new-life-songs-prod
global:
application_name: new-life-songs
branch: null
default_ec2_keyname: id_jdb@jdb-maingear
default_platform: Tomcat 8 Java 8
default_region: us-west-2
profile: eb-cli
repository: null
sc: git
deploy:
artifact: service/build/ROOT.war

View File

@ -1,24 +1,169 @@
plugins { id 'com.palantir.git-version' version '0.5.2' }
import org.apache.tools.ant.filters.ReplaceTokens
allprojects {
group = "com.jdbernard"
apply plugin: "groovy"
apply plugin: "maven"
apply plugin: "war"
apply plugin: "jetty"
buildscript {
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url 'https://mvn.jdb-labs.com/repo' }
}
}
apply from: 'shell.gradle'
repositories {
group = "com.jdbernard"
version = new ProjectVersion()
// webAppDirName = "build/webapp/main"
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://mvn.jdb-labs.com/repo" }
}
mavenCentral() }
dependencies {
compile 'ch.qos.logback:logback-classic:1.1.2'
compile 'ch.qos.logback:logback-core:1.1.2'
compile 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.3'
compile 'com.lambdaworks:scrypt:1.4.0'
compile 'com.zaxxer:HikariCP-java6:2.3.2'
compile 'javax:javaee-api:7.0'
compile 'javax.ws.rs:javax.ws.rs-api:2.0.1'
compile 'joda-time:joda-time:2.7'
compile 'org.codehaus.groovy:groovy-all:2.3.6'
compile 'org.slf4j:slf4j-api:1.7.10'
runtime 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.3.2'
runtime 'org.glassfish.jersey.containers:jersey-container-servlet:2.16'
runtime 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testCompile 'com.jdbernard:jdb-util:3.4'
testCompile 'junit:junit:4.12'
testRuntime 'com.h2database:h2:1.4.186'
apply plugin: 'com.palantir.git-version'
version = gitVersion()
}
war {
from "resources/webapp"
from "build/webapp"
filter(ReplaceTokens, tokens: [version: version])
rename '(.+)(\\..*(css|js))', '$1-' + version + '$2'
version = project.version.releaseVersion
webInf { from 'resources/main/WEB-INF' }
exclude "**/.*.swp", "**/.sass-cache"
}
test { testLogging { events 'failed' } }
task testWar(type: War) {
from 'resources/webapp'
filter(ReplaceTokens, tokens: [version: version])
rename '(.+)(\\..*(css|js))', '$1-' + version + '$2'
version = project.version.releaseVersion
webInf { from 'resources/test/WEB-INF' }
classifier 'test' }
task compileScss(
group: 'build',
description: 'Compile SCSS files into CSS.',
type: Exec
) {
executable "scss"
args "--update", "src/main/webapp/css:build/webapp/css"
}
war.dependsOn compileScss
testWar.dependsOn compileScss
// ## Build Versioning task
task incrementBuildNumber(
group: 'versioning',
description: "Increment the project's build number."
) << { ++version.build }
task incrementMinorNumber(
group: 'versioning',
description: "Increment the project's minor version number."
) << { ++version.minor }
task incrementMajorNumber(
group: 'versioning',
description: "Increment the project's major version number."
) << { ++version.major }
task markReleaseBuild(
group: 'versioning',
description: "Mark this version of the project as a release version."
) << { version.release = true }
war.dependsOn << incrementBuildNumber
testWar.dependsOn << incrementBuildNumber
// ## Custom tasks for local deployment
task deployLocal(dependsOn: ['build']) << {
def warName = "${project.name}-${version.releaseVersion}.war"
def jettyHome = System.getenv("JETTY_HOME")
def deployedWar = new File("$jettyHome/webapps/$warName")
if (deployedWar.exists()) deployedWar.delete();
copy {
from "build/libs"
into "$jettyHome/webapps"
include warName } }
task killJettyLocal() << {
def pidFile = new File(System.properties['user.home'] + "/temp/jetty.pid")
println "Killing old Jetty instance."
shell_("sh", "-c", 'kill $(jps -l | grep start.jar | cut -f 1 -d " ")') }
task localJetty(dependsOn: ['killJettyLocal', 'deployLocal']) << {
spawn(["java", "-jar", "start.jar"], new File(jettyHome)) }
// ## Project Version
class ProjectVersion {
private File versionFile
int major
int minor
int build
boolean release
public ProjectVersion() { this(new File('version.properties')) }
public ProjectVersion(File versionFile) {
this.versionFile = versionFile
if (!versionFile.exists()) {
versionFile.createNewFile()
this.major = this.minor = this.build = 0
this.save() }
else this.load() }
@Override String toString() { "$major.$minor${release ? '' : '-build' + build}" }
public String getReleaseVersion() { "$major.$minor" }
public void setRelease(boolean release) { this.release = release; save() }
public void setMajor(int major) {
this.major = major; minor = build = 0; release = false; save() }
public void setMinor(int minor) {
this.minor = minor; build = 0; release = false; save() }
public void setBuild(int build) { this.build = build; save() }
private void save() {
def props = new Properties()
versionFile.withInputStream { props.load(it) }
["major", "minor", "build"].each { props[it] = this[it].toString() }
props["version.release"] = release.toString()
versionFile.withOutputStream { props.store(it, "") } }
private void load() {
def props = new Properties()
versionFile.withInputStream { props.load(it) }
["major", "minor", "build"].each {
this[it] = props[it] ? props[it] as int : 0 }
release = Boolean.parseBoolean(props["version.release"]) }
}

View File

@ -0,0 +1,34 @@
INSERT INTO SERVICES (date, service_type) values
('2015-02-01', 'SUN_AM'),
('2015-02-01', 'SUN_PM'),
('2015-02-04', 'WED'),
('2015-02-08', 'SUN_AM'),
('2015-02-08', 'SUN_PM'),
('2015-02-11', 'WED'),
('2015-02-15', 'SUN_AM'),
('2015-02-15', 'SUN_PM');
INSERT INTO songs (name, artists) VALUES
('Breathe On Us', 'Kari Jobe'),
('How Great Is Our God', 'Chris Tomlin'),
('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 users (username, pwd, role) VALUES
('admin', '', 'admin'),
('test', '', '');

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,79 +0,0 @@
import org.apache.tools.ant.filters.ReplaceTokens
apply plugin: "groovy"
apply plugin: "maven"
apply plugin: "war"
// webAppDirName = "build/webapp/main"
buildscript {
dependencies {
classpath 'com.jdbernard:gradle-exec-util:0.2.0'
}
}
import static com.jdbernard.gradle.ExecUtil.*
dependencies {
compile localGroovy()
compile 'ch.qos.logback:logback-classic:1.1.8'
compile 'ch.qos.logback:logback-core:1.1.8'
compile 'org.slf4j:slf4j-api:1.7.22'
compile 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.6'
compile 'com.lambdaworks:scrypt:1.4.0'
compile 'com.zaxxer:HikariCP:2.5.1'
compile 'javax:javaee-api:7.0'
compile 'javax.ws.rs:javax.ws.rs-api:2.0.1'
compile 'joda-time:joda-time:2.7'
runtime 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.3.2'
runtime 'org.glassfish.jersey.containers:jersey-container-servlet:2.16'
runtime 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'
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'
}
war {
from "resources/webapp"
from "build/webapp"
filter(ReplaceTokens, tokens: [version: version])
rename '(.+)(\\..*(css|js))', '$1-' + version + '$2'
webInf { from 'resources/main/WEB-INF' }
exclude "**/.*.swp", "**/.sass-cache"
}
test { testLogging { events 'failed' } }
task testWar(type: War) {
from 'resources/webapp'
filter(ReplaceTokens, tokens: [version: version])
rename '(.+)(\\..*(css|js))', '$1-' + version + '$2'
webInf { from 'resources/test/WEB-INF' }
classifier 'test' }
task compileScss(
group: 'build',
description: 'Compile SCSS files into CSS.',
type: Exec
) {
executable "scss"
args "--update", "src/main/webapp/css:build/webapp/css"
}
war.dependsOn compileScss
testWar.dependsOn compileScss
task deployProd(dependsOn: ['build']) { doLast {
def warName = "${project.name}-${version}.war"
def artifactName = "ROOT.war"
copy {
from "build/libs"
into "build"
include warName
rename warName, artifactName }
exec("eb", "deploy", "-l", "${parent.name}-${project.name}-${version}")
} }

View File

@ -1,18 +0,0 @@
import ch.qos.logback.core.*;
import ch.qos.logback.core.encoder.*;
import ch.qos.logback.core.read.*;
import ch.qos.logback.core.rolling.*;
import ch.qos.logback.core.status.*;
import ch.qos.logback.classic.net.*;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
appender("STDOUT", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%level %logger - %msg%n"
}
}
root(INFO, ["STDOUT"])
logger('com.jdbernard', INFO)

View File

@ -1,34 +0,0 @@
INSERT INTO SERVICES (date, service_type) values
('2015-02-01', 'SUN_AM'),
('2015-02-01', 'SUN_PM'),
('2015-02-04', 'WED'),
('2015-02-08', 'SUN_AM'),
('2015-02-08', 'SUN_PM'),
('2015-02-11', 'WED'),
('2015-02-15', 'SUN_AM'),
('2015-02-15', 'SUN_PM');
INSERT INTO songs (name, artists) VALUES
('Breathe On Us', 'Kari Jobe'),
('How Great Is Our God', 'Chris Tomlin'),
('Glorious', 'Martha Munizzi'),
('Rez Power', 'Israel Houghton');
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'),
('test', '', '');

View File

@ -1,9 +0,0 @@
-- # New Life Songs DB
-- @author Jonathan Bernard <jdb@jdb-labs.com>
--
-- PostgreSQL database un-creation sript.
DROP TABLE performances;
DROP TABLE services;
DROP TABLE songs;
DROP TABLE tokens;
DROP TABLE users;

View File

@ -1,5 +0,0 @@
-- # New Life Songs DB
-- @author Jonathan Bernard <jdb@jdb-labs.com>
--
-- Remove performances.rank
ALTER TABLE performances DROP COLUMN rank;

View File

@ -1,6 +0,0 @@
-- # New Life Songs DB
-- @author Jonathan Bernard <jdb@jdb-labs.com>
--
-- 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;

View File

@ -1,6 +0,0 @@
#
#Sat Dec 17 21:52:48 CST 2016
major=2
version.release=true
minor=5
build=2

View File

@ -1 +0,0 @@
include 'service', 'uploader'

33
shell.gradle Normal file
View File

@ -0,0 +1,33 @@
// ## Utility methods for working with processes.
def shell_(List<String> cmd) { shell(cmd, null, false) }
def shell_(String... cmd) { shell(cmd, null, false) }
def shell(String... cmd) { shell(cmd, null, true) }
def shell(List<String> cmd, File workingDir, boolean checkExit) {
shell(cmd as String[], workingDir, checkExit) }
def shell(String[] cmd, File workingDir, boolean checkExit) {
def pb = new ProcessBuilder(cmd)
if (workingDir) pb.directory(workingDir)
def process = pb.start()
process.waitForProcessOutput(System.out, System.err)
if (process.exitValue() != 0)
println "Command $cmd exited with non-zero result code."
if (checkExit) assert process.exitValue() == 0 : "Not ignoring failed command." }
def shell(List<List<String>> cmds, File workingDir) {
cmds.each {
ProcessBuilder pb = new ProcessBuilder(it)
pb.directory(workingDir)
pb.start().waitForProcessOutput(System.out, System.err) } }
def spawn(String... cmd) { spawn(cmd, null) }
def spawn(List<String> cmd, File workingDir) { spawn(cmd as String[], workingDir) }
def spawn(String[] cmd, File workingDir) {
def pb = new ProcessBuilder(cmd)
if (workingDir) pb.directory(workingDir)
def process = pb.start() }

View File

@ -27,7 +27,10 @@ public class NLSongsDB {
/// ### Common
public def save(def model) {
if (model.id > 0) return update(model)
else return create(model) }
else {
if (create(model) > 0) return model
else return null } }
/// ### Services
public Service findService(int id) {
@ -152,10 +155,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, rank, pianist, " +
"INSERT INTO performances (service_id, song_id, pianist, " +
"organist, bassist, drummer, guitarist, leader) VALUES " +
"(?, ?, ?, ?, ?, ?, ?, ?)", [perf.serviceId, perf.songId,
perf.rank, perf.pianist, perf.organist, perf.bassist, perf.drummer,
perf.pianist, perf.organist, perf.bassist, perf.drummer,
perf.guitarist, perf.leader])
return perf }
@ -163,11 +166,10 @@ public class NLSongsDB {
// TODO: handle constraint violation (same service and song ids)
return sql.executeUpdate(
"UPDATE performances SET pianist = ?, organist = ?, " +
"bassist = ?, drummer = ?, guitarist = ?, leader = ?, " +
"rank = ? WHERE service_id = ? AND song_id = ?",
"bassist = ?, drummer = ?, guitarist = ?, leader = ? " +
"WHERE service_id = ? AND song_id = ?",
[perf.pianist, perf.organist, perf.bassist, perf.drummer,
perf.guitarist, perf.leader, perf.rank, perf.serviceId,
perf.songId]) }
perf.guitarist, perf.leader, perf.serviceId, perf.songId]) }
public int delete(Performance perf) {
sql.execute(
@ -313,7 +315,7 @@ public class NLSongsDB {
return buildToken(row, user) }
public static List<String> unwrapArtists(String artists) {
return artists.split(':') as List<String> }
return artists.split(';') as List<String> }
public static String wrapArtists(List<String> artists) {
return artists.join(':') }

View File

@ -4,7 +4,6 @@ public class Performance implements Serializable {
int serviceId
int songId
int rank
String pianist
String organist
String bassist
@ -20,7 +19,6 @@ 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 &&
@ -29,5 +27,5 @@ public class Performance implements Serializable {
this.leader == that.leader) }
@Override String toString() {
return "($serviceId, $songId)-$rank: $leader - $pianist" }
return "($serviceId, $songId): $leader - $pianist" }
}

View File

@ -16,7 +16,7 @@ public class Service implements Serializable {
Service that = (Service) thatObj
return (this.id == that.id &&
this.date == (that.localDate) &&
this.date == (that.@date) &&
this.serviceType == that.serviceType) }
public void setDate(Date date) { this.date = LocalDate.fromDateFields(date) }
@ -26,8 +26,4 @@ public class Service implements Serializable {
public Date getDate() { return this.date.toDate() }
public String toString() { return "$id: $date - $serviceType" }
// Needed only because the @directFieldAccesor syntax stopped working in
// Groovy 2.4.7
private LocalDate getLocalDate() { return this.date }
}

View File

@ -7,12 +7,11 @@ import com.jdbernard.nlsongs.model.Song
public class NLSongsContext {
public static NLSongsDB songsDB
public static String mediaBaseUrl
public static String makeUrl(Service service, Song song) {
return mediaBaseUrl + '/' + service.localDate.toString('yyyy') + "/" +
service.localDate.toString('yyyy-MM-dd') + '_' +
return mediaBaseUrl + '/' + service.@date.toString('yyyy-MM-dd') + '_' +
service.serviceType.name().toLowerCase() + '_' +
song.name.replaceAll(/[\s'"\\\/\?!]/, '') + '.mp3' }
}

View File

@ -9,40 +9,20 @@ import com.jdbernard.nlsongs.db.NLSongsDB
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
public final class NLSongsContextListener implements ServletContextListener {
private static final log = LoggerFactory.getLogger(NLSongsContextListener)
public void contextInitialized(ServletContextEvent event) {
def context = event.servletContext
// Load the context configuration.
Properties props = new Properties()
// Load configuration details from the context configuration.
NLSongsContextListener.getResourceAsStream(
context.getInitParameter('context.config.file'))
.withStream { is -> props.load(is) }
// Load database configuration
Properties dataSourceProps = new Properties()
String dbConfigFile = context.getInitParameter('datasource.config.file')
if (dbConfigFile) {
NLSongsContextListener.getResourceAsStream(dbConfigFile)
.withStream { is -> dataSourceProps.load(is) } }
// Load database configuration from environment variables (may
// override settings in file).
System.properties.keySet().findAll { it.startsWith('DB_') }.each { key ->
dataSourceProps["dataSource.${key.substring(3)}"] = System.properties[key] }
log.debug("Database configuration: {}", dataSourceProps)
context.getInitParameter('context.config.file')).withStream { is ->
props.load(is) }
// Create the pooled data source
HikariConfig hcfg = new HikariConfig(dataSourceProps)
HikariConfig hcfg = new HikariConfig(
context.getInitParameter('datasource.config.file'))
HikariDataSource hds = new HikariDataSource(hcfg)
@ -59,6 +39,6 @@ public final class NLSongsContextListener implements ServletContextListener {
// Shutdown the Songs DB instance (it will shut down the data source).
NLSongsDB songsDB = context.getAttribute('songsDB')
if (songsDB) songsDB.shutdown()
context.removeAttribute('songsDB') }
}

View File

@ -0,0 +1,8 @@
-- 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

View File

@ -4,7 +4,8 @@
-- PostgreSQL database creation sript.
-- Services table
CREATE TABLE services (
DROP TABLE IF EXISTS services;
CREATE TABLE IF NOT EXISTS services (
id SERIAL,
date DATE NOT NULL,
service_type VARCHAR(16) DEFAULT NULL,
@ -12,8 +13,10 @@ CREATE TABLE services (
CONSTRAINT uc_serviceTypeAndDate UNIQUE (date, service_type),
PRIMARY KEY (id));
-- Songs table
CREATE TABLE songs (
DROP TABLE IF EXISTS songs;
CREATE TABLE IF NOT EXISTS songs (
id SERIAL,
name VARCHAR(128) NOT NULL,
artists VARCHAR(256) DEFAULT NULL,
@ -22,7 +25,8 @@ CREATE TABLE songs (
-- performances table
CREATE TABLE performances (
DROP TABLE IF EXISTS performances;
CREATE TABLE IF NOT EXISTS performances (
service_id INTEGER NOT NULL,
song_id INTEGER NOT NULL,
pianist VARCHAR(64) DEFAULT NULL,
@ -36,16 +40,16 @@ CREATE TABLE performances (
FOREIGN KEY (song_id) REFERENCES songs (id) ON DELETE CASCADE);
-- Users table
CREATE TABLE users (
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
id SERIAL,
username VARCHAR(64) UNIQUE NOT NULL,
pwd VARCHAR(80),
role VARCHAR(16) NOT NULL,
PRIMARY KEY (id));
-- Tokens table
CREATE TABLE tokens (
DROP TABLE IF EXISTS tokens;
CREATE TABLE IF NOT EXISTS tokens (
token VARCHAR(64),
user_id INTEGER NOT NULL,
expires TIMESTAMP NOT NULL,

View File

@ -0,0 +1,5 @@
DROP TABLE tokens;
DROP TABLE users;
DROP TABLE performances;
DROP TABLE songs;
DROP TABLE services;

View File

@ -142,26 +142,11 @@ Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
in with the request will be ignored.
<p><h4>Reponse</h4>
The newly-created song record. If a value is given in the
request for the <tt>id</tt> attribute it is ignored. The
attribute for new records is determined by the service and
returned as part of the response.
The newly-created song record.
<p><h4>Example</h4>
<pre>
POST http://newlifesongs.jdbernard.com/api/v1/songs
Content-Length: 60
Content-Type: application/json
{"id":22,"name":"This is How We Praise Him","artists":[""]}
</pre>
<p><pre>
HTTP/1.1 201 Created
Content-Length:
Content-Type: application/json
</pre></li>
</pre></li>
<li><h3><code>GET /songs/&lt;songId&gt;</code></h3>

View File

@ -22,7 +22,7 @@ if (!service) { response.sendError(response.SC_NOT_FOUND); return }
<meta name="referrer" content="origin">
<link rel="shortcut icon" href="../images/favicon.ico">
<title><%= service.localDate.toString("yyyy-MM-dd")
<title><%= service.@date.toString("yyyy-MM-dd")
%> (<%= service.serviceType.displayName %>) - New Life Songs Database</title>
<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!--<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script>-->
@ -38,7 +38,7 @@ if (!service) { response.sendError(response.SC_NOT_FOUND); return }
<body>
<header>
<h1><a href="../">New Life Songs</a></h1>
<h2 class=service-desc><%= service.localDate.toString("yyyy-MM-dd") %>: (<%=
<h2 class=service-desc><%= service.@date.toString("yyyy-MM-dd") %>: (<%=
service.description ?: service.serviceType.displayName %>)</h2>
<nav><ul>
@ -64,7 +64,7 @@ if (!service) { response.sendError(response.SC_NOT_FOUND); return }
<tbody>
<% songsDB.findPerformancesForServiceId(service.id).
collect { [perf: it, song: songsDB.findSong(it.songId)] }.
sort { it.song.name }.sort { it.perf.rank }.each { row -> %>
sort { it.song.name }.each { row -> %>
<tr><td class=actions><a href="<%= NLSongsContext.makeUrl(service, row.song) %>"><i class="fa fa-download"></i></a></td>
<td class=song-name><a href='../song/<%= row.song.id %>'><%=
row.song.name %></a></td>

View File

@ -44,7 +44,7 @@ songsDB = NLSongsContext.songsDB
<tbody>
<% songsDB.findAllServices().sort { it.date }.reverse().each { service -> %>
<tr><td class=date><a href="../service/<%= service.id %>"><%=
service.localDate.toString("yyyy-MM-dd") %></a></td>
service.@date.toString("yyyy-MM-dd") %></a></td>
<td class=service-type><%= service.description ?:
service.serviceType.displayName %></td></tr><% } %>
</tbody>
@ -57,8 +57,7 @@ songsDB = NLSongsContext.songsDB
<script type="application/javascript">
window.onload = function() { \$("#services-table").
dataTable({ "paging": false,
"order": [[0, "desc"]]}); };
dataTable({ "paging": false }); };
</script>
</body>
</html>

View File

@ -67,7 +67,7 @@ if (!song) { response.sendError(response.SC_NOT_FOUND); return }
sort { it.svc.date }.each { row -> %>
<tr><td class=actions><a href='<%= NLSongsContext.makeUrl(row.svc, song) %>'><i class="fa fa-download"></i></a></td>
<td class=performance-date><a href='../service/<%= row.svc.id %>'><%=
row.svc.localDate.toString("yyyy-MM-dd") %></a></td>
row.svc.@date.toString("yyyy-MM-dd") %></a></td>
<td class=service-type><%= row.svc.serviceType.displayName %></td>
<td class=not-small><%= row.perf.leader ?: "" %></td>
<td class=not-small><%= row.perf.pianist ?: "" %></td>

View File

@ -1,5 +1,6 @@
package com.jdbernard.nlsongs.rest
import com.jdbernard.net.HttpContext
import org.junit.Test
import org.junit.AfterClass
import org.junit.BeforeClass

View File

@ -3,7 +3,6 @@ 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
@ -25,9 +24,8 @@ 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
@ -63,23 +61,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],
[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 {
[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 {
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], rank: it[8]) }
guitarist: it[6], leader: it[7]) }
}
@BeforeClass
@ -92,13 +90,8 @@ public class NLSongsDBTest {
HikariDataSource dataSource = new HikariDataSource(hcfg)
// Create NLSongsDB
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)
this.songsDB = new NLSongsDB(dataSource)
this.sql = new Sql(dataSource)
// Set NLSongsContext
NLSongsContext.songsDB = songsDB }
@ -110,18 +103,18 @@ public class NLSongsDBTest {
@Before
public void initData() {
// Create the DB schema
dbmigrate.up()
// 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)
// Populate the DB with test data.
File testDataSql = new File("resources/test/testdb.init.sql")
sql.execute(testDataSql.text) }
@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)
@ -185,8 +178,8 @@ public class NLSongsDBTest {
assertCollectionsEqual(
performances.findAll { it.serviceId != 1 },
songsDB.findAllPerformances()) }
/// ### Songs
@Test public void shoudCreateSong() {
def song = new Song(name: "Test Song", artists: ["Bob Sam"])
def newSong = songsDB.create(song)
@ -254,10 +247,5 @@ 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) }) }
}

View File

@ -1,24 +0,0 @@
apply plugin: 'groovy'
apply plugin: 'application'
mainClassName = 'com.jdbernard.nlsongs.NLSongsUploader'
dependencies {
compile localGroovy()
compile 'ch.qos.logback:logback-classic:1.1.8'
compile 'ch.qos.logback:logback-core:1.1.8'
compile 'org.slf4j:slf4j-api:1.7.22'
compile 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.6'
compile 'com.zaxxer:HikariCP:2.5.1'
compile 'com.miglayout:miglayout-swing:5.0'
compile project(':service')
}
task writeVersionFile(
group: 'build',
description: 'Write the version to VERSION.txt') { doLast {
(new File("${buildDir}/classes/main/VERSION.txt")).text = version
} }
build.dependsOn writeVersionFile

View File

@ -1,37 +0,0 @@
package com.jdbernard.nlsongs
import groovy.beans.Bindable
import groovy.swing.SwingBuilder
import javax.swing.JFrame
import net.miginfocom.swing.MigLayout
public class NLSongsUploader {
public static final String VERSION =
NLSongsUploader.getResourceAsStream('/VERSION.txt').text
// GUI Elements (View)
SwingBuilder swing = new SwingBuilder()
JFrame rootFrame
public static void main(String[] args) { def inst = new NLSongsUploader() }
public NLSongsUploader() {
initGui()
rootFrame.show()
}
private void initGui() {
swing.edtBuilder {
this.rootFrame = frame(title: "New Life Songs Uploader ${VERSION}",
iconImages: [imageIcon('/icon.png').image],
preferredSize: [1024, 768], pack: true,
layout: new MigLayout("ins 0, fill"),
defaultCloseOperation: JFrame.EXIT_ON_CLOSE) {
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

6
version.properties Normal file
View File

@ -0,0 +1,6 @@
#
#Tue May 12 20:51:08 CDT 2015
major=2
version.release=true
minor=3
build=0