Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jooq.version>3.19.10</jooq.version>
<nano.version>2025.1.3</nano.version>
<nano.version>2025.10.2750958</nano.version>
<hikari.version>5.1.0</hikari.version>
<postgres.version>42.7.3</postgres.version>
<slf4j.version>1.7.36</slf4j.version> <!-- For transitive dependencies -->
Expand Down Expand Up @@ -103,6 +103,11 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nanonative</groupId>
<artifactId>devconsole</artifactId>
<version>1.0.0</version>
</dependency>

<!-- CVE remediation for transitive dependencies-->
<dependency>
Expand Down Expand Up @@ -150,6 +155,23 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.github.absketches</groupId>
<artifactId>codegen-concrete-classes-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>nano-service-index</id>
<phase>process-classes</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<verbose>true</verbose>
</configuration>
</execution>
</executions>
</plugin>
<!-- jOOQ codegen; run with: mvn -Pjooq generate-sources -->
<plugin>
<groupId>org.jooq</groupId>
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/ab/sentinel/App.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.ab.sentinel;

import org.ab.sentinel.controller.AppController;
import org.ab.sentinel.service.AppService;
import org.ab.sentinel.service.PostgreSqlService;
import org.ab.sentinel.controller.UserController;
import org.ab.sentinel.service.integrations.GithubIntegrationService;
import org.nanonative.devconsole.service.DevConsoleService;
import org.nanonative.nano.core.Nano;
import org.nanonative.nano.services.http.HttpClient;
import org.nanonative.nano.services.http.HttpServer;
Expand All @@ -23,7 +25,7 @@ public static void main(String[] args) {
CONFIG_LOG_LEVEL, DEBUG,
CONFIG_LOG_FORMATTER, "console",
CONFIG_SERVICE_HTTP_PORT, "8080"
), new HttpServer(), new HttpClient(), new PostgreSqlService(), new GithubIntegrationService());
), new HttpServer(), new HttpClient(), new PostgreSqlService(), new DevConsoleService(), new GithubIntegrationService(), new AppService());

nano.subscribeEvent(EVENT_HTTP_REQUEST, event -> event.payloadOpt()
.filter(HttpObject::isMethodOptions)
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/org/ab/sentinel/DbEvents.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.ab.sentinel;

import org.ab.sentinel.db.api.DeleteByCondition;
import org.ab.sentinel.db.api.FetchMap;
import org.ab.sentinel.db.api.FetchOneByCondition;
import org.ab.sentinel.db.api.InsertAndReturn;
import org.ab.sentinel.db.api.UpdateById;
import org.jooq.Record;
import org.nanonative.nano.helper.event.model.Channel;

import static org.nanonative.nano.helper.event.model.Channel.registerChannelId;

import java.util.Map;

public final class DbEvents {
private DbEvents() {}

public static final Channel<FetchOneByCondition, Record> FETCH_ONE =
registerChannelId("DB_FETCH_ONE", FetchOneByCondition.class, Record.class);

public static final Channel<InsertAndReturn, Record> INSERT_RETURNING =
registerChannelId("DB_INSERT_RETURNING", InsertAndReturn.class, Record.class);

public static final Channel<UpdateById, Record> UPDATE_BY_ID_RETURNING =
registerChannelId("DB_UPDATE_BY_ID_RETURNING", UpdateById.class, Record.class);

public static final Channel<DeleteByCondition, Integer> DELETE_WHERE =
registerChannelId("DB_DELETE_WHERE", DeleteByCondition.class, Integer.class);

public static final Channel<FetchMap, Map> FETCH_MAP =
registerChannelId("DB_FETCH_MAP", FetchMap.class, Map.class);
}
4 changes: 2 additions & 2 deletions src/main/java/org/ab/sentinel/controller/AppController.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static void integrationRequest(final Event<HttpObject, HttpObject> event)

switch (appId) {
case 1 -> handleGithubIntegration(event, body, userId, appId);
default -> event.respond(problem(event, 404, "App not available"));
default -> event.error(new RuntimeException("App not available"));
}
});
}
Expand All @@ -83,6 +83,6 @@ private static void handleGithubIntegration(final Event<HttpObject, HttpObject>
} else {
event.respond(jsonOk(event, Map.of("integration", "success")));
}
}, () -> event.respond(problem(event, 502, "GitHub token validation unavailable")));
}, () -> event.error(new RuntimeException("GitHub token validation unavailable")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public static void registerUser(Event<HttpObject, HttpObject> event) {
final String token = JwtHelper.createToken(Map.of("sub", user.getId(), "email", email), TOKEN_TTL);
event.respond(jsonOk(event, Map.of("token", token)));

}, () -> event.respond(problem(event, 422, "Email already registered")));
}, () -> event.error(new RuntimeException("ADD_USER failed")));
});
}
}
101 changes: 101 additions & 0 deletions src/main/java/org/ab/sentinel/db/JooqDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.ab.sentinel.db;

import org.jooq.DSLContext;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.Condition;
import org.jooq.UpdatableRecord;
import org.jooq.Record;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;

import java.util.Objects;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

public class JooqDao {

private final DSLContext dsl;

public JooqDao(DSLContext dsl) {
this.dsl = Objects.requireNonNull(dsl);
}

/* ===================== READS (no tx required) ===================== */

public <R extends Record> Optional<R> getOne(Table<R> table, Condition condition) {
return Optional.ofNullable(dsl.selectFrom(table).where(condition).fetchOne());
}

public <R extends Record> boolean exists(Table<R> table, Condition condition) {
return dsl.fetchExists(dsl.selectOne().from(table).where(condition));
}

public <R extends Record, K> Map<K, R> fetchMap(Table<R> table, TableField<R, K> keyField) {
return dsl.selectFrom(table).fetchMap(keyField);
}

/* ===================== TRANSACTION ENTRYPOINT ===================== */


public <T> T writeTx(Function<Tx, T> work) throws DataAccessException {
return dsl.transactionResult(cfg -> work.apply(new Tx(DSL.using(cfg))));
}

/* ===================== Tx based sql ===================== */

public <R extends UpdatableRecord<R>> R insertReturning(Table<R> table, Map<TableField<?, ?>, Object> values) {
return writeTx(tx -> tx.insertReturning(table, values));
}

public <R extends UpdatableRecord<R>, ID> Optional<R> updateByIdReturning(
Table<R> table, TableField<R, ID> idField, ID id, Map<TableField<?, ?>, Object> values) {
return writeTx(tx -> tx.updateByIdReturning(table, idField, id, values));
}

public <R extends Record> int deleteWhere(Table<R> table, Condition condition) {
return writeTx(tx -> tx.deleteWhere(table, condition));
}

/* ===================== TX-SCOPED VIEW ===================== */

public static final class Tx {
private final DSLContext ctx;

private Tx(DSLContext ctx) {this.ctx = ctx;}

public <R extends UpdatableRecord<R>> R insertReturning(Table<R> table, Map<TableField<?, ?>, Object> values) {
R rec = ctx.newRecord(table);
setAll(rec, values);
rec.store();
rec.refresh();
return rec;
}

public <R extends UpdatableRecord<R>, ID> Optional<R> updateByIdReturning(
Table<R> table, TableField<R, ID> idField, ID id, Map<TableField<?, ?>, Object> values) {
R rec = ctx.selectFrom(table)
.where(idField.eq(id))
.forUpdate() // lock row to avoid races
.fetchOne();
if (null == rec)
return Optional.empty();
setAll(rec, values);
rec.update();
rec.refresh();
return Optional.of(rec);
}

public <R extends Record> int deleteWhere(Table<R> table, Condition condition) {
return ctx.deleteFrom(table).where(condition).execute();
}

private void setAll(UpdatableRecord<?> rec, Map<TableField<?, ?>, Object> values) {
values.forEach((f, v) -> {
TableField tf = f;
rec.set(tf, v);
});
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/org/ab/sentinel/db/api/DeleteByCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.ab.sentinel.db.api;

import org.jooq.Condition;
import org.jooq.Table;

import java.io.Serializable;

public record DeleteByCondition(Table<?> table, Condition condition) implements Serializable {}
8 changes: 8 additions & 0 deletions src/main/java/org/ab/sentinel/db/api/FetchMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.ab.sentinel.db.api;

import org.jooq.Table;
import org.jooq.TableField;

import java.io.Serializable;

public record FetchMap(Table<?> table, TableField<?, ?> keyField) implements Serializable {}
8 changes: 8 additions & 0 deletions src/main/java/org/ab/sentinel/db/api/FetchOneByCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.ab.sentinel.db.api;

import org.jooq.Condition;
import org.jooq.Table;

import java.io.Serializable;

public record FetchOneByCondition(Table<?> table, Condition condition) implements Serializable {}
9 changes: 9 additions & 0 deletions src/main/java/org/ab/sentinel/db/api/InsertAndReturn.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.ab.sentinel.db.api;

import org.jooq.Table;
import org.jooq.TableField;

import java.io.Serializable;
import java.util.Map;

public record InsertAndReturn(Table<?> table, Map<TableField<?, ?>, Object> values) implements Serializable {}
10 changes: 10 additions & 0 deletions src/main/java/org/ab/sentinel/db/api/UpdateById.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.ab.sentinel.db.api;

import org.jooq.Table;
import org.jooq.TableField;

import java.io.Serializable;
import java.util.Map;

public record UpdateById(Table<?> table, TableField<?, ?> idField, Object id,
Map<TableField<?, ?>, Object> values) implements Serializable {}
Loading