diff --git a/build.gradle b/build.gradle index 63184e318..8e9d4e244 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,9 @@ + + plugins { id "java" id "org.springframework.boot" version "1.5.4.RELEASE" + id "org.flywaydb.flyway" version "5.1.4" } repositories { @@ -10,12 +13,31 @@ repositories { dependencies { compile("org.springframework.boot:spring-boot-starter-web") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") + compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("mysql:mysql-connector-java:6.0.6") testCompile("org.springframework.boot:spring-boot-starter-test") } +def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" bootRun.environment([ - "WELCOME_MESSAGEE": "ola", + "WELCOME_MESSAGE": "hello", + "SPRING_DATASOURCE_URL": developmentDbUrl, ]) + +def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" test.environment([ "WELCOME_MESSAGE": "Hello from test", + "SPRING_DATASOURCE_URL": testDbUrl, ]) + +flyway { + url = developmentDbUrl + user = "tracker" + password = "" + locations = ["filesystem:databases/tracker/migrations"] +} + +task testMigrate(type: org.flywaydb.gradle.task.FlywayMigrateTask) { + url = testDbUrl + +} diff --git a/manifest.yml b/manifest.yml index ebef391e7..cb7a4a69e 100644 --- a/manifest.yml +++ b/manifest.yml @@ -3,6 +3,6 @@ applications: - name: pal-tracker path: build/libs/pal-tracker.jar routes: - - route: vk-pal-tracker-sleepy-hyena.apps.pcf.paltraining.perficient.com + - route: ph-pal-tracker-wacky-roan.apps.evans.pal.pivotal.io env: WELCOME_MESSAGE: Hello from the review environment \ No newline at end of file diff --git a/out/production/classes/io/pivotal/pal/tracker/EnvController.class b/out/production/classes/io/pivotal/pal/tracker/EnvController.class index 20c1a2965..78ffd3d3e 100644 Binary files a/out/production/classes/io/pivotal/pal/tracker/EnvController.class and b/out/production/classes/io/pivotal/pal/tracker/EnvController.class differ diff --git a/out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class index e761c5082..1e05159f0 100644 Binary files a/out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class and b/out/production/classes/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.class differ diff --git a/out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class new file mode 100644 index 000000000..f10e9c133 Binary files /dev/null and b/out/production/classes/io/pivotal/pal/tracker/JdbcTimeEntryRepository.class differ diff --git a/out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class b/out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class index ffd9d4aa4..d105f1222 100644 Binary files a/out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class and b/out/production/classes/io/pivotal/pal/tracker/PalTrackerApplication.class differ diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class index 57e21d434..4a7389b8a 100644 Binary files a/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class and b/out/production/classes/io/pivotal/pal/tracker/TimeEntry.class differ diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class index 3ddc7cc13..3b50ba488 100644 Binary files a/out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class and b/out/production/classes/io/pivotal/pal/tracker/TimeEntryController.class differ diff --git a/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class b/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class index 7f2d9dfdf..8ae2e8a5f 100644 Binary files a/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class and b/out/production/classes/io/pivotal/pal/tracker/TimeEntryRepository.class differ diff --git a/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class index 17a4fa0f9..2a79d98f7 100644 Binary files a/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class and b/out/test/classes/test/pivotal/pal/tracker/EnvControllerTest.class differ diff --git a/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class b/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class index 8bebdbf5b..b378ee436 100644 Binary files a/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class and b/out/test/classes/test/pivotal/pal/tracker/InMemoryTimeEntryRepositoryTest.class differ diff --git a/out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class b/out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class new file mode 100644 index 000000000..0e63d8362 Binary files /dev/null and b/out/test/classes/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.class differ diff --git a/out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class b/out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class index d31a79fdc..13e701f3a 100644 Binary files a/out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class and b/out/test/classes/test/pivotal/pal/tracker/TimeEntryControllerTest.class differ diff --git a/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class b/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class index ae5671cd0..52b364015 100644 Binary files a/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class and b/out/test/classes/test/pivotal/pal/trackerapi/TimeEntryApiTest.class differ diff --git a/readMe.txt b/readMe.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/io/pivotal/pal/tracker/EnvController.java b/src/main/java/io/pivotal/pal/tracker/EnvController.java index cb3db6f70..f093373b5 100644 --- a/src/main/java/io/pivotal/pal/tracker/EnvController.java +++ b/src/main/java/io/pivotal/pal/tracker/EnvController.java @@ -15,7 +15,6 @@ public class EnvController { public String memLimit=""; public String cfInstanceIndex=""; public String cfInstanceAddress=""; - public EnvController( @Value("${PORT:NOT SET}") String port, @Value("${MEMORY_LIMIT:NOT SET}") String mem_Limit_value, diff --git a/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java new file mode 100644 index 000000000..09906eb5e --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java @@ -0,0 +1,84 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + +import javax.sql.DataSource; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.util.List; + +import static java.sql.Statement.RETURN_GENERATED_KEYS; + +public class JdbcTimeEntryRepository implements TimeEntryRepository { + private final JdbcTemplate jdbcTemplateObj; + public JdbcTimeEntryRepository(DataSource dataSource) { + this.jdbcTemplateObj = new JdbcTemplate(dataSource); + } + + public TimeEntry create(TimeEntry timeEntry) { + KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + + jdbcTemplateObj.update(connection -> { + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO time_entries (project_id, user_id, date, hours) " + + "VALUES (?, ?, ?, ?)", + RETURN_GENERATED_KEYS + ); + + statement.setLong(1, timeEntry.getProjectId()); + statement.setLong(2, timeEntry.getUserId()); + statement.setDate(3, Date.valueOf(timeEntry.getDate())); + statement.setInt(4, timeEntry.getHours()); + + return statement; + }, generatedKeyHolder); + return find(generatedKeyHolder.getKey().longValue()); + } + @Override + public TimeEntry find(Long id) { + return jdbcTemplateObj.query( + "SELECT id, project_id, user_id, date, hours FROM time_entries WHERE id = ?", + new Object[]{id}, + extractor); + } + + @Override + public List list() { + return jdbcTemplateObj.query("SELECT id, project_id, user_id, date, hours FROM time_entries", mapper); + } + + @Override + public TimeEntry update(Long id, TimeEntry timeEntry) { + jdbcTemplateObj.update("UPDATE time_entries " + + "SET project_id = ?, user_id = ?, date = ?, hours = ? " + + "WHERE id = ?", + timeEntry.getProjectId(), + timeEntry.getUserId(), + Date.valueOf(timeEntry.getDate()), + timeEntry.getHours(), + id); + + return find(id); + } + + @Override + public void delete(Long id) { + jdbcTemplateObj.update("DELETE FROM time_entries WHERE id = ?", id); + } + + private final RowMapper mapper = (rs, rowNum) -> new TimeEntry( + rs.getLong("id"), + rs.getLong("project_id"), + rs.getLong("user_id"), + rs.getDate("date").toLocalDate(), + rs.getInt("hours") + ); + + private final ResultSetExtractor extractor = + (rs) -> rs.next() ? mapper.mapRow(rs, 1) : null; +} + diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index 09c99734d..83fa2bec6 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -9,6 +9,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import javax.sql.DataSource; + @SpringBootApplication public class PalTrackerApplication { @@ -21,6 +23,10 @@ TimeEntryRepository timeEntryRepository() { return new InMemoryTimeEntryRepository(); } + @Bean + TimeEntryRepository timeEntryRepository1(DataSource dataSourceObj) { + return new JdbcTimeEntryRepository(dataSourceObj); + } @Bean public ObjectMapper jsonObjectMapper() { ObjectMapper build = Jackson2ObjectMapperBuilder.json() diff --git a/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java new file mode 100644 index 000000000..46730a3db --- /dev/null +++ b/src/test/java/test/pivotal/pal/tracker/JdbcTimeEntryRepositoryTest.java @@ -0,0 +1,160 @@ +package test.pivotal.pal.tracker; + + +import com.mysql.cj.jdbc.MysqlDataSource; +import io.pivotal.pal.tracker.JdbcTimeEntryRepository; +import io.pivotal.pal.tracker.TimeEntry; +import io.pivotal.pal.tracker.TimeEntryRepository; +import org.junit.Before; +import org.junit.Test; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.Date; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JdbcTimeEntryRepositoryTest { + private TimeEntryRepository subject; + private JdbcTemplate jdbcTemplate; + + @Before + public void setUp() throws Exception { + MysqlDataSource dataSource = new MysqlDataSource(); + System.out.println("SPRING_DATASOURCE_URL: "+System.getenv("SPRING_DATASOURCE_URL")); + dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + + subject = new JdbcTimeEntryRepository(dataSource); + + jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("DELETE FROM time_entries"); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @Test + public void createInsertsATimeEntryRecord() throws Exception { + TimeEntry newTimeEntry = new TimeEntry(123, 321, LocalDate.parse("2017-01-09"), 8); + TimeEntry entry = subject.create(newTimeEntry); + + Map foundEntry = jdbcTemplate.queryForMap("Select * from time_entries where id = ?", entry.getId()); + + assertThat(foundEntry.get("id")).isEqualTo(entry.getId()); + assertThat(foundEntry.get("project_id")).isEqualTo(123L); + assertThat(foundEntry.get("user_id")).isEqualTo(321L); + assertThat(((Date)foundEntry.get("date")).toLocalDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(foundEntry.get("hours")).isEqualTo(8); + } + + @Test + public void createReturnsTheCreatedTimeEntry() throws Exception { + TimeEntry newTimeEntry = new TimeEntry(123, 321, LocalDate.parse("2017-01-09"), 8); + TimeEntry entry = subject.create(newTimeEntry); + + assertThat(entry.getId()).isNotNull(); + assertThat(entry.getProjectId()).isEqualTo(123); + assertThat(entry.getUserId()).isEqualTo(321); + assertThat(entry.getDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(entry.getHours()).isEqualTo(8); + } + + @Test + public void findFindsATimeEntry() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (999, 123, 321, '2017-01-09', 8)" + ); + + TimeEntry timeEntry = subject.find(999L); + + assertThat(timeEntry.getId()).isEqualTo(999L); + assertThat(timeEntry.getProjectId()).isEqualTo(123L); + assertThat(timeEntry.getUserId()).isEqualTo(321L); + assertThat(timeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(timeEntry.getHours()).isEqualTo(8); + } + + @Test + public void findReturnsNullWhenNotFound() throws Exception { + TimeEntry timeEntry = subject.find(999L); + + assertThat(timeEntry).isNull(); + } + + @Test + public void listFindsAllTimeEntries() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (999, 123, 321, '2017-01-09', 8), (888, 456, 678, '2017-01-08', 9)" + ); + + List timeEntries = subject.list(); + assertThat(timeEntries.size()).isEqualTo(2); + + TimeEntry timeEntry = timeEntries.get(0); + assertThat(timeEntry.getId()).isEqualTo(888L); + assertThat(timeEntry.getProjectId()).isEqualTo(456L); + assertThat(timeEntry.getUserId()).isEqualTo(678L); + assertThat(timeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-08")); + assertThat(timeEntry.getHours()).isEqualTo(9); + + timeEntry = timeEntries.get(1); + assertThat(timeEntry.getId()).isEqualTo(999L); + assertThat(timeEntry.getProjectId()).isEqualTo(123L); + assertThat(timeEntry.getUserId()).isEqualTo(321L); + assertThat(timeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-09")); + assertThat(timeEntry.getHours()).isEqualTo(8); + } + + @Test + public void updateReturnsTheUpdatedRecord() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (1000, 123, 321, '2017-01-09', 8)"); + + TimeEntry timeEntryUpdates = new TimeEntry(456, 987, LocalDate.parse("2017-01-10"), 10); + + TimeEntry updatedTimeEntry = subject.update(1000L, timeEntryUpdates); + + assertThat(updatedTimeEntry.getId()).isEqualTo(1000L); + assertThat(updatedTimeEntry.getProjectId()).isEqualTo(456L); + assertThat(updatedTimeEntry.getUserId()).isEqualTo(987L); + assertThat(updatedTimeEntry.getDate()).isEqualTo(LocalDate.parse("2017-01-10")); + assertThat(updatedTimeEntry.getHours()).isEqualTo(10); + } + + @Test + public void updateUpdatesTheRecord() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (1000, 123, 321, '2017-01-09', 8)"); + + TimeEntry updatedTimeEntry = new TimeEntry(456, 322, LocalDate.parse("2017-01-10"), 10); + + TimeEntry timeEntry = subject.update(1000L, updatedTimeEntry); + + Map foundEntry = jdbcTemplate.queryForMap("Select * from time_entries where id = ?", timeEntry.getId()); + + assertThat(foundEntry.get("id")).isEqualTo(timeEntry.getId()); + assertThat(foundEntry.get("project_id")).isEqualTo(456L); + assertThat(foundEntry.get("user_id")).isEqualTo(322L); + assertThat(((Date)foundEntry.get("date")).toLocalDate()).isEqualTo(LocalDate.parse("2017-01-10")); + assertThat(foundEntry.get("hours")).isEqualTo(10); + } + + @Test + public void deleteRemovesTheRecord() throws Exception { + jdbcTemplate.execute( + "INSERT INTO time_entries (id, project_id, user_id, date, hours) " + + "VALUES (999, 123, 321, '2017-01-09', 8)" + ); + + subject.delete(999L); + + Map foundEntry = jdbcTemplate.queryForMap("Select count(*) count from time_entries where id = ?", 999); + assertThat(foundEntry.get("count")).isEqualTo(0L); + } +} diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 8686b5c2b..2ca145ae9 100644 --- a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java +++ b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java @@ -1,8 +1,10 @@ package test.pivotal.pal.trackerapi; import com.jayway.jsonpath.DocumentContext; +import com.mysql.cj.jdbc.MysqlDataSource; import io.pivotal.pal.tracker.PalTrackerApplication; import io.pivotal.pal.tracker.TimeEntry; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -12,10 +14,12 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit4.SpringRunner; import java.time.LocalDate; import java.util.Collection; +import java.util.TimeZone; import static com.jayway.jsonpath.JsonPath.parse; import static org.assertj.core.api.Assertions.assertThat; @@ -29,6 +33,16 @@ public class TimeEntryApiTest { private TestRestTemplate restTemplate; private TimeEntry timeEntry = new TimeEntry(123L, 456L, LocalDate.parse("2017-01-08"), 8); + @Before + public void setUp() throws Exception { + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL")); + + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("TRUNCATE time_entries"); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } @Test public void testCreate() throws Exception {