diff --git a/src/main/groovy/com/jdbernard/nlsongs/db/.NLSongsDB.groovy.swp b/src/main/groovy/com/jdbernard/nlsongs/db/.NLSongsDB.groovy.swp deleted file mode 100644 index 314c8d3..0000000 Binary files a/src/main/groovy/com/jdbernard/nlsongs/db/.NLSongsDB.groovy.swp and /dev/null differ diff --git a/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy b/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy index c99b805..2198b23 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy @@ -3,9 +3,9 @@ package com.jdbernard.nlsongs.db import java.sql.Connection import java.sql.PreparedStatement import java.sql.ResultSet +import java.text.SimpleDateFormat import com.zaxxer.hikari.HikariDataSource -import groovy.sql.GroovyRowResult import groovy.sql.Sql import groovy.transform.CompileStatic @@ -24,39 +24,216 @@ public class NLSongsDB { public void shutdown() { dataSource.shutdown() } - // ### Services - public Service findService(int id) { - GroovyRowResult row = sql.firstRow( - "SELECT * FROM services WHERE id = ?", [id] as List) - if (row) return (Service) recordToModel(row, Service) - else return null } + /// ### Common + public def save(def model) { + if (model.id > 0) return update(model) + else { + if (create(model) > 0) return model + else return null } } + + + /// ### Services + public Service findService(int id) { + def row = sql.firstRow("SELECT * FROM services WHERE id = ?", [id]) + recordToModel(row, Service) } + + public List findAllServices() { + return sql.rows("SELECT * FROM services"). + collect { recordToModel(it, Service) } } + + public List findServicesForSongId(int songId) { + return sql.rows("SELECT svc.* " + + "FROM services svc JOIN " + + "performances prf ON " + + "svc.id = prf.service_id " + + "WHERE prf.song_id = ?", [songId]). + collect { recordToModel(it, Service) } } + + public List findServicesAfter(Date d) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.rows('SELECT * FROM services WHERE "date" > ?', + [sdf.format(d)]). collect { recordToModel(it, Service) } } + + public List findServicesBefore(Date d) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.rows('SELECT * FROM services WHERE "date" < ?', + [sdf.format(d)]).collect { recordToModel(it, Service) } } + + public List findServicesBetween(Date b, Date e) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.rows('SELECT * FROM services WHERE "date" BETWEEN ? AND ?', + [sdf.format(b),sdf.format(e)]). + collect { recordToModel(it, Service) } } + + public Service create(Service service) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + int newId = sql.executeInsert( + 'INSERT INTO services ("date", service_type) VALUES (?, ?)', + [sdf.format(service.date), service.serviceType])[0][0] + + service.id = newId + return service } + + public int update(Service service) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.executeUpdate( + 'UPDATE services SET "date" = ?, service_type = ? WHERE id = ?', + [sdf.format(service.date), service.serviceType, service.id] ) } + + /// ### Songs + public Song findSong(int id) { + def row = sql.firstRow("SELECT * FROM songs WHERE id = ?", [id]) + return recordToModel(row, Song) } + + public List findAllSongs() { + return sql.rows("SELECT * FROM songs"). + collect { recordToModel(it, Song) } } + + public List findSongsForServiceId(int serviceId) { + return sql.rows("SELECT sng.* " + + "FROM songs sng JOIN " + + "performances prf ON " + + "sng.id = prf.song_id " + + "WHERE prf.service_id = ?", [serviceId]). + collect { recordToModel(it, Song) } } + + public List findSongsByName(String name) { + return sql.rows("SELECT * FROM songs WHERE name = ?", [name]). + collect { recordToModel(it, Song) } } + + public List findSongsLikeName(String name) { + return sql.rows("SELECT * FROM songs WHERE name LIKE ?", ["%$name%"]). + collect { recordToModel(it, Song) } } + + public List findSongsByArtist(String artist) { + return sql.rows("SELECT * FROM songs WHERE artists LIKE ?", ["%$artist%"]). + collect { recordToModel(it, Song) } } + + public List findSongsByNameAndArtist(String name, String artist) { + return sql.rows("SELECT * FROM songs WHERE name = ? AND artists LIKE ?", + [name,"%$artist%"]).collect { recordToModel(it, Song) } } + + public Song create(Song song) { + int newId = sql.executeInsert( + "INSERT INTO songs (name, artists) VALUES (?, ?)", + [song.name, wrapArtists(song.artists)])[0][0] + + song.id = newId + return song } + + public int update(Song song) { + return sql.executeUpdate( + "UPDATE songs SET name = ?, artists = ? WHERE id = ?", + [song.name, wrapArtists(song.artists), song.id] ) } + + /// ### Performances + public Performance findPerformance(int serviceId, int songId) { + def perf = sql.firstRow( + "SELECT * FROM performances WHERE service_id = ? AND song_id = ?", + [serviceId, songId]) + return recordToModel(perf, Performance) } + + public List findPerformancesForServiceId(int serviceId) { + return sql.rows("SELECT * FROM performances WHERE service_id = ?", + [serviceId]).collect { recordToModel(it, Performance) } } + + public List findPerformancesForSongId(int songId) { + return sql.rows("SELECT * FROM performances WHERE song_id = ?", + [songId]).collect { recordToModel(it, Performance) } } + + public Performance create(Performance perf) { + // TODO: handle constraint violation (same service and song ids) + sql.executeInsert( + "INSERT INTO performances (service_id, song_id, pianist, " + + "organist, bassist, drummer, guitarist, leader) VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?)", [perf.serviceId, perf.songId, + perf.pianist, perf.organist, perf.bassist, perf.drummer, + perf.guitarist, perf.leader]) + return perf } + + public int update(Performance perf) { + // 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 = ?", + [perf.pianist, perf.organist, perf.bassist, perf.drummer, + perf.guitarist, perf.leader, perf.serviceId, perf.songId]) } + + public int delete(Performance perf) { + sql.execute( + "DELETE FROM performances WHERE service_id = ? AND song_id = ?") + return sql.getUpdateCount() } + + /// ### API Keys + public ApiKey findKey(String key) { + def row = sql.firstRow("SELECT * FROM api_keys WHERE key = ?", [key]) + return recordToModel(row, ApiKey) } + + public ApiKey save(ApiKey apiKey) { + if (findKey(apiKey.key)) { + update(apiKey) + return apiKey } + else return create(apiKey) } + + public ApiKey create(ApiKey apiKey) { + sql.executeInsert( + "INSERT INTO api_keys (key, description) VALUES (?, ?)", + [apiKey.key, apiKey.description] ) + + return apiKey } + + public int update(ApiKey apiKey) { + return sql.executeUpdate( + "UPDATE api_keys SET description = ? WHERE key = ?", + [apiKey.description, apiKey.key]) } + + /// ### User management + // TODO + + /// ### Utility functions + static def recordToModel(def record, Class clazz) { + if (record == null) return null - // #### Utility functions - static def recordToModel(def row, Class clazz) { def model = clazz.newInstance() - row.each { recordKey, v -> + record.each { recordKey, v -> def pts = recordKey.split('_') def modelKey = pts.length == 1 ? pts[0] : pts[0] + pts[1..-1].collect { it.capitalize() }.join() + + // Hacky, there should be a better way + if (recordKey == "artists") v = unwrapArtists(v); + model[modelKey] = v } return model } static def modelToRecord(def model) { + if (model == null) return null + def record = [:] model.properties.each { modelKey, v -> if (modelKey == "class") return def recordKey = modelKey. replaceAll(/(\p{javaUpperCase})/, /_$1/).toLowerCase() + + // Hack + if (modelKey == "artists") v = wrapArtists(v) + record[recordKey] = v } return record } + public static List unwrapArtists(String artists) { + return artists.split(';') as List } + + public static String wrapArtists(List artists) { + return artists.join(':') } /* static Object recordToModel(GroovyRowResult row, Class clazz) { Object model = clazz.newInstance() - row.each { recordKey, v -> + row.each { recordKey, v -> String[] pts = ((String) recordKey).split('_') String modelKey = pts[0] + pts[1..-1].collect { it.capitalize() }.join() diff --git a/src/main/groovy/com/jdbernard/nlsongs/servlet/.NLSongsContextListener.groovy.swp b/src/main/groovy/com/jdbernard/nlsongs/servlet/.NLSongsContextListener.groovy.swp deleted file mode 100644 index 28b960e..0000000 Binary files a/src/main/groovy/com/jdbernard/nlsongs/servlet/.NLSongsContextListener.groovy.swp and /dev/null differ diff --git a/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy b/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy index 828078a..d260388 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy @@ -12,31 +12,26 @@ import com.zaxxer.hikari.HikariDataSource public final class NLSongsContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { - context = event.servletContext + def context = event.servletContext // Create the pooled data source - HikariConfig hcfg = new HikariConfig() - hcfg.username = 'TODO' - hcfg.password = 'TODO' - hcfg.dataSourceClassName = 'TODO' - hcfg.addDataSourceProperty('cachePrepStmts', 'true') - hcfg.addDataSourceProperty('prepStmtCacheSize', '250') - hcfg.addDataSourceProperty('prepStmtCacheSqlLimit', '2048') - hcfg.addDataSourceProperty('useServerPrepStmts', 'true') + HikariConfig hcfg = new HikariConfig( + context.getInitParameter("context.config.file")) HikariDataSource hds = new HikariDataSource(hcfg) // Create the NLSonsDB instance. NLSongsDB songsDB = new NLSongsDB(hds) - context.setAttribute('songsDB', songsDB) } + context.setAttribute('songsDB', songsDB) + NLSongsContext.songsDB = songsDB } public void contextDestroyed(ServletContextEvent event) { - context = event.servletContext + def context = event.servletContext // Shutdown the Songs DB instance (it will shut down the data source). NLSongsDB songsDB = context.getAttribute('songsDB') - songsDB.shutdown() + if (songsDB) songsDB.shutdown() context.removeAttribute('songsDB') } } diff --git a/src/main/sql/create-db.sql b/src/main/sql/create-db.sql index 0245745..c892650 100644 --- a/src/main/sql/create-db.sql +++ b/src/main/sql/create-db.sql @@ -4,12 +4,13 @@ -- PostgreSQL database creation sript. -- DROP DATABASE IF EXISTS nlsongs; -CREATE DATABASE IF NOT EXISTS nlsongs +CREATE DATABASE nlsongs ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8' CONNECTION LIMIT = 1; +\c nlsongs -- Services table DROP TABLE IF EXISTS services;