From 8d987ee50e50cf9e176b4061fa0da2b97bde867d Mon Sep 17 00:00:00 2001 From: Maxime Duchene-Savard Date: Mon, 29 Sep 2025 09:00:37 -0400 Subject: [PATCH] travaux --- .idea/kotlinc.xml | 7 -- pom.xml | 63 +++--------- src/main/java/dev/mduchene/App.java | 93 +----------------- src/main/java/dev/mduchene/Db.java | 21 +--- .../java/dev/mduchene/JsonBodyHandler.java | 37 +++++++ src/main/java/dev/mduchene/Migration.java | 27 ++++- src/main/java/dev/mduchene/Server.java | 98 +++++++++++++++++++ .../java/dev/mduchene/BasicHttpCallTest.java | 12 +++ .../dev/mduchene/BoltsIntegrationTest.java | 43 ++++++++ src/test/java/dev/mduchene/MigrationTest.java | 91 +---------------- src/test/java/dev/mduchene/TestUtil.java | 22 +++++ 11 files changed, 258 insertions(+), 256 deletions(-) create mode 100644 src/main/java/dev/mduchene/JsonBodyHandler.java create mode 100644 src/main/java/dev/mduchene/Server.java create mode 100644 src/test/java/dev/mduchene/BasicHttpCallTest.java create mode 100644 src/test/java/dev/mduchene/BoltsIntegrationTest.java create mode 100644 src/test/java/dev/mduchene/TestUtil.java diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index e8009fd..ee74011 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -6,11 +6,4 @@ - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4e1fc1a..4c42c46 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,6 @@ UTF-8 - 2.2.0 @@ -25,42 +24,6 @@ 21 - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - compile - - - - src/main/java - target/generated-sources/annotations - - - - - test-compile - test-compile - - test-compile - - - - src/test/java - target/generated-test-sources/test-annotations - - - - - - 1.8 - - org.apache.maven.plugins maven-compiler-plugin @@ -121,11 +84,18 @@ 1.8.1 - + - org.mariadb.jdbc - mariadb-java-client - 3.5.4 + org.postgresql + postgresql + 42.7.8 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.20.0 @@ -165,16 +135,5 @@ 2.3.230 test - - org.jetbrains.kotlin - kotlin-stdlib-jdk8 - ${kotlin.version} - - - org.jetbrains.kotlin - kotlin-test - ${kotlin.version} - test - diff --git a/src/main/java/dev/mduchene/App.java b/src/main/java/dev/mduchene/App.java index 1cfc5f1..d3748d7 100644 --- a/src/main/java/dev/mduchene/App.java +++ b/src/main/java/dev/mduchene/App.java @@ -1,103 +1,20 @@ package dev.mduchene; -import io.javalin.Javalin; -import org.apache.commons.dbutils.DbUtils; -import org.apache.commons.dbutils.QueryRunner; -import org.apache.commons.dbutils.handlers.ScalarHandler; -import org.apache.commons.io.FileUtils; -import org.jooq.impl.DSL; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.SQLException; -import java.time.LocalDateTime; -import java.util.List; - -/** Hello world! */ public class App { public static void main(String[] args) { System.setProperty("org.jooq.no-tips", "true"); System.setProperty("org.jooq.no-logo", "true"); Db db = - Db.Builder.create() - .url("jdbc:mariadb://localhost:3306/bolts") + Db.builder() + .url("jdbc:postgresql://127.0.0.1:5432/bolts") .user("root") .password("root") .build(); - db.init(); - List pendingBaseMigrations = Migration.of(db).getPendingBaseMigrations(); - for (File file : pendingBaseMigrations) { - LoggerFactory.getLogger(App.class).info("Running migration: {}", file.getName()); - try (Connection connection = db.getConnection()) { - String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8); - db.run(connection, content); - DSL.using(connection) - .insertInto(DSL.table("_migrations")) - .columns(DSL.field("name"), DSL.field("base"), DSL.field("executedAt")) - .values( - file.getName(), true, LocalDateTime.now()) // executedAt is null for base migrations - .execute(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + Migration.of(db).runBaseMigrations(); - var app = Javalin.create(cnf -> {}); - app.before( - ctx -> { - Connection connection = db.getConnection(); - ctx.attribute("connection", connection); - }); - app.after( - ctx -> { - Connection connection = ctx.attribute("connection"); - if (connection != null) { - DbUtils.commitAndCloseQuietly(connection); - } - }); - - app.exception( - Exception.class, - (e, ctx) -> { - LoggerFactory.getLogger(App.class).error("error", e); - - Connection connection = ctx.attribute("connection"); - if (connection != null) { - DbUtils.rollbackAndCloseQuietly(connection); - } - ctx.status(500); - }); - - app.get( - "/", - ctx -> { - var r = new QueryRunner(); - Integer query = - r.query( - (Connection) ctx.attribute("connection"), - "SELECT 1", - resultSet -> { - System.out.println("hello world"); - if (resultSet.next()) return resultSet.getInt(1); - - return null; - }); - - ctx.result(query.toString()); - }); - - app.get( - "/error", - ctx -> { - throw new RuntimeException("error"); - }); - - app.start(3001); + Server server = Server.builder().db(db).build(); + server.start(); } } diff --git a/src/main/java/dev/mduchene/Db.java b/src/main/java/dev/mduchene/Db.java index 4497bff..70ccbd8 100644 --- a/src/main/java/dev/mduchene/Db.java +++ b/src/main/java/dev/mduchene/Db.java @@ -2,19 +2,12 @@ package dev.mduchene; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import org.jooq.DSLContext; -import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; -import org.slf4j.LoggerFactory; - import java.sql.Connection; import java.sql.SQLException; -import java.util.List; - import org.jooq.SQLDialect; import org.jooq.conf.RenderQuotedNames; import org.jooq.conf.Settings; -import org.jooq.SQLDialect; +import org.jooq.impl.DSL; public class Db { private HikariDataSource dataSource; @@ -22,12 +15,9 @@ public class Db { private Db() {} - public void init() { - try (Connection connection = dataSource.getConnection()) { - connection.setAutoCommit(true); - } catch (Exception e) { - // Ignore if the table already exists - } + + public static Builder builder() { + return new Builder(); } public static class Builder { @@ -37,9 +27,6 @@ public class Db { private Builder() {} - public static Builder create() { - return new Builder(); - } public Builder url(String url) { this.url = url; diff --git a/src/main/java/dev/mduchene/JsonBodyHandler.java b/src/main/java/dev/mduchene/JsonBodyHandler.java new file mode 100644 index 0000000..5b9cacf --- /dev/null +++ b/src/main/java/dev/mduchene/JsonBodyHandler.java @@ -0,0 +1,37 @@ +package dev.mduchene; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.http.HttpResponse; +import java.util.function.Supplier; + +public class JsonBodyHandler implements HttpResponse.BodyHandler { + private static JsonBodyHandler instance; + + public static JsonBodyHandler getInstance() { + if (instance == null) { + instance = new JsonBodyHandler(); + } + return instance; + } + + private ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public HttpResponse.BodySubscriber apply(HttpResponse.ResponseInfo responseInfo) { + HttpResponse.BodySubscriber upstream = + HttpResponse.BodySubscribers.ofInputStream(); + return HttpResponse.BodySubscribers.mapping(upstream, this::toNode); + } + + public JsonNode toNode(InputStream inputStream) { + try (InputStream stream = inputStream) { + return objectMapper.readTree(stream); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/dev/mduchene/Migration.java b/src/main/java/dev/mduchene/Migration.java index e728891..871eb5b 100644 --- a/src/main/java/dev/mduchene/Migration.java +++ b/src/main/java/dev/mduchene/Migration.java @@ -1,14 +1,16 @@ package dev.mduchene; import java.io.File; +import java.nio.charset.StandardCharsets; import java.sql.Connection; -import java.sql.ResultSet; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; import org.jooq.Meta; -import org.jooq.Record1; -import org.jooq.SelectConditionStep; import org.jooq.impl.DSL; +import org.slf4j.LoggerFactory; public class Migration { @@ -30,6 +32,25 @@ public class Migration { .collect(Collectors.toList()); } + public void runBaseMigrations() { + List pendingBaseMigrations = getPendingBaseMigrations(); + for (File file : pendingBaseMigrations) { + LoggerFactory.getLogger(Migration.class).info("Running migration: {}", file.getName()); + try (Connection connection = db.getConnection()) { + String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + db.run(connection, content); + DSL.using(connection) + .insertInto(DSL.table("_migrations")) + .columns(DSL.field("name"), DSL.field("base"), DSL.field("executedAt")) + .values( + file.getName(), true, LocalDateTime.now()) // executedAt is null for base migrations + .execute(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + public boolean tableExists(String tableName) { // Get the Meta object, which contains all database metadata try { diff --git a/src/main/java/dev/mduchene/Server.java b/src/main/java/dev/mduchene/Server.java new file mode 100644 index 0000000..d75a1f2 --- /dev/null +++ b/src/main/java/dev/mduchene/Server.java @@ -0,0 +1,98 @@ +package dev.mduchene; + +import io.javalin.Javalin; +import org.apache.commons.dbutils.DbUtils; +import org.apache.commons.dbutils.QueryRunner; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; + +public class Server { + private Javalin app; + private Db db; + + public static Builder builder() { + return new Builder(); + } + + public Server start() { + app = Javalin.create(cnf -> {}); + app.before( + ctx -> { + Connection connection = db.getConnection(); + ctx.attribute("connection", connection); + }); + app.after( + ctx -> { + Connection connection = ctx.attribute("connection"); + if (connection != null) { + DbUtils.commitAndCloseQuietly(connection); + } + }); + + app.exception( + Exception.class, + (e, ctx) -> { + LoggerFactory.getLogger(App.class).error("error", e); + + Connection connection = ctx.attribute("connection"); + if (connection != null) { + DbUtils.rollbackAndCloseQuietly(connection); + } + ctx.status(500); + }); + + app.get( + "/", + ctx -> { + var r = new QueryRunner(); + Integer query = + r.query( + (Connection) ctx.attribute("connection"), + "SELECT 1", + resultSet -> { + System.out.println("hello world"); + if (resultSet.next()) return resultSet.getInt(1); + + return null; + }); + + ctx.result(query.toString()); + }); + + app.get( + "/error", + ctx -> { + throw new RuntimeException("error"); + }); + + app.start(3001); + return this; + } + + public Server stop() { + if (app != null) { + app.stop(); + } + return this; + } + + + + public static class Builder { + private Db db; + + private Builder() {} + + public Builder db(Db db) { + this.db = db; + return this; + } + + public Server build() { + Server server = new Server(); + server.db = this.db; + return server; + } + } +} diff --git a/src/test/java/dev/mduchene/BasicHttpCallTest.java b/src/test/java/dev/mduchene/BasicHttpCallTest.java new file mode 100644 index 0000000..99ac0b7 --- /dev/null +++ b/src/test/java/dev/mduchene/BasicHttpCallTest.java @@ -0,0 +1,12 @@ +package dev.mduchene; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(BoltsIntegrationTest.class) +public class BasicHttpCallTest { + @Test + void test() { +get("http://localhost:7000/health"); + } +} diff --git a/src/test/java/dev/mduchene/BoltsIntegrationTest.java b/src/test/java/dev/mduchene/BoltsIntegrationTest.java new file mode 100644 index 0000000..4cb742a --- /dev/null +++ b/src/test/java/dev/mduchene/BoltsIntegrationTest.java @@ -0,0 +1,43 @@ +package dev.mduchene; + +import org.junit.jupiter.api.extension.*; + +import java.sql.Connection; + +public class BoltsIntegrationTest + implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback { + + private Server server; + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + Db db = + Db.builder() + .url("jdbc:postgresql://127.0.0.1:5432/bolts_test") + .user("root") + .password("root") + .build(); + + try (Connection connection = db.getConnection()) { + connection.createStatement().execute("DROP SCHEMA IF EXISTS public CASCADE;"); + connection.createStatement().execute("CREATE SCHEMA public;"); + } + + Migration.of(db).runBaseMigrations(); + + server = Server.builder().db(db).build().start(); + } + + @Override + public void afterAll(ExtensionContext context) throws Exception { + if (server != null) { + server.stop(); + } + } + + @Override + public void afterEach(ExtensionContext context) throws Exception {} + + @Override + public void beforeEach(ExtensionContext context) throws Exception {} +} diff --git a/src/test/java/dev/mduchene/MigrationTest.java b/src/test/java/dev/mduchene/MigrationTest.java index 229e0ef..41a95fc 100644 --- a/src/test/java/dev/mduchene/MigrationTest.java +++ b/src/test/java/dev/mduchene/MigrationTest.java @@ -1,96 +1,9 @@ package dev.mduchene; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(BoltsIntegrationTest.class) public class MigrationTest { -// private Db db; -// private Migration migration; -// -// @BeforeEach -// public void setUp() { -// db = Db.Builder.create() -// .url("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL") -// .user("sa") -// .password("") -// .build(); -// db.init(); -// migration = Migration.of(db); -// } -// -// @AfterEach -// public void tearDown() throws IOException { -// // Clean up the created migration files -// File migrationsDir = new File("src/main/resources/migrations"); -// for (File file : Objects.requireNonNull(migrationsDir.listFiles())) { -// if (!file.getName().equals(".gitkeep")) { -// Files.delete(file.toPath()); -// } -// } -// db.run("DROP ALL OBJECTS"); -// } -// -// -// -// @Test -// public void testCreateMigration() { -// migration.create("test_migration"); -// File migrationsDir = new File("src/main/resources/migrations"); -// assertEquals(1, Objects.requireNonNull(migrationsDir.listFiles()).length); -// assertTrue(Objects.requireNonNull(migrationsDir.listFiles())[0].getName().contains("_test_migration.sql")); -// } -// -// @Test -// public void testGetPendingMigrations() throws IOException { -// // Create a dummy migration file -// Path newFile = Paths.get("src/main/resources/migrations/20230101000000_initial.sql"); -// Files.createFile(newFile); -// -// List pending = migration.getPending(); -// assertEquals(1, pending.size()); -// assertEquals("20230101000000_initial.sql", pending.get(0)); -// -// // Run the migration -// db.run("INSERT INTO _migrations (name) VALUES ('20230101000000_initial.sql')"); -// -// pending = migration.getPending(); -// assertEquals(0, pending.size()); -// } -// -// @Test -// public void testRunMigrations() throws IOException { -// // Create a dummy migration file with some SQL -// String migrationName = "20230101000001_create_users_table.sql"; -// Path newFile = Paths.get("src/main/resources/migrations/" + migrationName); -// Files.write(newFile, "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));".getBytes()); -// -// // Run pending migrations -// List pending = migration.getPending(); -// migration.run(pending); -// -// // Check if the table was created -// try { -// db.run("SELECT * FROM users"); -// } catch (Exception e) { -// fail("Table 'users' should have been created by the migration."); -// } -// -// // Check if the migration was recorded in the _migrations table -//// List ranMigrations = db.getRanMigrations(); -//// assertEquals(1, ranMigrations.size()); -//// assertEquals(migrationName, ranMigrations.get(0)); -// } } diff --git a/src/test/java/dev/mduchene/TestUtil.java b/src/test/java/dev/mduchene/TestUtil.java new file mode 100644 index 0000000..b596742 --- /dev/null +++ b/src/test/java/dev/mduchene/TestUtil.java @@ -0,0 +1,22 @@ +package dev.mduchene; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class TestUtil { + public static HttpResponse get(String url) { + UriBuilder. + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build(); + + try (var client = HttpClient.newHttpClient()) { + return client.send(request, JsonBodyHandler.getInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +}