diff --git a/build.gradle b/build.gradle index 4920522..70ad359 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id "java" id "org.springframework.boot" version "1.5.4.RELEASE" + id "org.flywaydb.flyway" version "4.2.0" } repositories { @@ -11,22 +12,34 @@ dependencies { testCompile("org.springframework.boot:spring-boot-starter-test") compile("org.springframework.boot:spring-boot-starter-web") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1") -// compile("com.fasterxml.jackson.core:jackson-databind") -// compile("org.hibernate:hibernate-validator") -// compile("org.springframework:spring-web") -// compile("org.springframework:spring-webmvc") -// compile("org.springframework.boot:spring-boot-starter") -// compile("org.springframework.boot:spring-boot-starter-tomcat") + compile("org.springframework.boot:spring-boot-starter-jdbc") + compile("mysql:mysql-connector-java:8.0.12") } +def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false" + bootRun.environment( [ "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, ] -) \ No newline at end of file +) + +flyway { + url = developmentDbUrl + user = "tracker" + password = "" + locations = ["filesystem:databases/tracker/migrations"] +} + +task testMigrate(type: org.flywaydb.gradle.task.FlywayMigrateTask) { + url = testDbUrl +} \ No newline at end of file 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 0000000..b3f2630 --- /dev/null +++ b/src/main/java/io/pivotal/pal/tracker/JdbcTimeEntryRepository.java @@ -0,0 +1,91 @@ +package io.pivotal.pal.tracker; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +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.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +import static java.sql.Statement.RETURN_GENERATED_KEYS; + +public class JdbcTimeEntryRepository implements TimeEntryRepository { + + private final JdbcTemplate template; + + public JdbcTimeEntryRepository(DataSource dataSource) { + template = new JdbcTemplate(dataSource); + } + + @Override + public TimeEntry create(TimeEntry timeEntry) { + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + template.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; + }, keyHolder); + return find(keyHolder.getKey().longValue()); + } + + @Override + public TimeEntry find(long id) { + String sql = "SELECT id, project_id, user_id, date, hours FROM time_entries WHERE id = ?"; + return template.query(sql, new Object[]{id}, resultExtractor); + } + + private final RowMapper rowMapper = (rs, rowNumber) -> new TimeEntry(rs.getLong("id"), + rs.getLong("project_id"), + rs.getLong("user_id"), + rs.getDate("date").toLocalDate(), + rs.getInt("hours")); + + private final ResultSetExtractor resultExtractor = (rs) -> + rs.next() ? rowMapper.mapRow(rs, 1) : null; + + @Override + public List list() { + return template.query("SELECT * FROM time_entries", rowMapper); + } + + @Override + public TimeEntry update(long id, TimeEntry timeEntry) { + + String sql = "UPDATE time_entries "+ + "SET project_id = ?, user_id = ?, date = ?, hours = ? "+ + "WHERE id = ?"; + + template.update(sql, + timeEntry.getProjectId(), + timeEntry.getUserId(), + Date.valueOf(timeEntry.getDate()), + timeEntry.getHours(), + id); + return find(id); + } + + @Override + public void delete(long id) { + String sql = "DELETE from time_entries "+ + "WHERE id = "+id; + template.execute(sql); + } +} diff --git a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java index 5cd990b..825bb84 100644 --- a/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java +++ b/src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java @@ -9,12 +9,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import javax.sql.DataSource; +import java.util.TimeZone; + @SpringBootApplication public class PalTrackerApplication { @Bean - TimeEntryRepository timeEntryRepository() { - return new InMemoryTimeEntryRepository(); + TimeEntryRepository timeEntryRepository(DataSource dataSource) { + return new JdbcTimeEntryRepository(dataSource); } @Bean @@ -27,6 +30,7 @@ public ObjectMapper jsonObjectMapper() { } public static void main(String[] args) { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); SpringApplication.run(PalTrackerApplication.class, args); } } \ No newline at end of file diff --git a/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java b/src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java index 49b6e94..865c204 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; @@ -30,6 +34,17 @@ public class TimeEntryApiTest { 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 { ResponseEntity createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class);