diff --git a/pom.xml b/pom.xml
index ffc9700..b983f70 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
21
UTF-8
3.19.10
- 2025.1.3
+ 2025.10.2750958
5.1.0
42.7.3
1.7.36
@@ -103,6 +103,11 @@
postgresql
test
+
+ org.nanonative
+ devconsole
+ 1.0.0
+
@@ -150,6 +155,23 @@
+
+ io.github.absketches
+ codegen-concrete-classes-maven-plugin
+ 1.0.0
+
+
+ nano-service-index
+ process-classes
+
+ generate
+
+
+ true
+
+
+
+
org.jooq
diff --git a/src/main/java/org/ab/sentinel/App.java b/src/main/java/org/ab/sentinel/App.java
index b8d4614..02297a2 100644
--- a/src/main/java/org/ab/sentinel/App.java
+++ b/src/main/java/org/ab/sentinel/App.java
@@ -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;
@@ -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)
diff --git a/src/main/java/org/ab/sentinel/DbEvents.java b/src/main/java/org/ab/sentinel/DbEvents.java
new file mode 100644
index 0000000..5aeb183
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/DbEvents.java
@@ -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 FETCH_ONE =
+ registerChannelId("DB_FETCH_ONE", FetchOneByCondition.class, Record.class);
+
+ public static final Channel INSERT_RETURNING =
+ registerChannelId("DB_INSERT_RETURNING", InsertAndReturn.class, Record.class);
+
+ public static final Channel UPDATE_BY_ID_RETURNING =
+ registerChannelId("DB_UPDATE_BY_ID_RETURNING", UpdateById.class, Record.class);
+
+ public static final Channel DELETE_WHERE =
+ registerChannelId("DB_DELETE_WHERE", DeleteByCondition.class, Integer.class);
+
+ public static final Channel FETCH_MAP =
+ registerChannelId("DB_FETCH_MAP", FetchMap.class, Map.class);
+}
diff --git a/src/main/java/org/ab/sentinel/controller/AppController.java b/src/main/java/org/ab/sentinel/controller/AppController.java
index 5abee01..99f149c 100644
--- a/src/main/java/org/ab/sentinel/controller/AppController.java
+++ b/src/main/java/org/ab/sentinel/controller/AppController.java
@@ -56,7 +56,7 @@ public static void integrationRequest(final Event 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"));
}
});
}
@@ -83,6 +83,6 @@ private static void handleGithubIntegration(final Event
} 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")));
}
}
diff --git a/src/main/java/org/ab/sentinel/controller/UserController.java b/src/main/java/org/ab/sentinel/controller/UserController.java
index f213c5f..97017ff 100644
--- a/src/main/java/org/ab/sentinel/controller/UserController.java
+++ b/src/main/java/org/ab/sentinel/controller/UserController.java
@@ -66,7 +66,7 @@ public static void registerUser(Event 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")));
});
}
}
diff --git a/src/main/java/org/ab/sentinel/db/JooqDao.java b/src/main/java/org/ab/sentinel/db/JooqDao.java
new file mode 100644
index 0000000..3197555
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/db/JooqDao.java
@@ -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 Optional getOne(Table table, Condition condition) {
+ return Optional.ofNullable(dsl.selectFrom(table).where(condition).fetchOne());
+ }
+
+ public boolean exists(Table table, Condition condition) {
+ return dsl.fetchExists(dsl.selectOne().from(table).where(condition));
+ }
+
+ public Map fetchMap(Table table, TableField keyField) {
+ return dsl.selectFrom(table).fetchMap(keyField);
+ }
+
+ /* ===================== TRANSACTION ENTRYPOINT ===================== */
+
+
+ public T writeTx(Function work) throws DataAccessException {
+ return dsl.transactionResult(cfg -> work.apply(new Tx(DSL.using(cfg))));
+ }
+
+ /* ===================== Tx based sql ===================== */
+
+ public > R insertReturning(Table table, Map, Object> values) {
+ return writeTx(tx -> tx.insertReturning(table, values));
+ }
+
+ public , ID> Optional updateByIdReturning(
+ Table table, TableField idField, ID id, Map, Object> values) {
+ return writeTx(tx -> tx.updateByIdReturning(table, idField, id, values));
+ }
+
+ public int deleteWhere(Table 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 insertReturning(Table table, Map, Object> values) {
+ R rec = ctx.newRecord(table);
+ setAll(rec, values);
+ rec.store();
+ rec.refresh();
+ return rec;
+ }
+
+ public , ID> Optional updateByIdReturning(
+ Table table, TableField idField, ID id, Map, 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 int deleteWhere(Table table, Condition condition) {
+ return ctx.deleteFrom(table).where(condition).execute();
+ }
+
+ private void setAll(UpdatableRecord> rec, Map, Object> values) {
+ values.forEach((f, v) -> {
+ TableField tf = f;
+ rec.set(tf, v);
+ });
+ }
+ }
+}
diff --git a/src/main/java/org/ab/sentinel/db/api/DeleteByCondition.java b/src/main/java/org/ab/sentinel/db/api/DeleteByCondition.java
new file mode 100644
index 0000000..23ad504
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/db/api/DeleteByCondition.java
@@ -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 {}
diff --git a/src/main/java/org/ab/sentinel/db/api/FetchMap.java b/src/main/java/org/ab/sentinel/db/api/FetchMap.java
new file mode 100644
index 0000000..ca25348
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/db/api/FetchMap.java
@@ -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 {}
diff --git a/src/main/java/org/ab/sentinel/db/api/FetchOneByCondition.java b/src/main/java/org/ab/sentinel/db/api/FetchOneByCondition.java
new file mode 100644
index 0000000..db50e05
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/db/api/FetchOneByCondition.java
@@ -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 {}
diff --git a/src/main/java/org/ab/sentinel/db/api/InsertAndReturn.java b/src/main/java/org/ab/sentinel/db/api/InsertAndReturn.java
new file mode 100644
index 0000000..b564cef
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/db/api/InsertAndReturn.java
@@ -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, Object> values) implements Serializable {}
diff --git a/src/main/java/org/ab/sentinel/db/api/UpdateById.java b/src/main/java/org/ab/sentinel/db/api/UpdateById.java
new file mode 100644
index 0000000..cc229f6
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/db/api/UpdateById.java
@@ -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, Object> values) implements Serializable {}
diff --git a/src/main/java/org/ab/sentinel/service/AppService.java b/src/main/java/org/ab/sentinel/service/AppService.java
new file mode 100644
index 0000000..17cd01e
--- /dev/null
+++ b/src/main/java/org/ab/sentinel/service/AppService.java
@@ -0,0 +1,137 @@
+package org.ab.sentinel.service;
+
+import berlin.yuna.typemap.model.TypeMapI;
+import org.ab.sentinel.AppEvents;
+import org.ab.sentinel.dto.AppDto;
+import org.ab.sentinel.dto.UserDto;
+import org.ab.sentinel.dto.integrations.AppIntegrationRequestDto;
+
+import static org.ab.sentinel.jooq.Tables.APPS;
+import static org.ab.sentinel.jooq.tables.Users.USERS;
+import static org.ab.sentinel.jooq.Tables.INTEGRATIONS;
+
+import org.ab.sentinel.jooq.tables.records.AppsRecord;
+import org.ab.sentinel.jooq.tables.records.UsersRecord;
+import org.ab.sentinel.jooq.tables.records.IntegrationsRecord;
+
+import org.ab.sentinel.DbEvents;
+import org.ab.sentinel.db.api.FetchMap;
+import org.ab.sentinel.db.api.FetchOneByCondition;
+import org.ab.sentinel.db.api.InsertAndReturn;
+
+import org.jooq.TableField;
+import org.nanonative.nano.core.model.Service;
+import org.nanonative.nano.helper.event.model.Event;
+
+import java.nio.charset.StandardCharsets;
+import java.time.ZoneOffset;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class AppService extends Service {
+
+ @Override
+ public void start() {}
+
+ @Override
+ public void stop() {}
+
+ @Override
+ public Object onFailure(Event event) {return null;}
+
+ @Override
+ public void onEvent(final Event, ?> event) {
+
+ // =================== FETCH_APPS ===================
+ event.channel(AppEvents.FETCH_APPS).ifPresent(ev -> {
+ ev.context().newEvent(DbEvents.FETCH_MAP, () -> new FetchMap(APPS, APPS.NAME)).send().responseOpt()
+ .ifPresentOrElse(obj -> {
+ @SuppressWarnings("unchecked")
+ Map appMap = (Map) obj;
+
+ Map out = appMap.entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ e -> toDto(e.getValue()),
+ (a, b) -> a,
+ LinkedHashMap::new
+ ));
+ ev.respond(out);
+ }, () -> ev.error(new RuntimeException("No Apps found")));
+ });
+
+ // =================== ADD_USER ===================
+ event.channel(AppEvents.ADD_USER).ifPresent(ev -> {
+ UserDto user = ev.payload();
+
+ boolean exists = ev.context()
+ .newEvent(DbEvents.FETCH_ONE, () -> new FetchOneByCondition(USERS, USERS.EMAIL.eq(user.email())))
+ .send()
+ .responseOpt()
+ .isPresent();
+
+ if (exists) {
+ ev.error(new RuntimeException("Email already registered"));
+ return;
+ }
+
+ var values = new LinkedHashMap, Object>();
+ values.put(USERS.EMAIL, user.email());
+ values.put(USERS.NAME, user.name());
+ values.put(USERS.PASSWORD_HASH, user.passwordHash());
+
+ ev.context()
+ .newEvent(DbEvents.INSERT_RETURNING, () -> new InsertAndReturn(USERS, values))
+ .send()
+ .responseOpt()
+ .ifPresentOrElse(obj -> {
+ UsersRecord ur = (UsersRecord) obj;
+ ev.respond(ur);
+ }, () -> ev.error(new RuntimeException("User insert failed")));
+ });
+
+ // =================== FETCH_USER ===================
+ event.channel(AppEvents.FETCH_USER).ifPresent(ev -> {
+ String email = ev.payload();
+
+ ev.context()
+ .newEvent(DbEvents.FETCH_ONE, () -> new FetchOneByCondition(USERS, USERS.EMAIL.eq(email)))
+ .send()
+ .responseOpt()
+ .ifPresentOrElse(obj -> {
+ UsersRecord user = (UsersRecord) obj;
+ ev.respond(user);
+ }, () -> ev.respond(null));
+ });
+
+ // =================== APP_INT_REQ ===================
+ event.channel(AppEvents.APP_INT_REQ).ifPresent(ev -> {
+ AppIntegrationRequestDto req = ev.payload();
+
+ var values = new LinkedHashMap, Object>();
+ values.put(INTEGRATIONS.APP_ID, req.appId());
+ values.put(INTEGRATIONS.USER_ID, req.userId());
+ values.put(INTEGRATIONS.SCOPES, req.scopes().split(","));
+ values.put(INTEGRATIONS.ACCESS_TOKEN_ENC, req.accessToken().getBytes(StandardCharsets.UTF_8));
+ values.put(INTEGRATIONS.EXPIRES_AT, req.expiresAt().atOffset(ZoneOffset.UTC));
+
+ ev.context()
+ .newEvent(DbEvents.INSERT_RETURNING, () -> new InsertAndReturn(INTEGRATIONS, values))
+ .send()
+ .responseOpt()
+ .ifPresentOrElse(obj -> {
+ IntegrationsRecord rec = (IntegrationsRecord) obj;
+ ev.respond(rec);
+ }, () -> ev.error(new RuntimeException("Integration insert failed")));
+ });
+ }
+
+ @Override
+ public void configure(final TypeMapI> changes, final TypeMapI> merged) {}
+
+ private AppDto toDto(AppsRecord r) {
+ var meta = (r.getMetadata() == null) ? null : r.getMetadata().data();
+ return new AppDto(r.getId(), r.getName(), r.getLogoUrl(), meta);
+ }
+}
diff --git a/src/main/java/org/ab/sentinel/service/PostgreSqlService.java b/src/main/java/org/ab/sentinel/service/PostgreSqlService.java
index 69ab7fe..7863fc3 100644
--- a/src/main/java/org/ab/sentinel/service/PostgreSqlService.java
+++ b/src/main/java/org/ab/sentinel/service/PostgreSqlService.java
@@ -1,52 +1,48 @@
package org.ab.sentinel.service;
import berlin.yuna.typemap.model.TypeMapI;
-import org.ab.sentinel.AppEvents;
-
-import static org.ab.sentinel.jooq.Tables.APPS;
-import static org.ab.sentinel.jooq.Tables.INTEGRATIONS;
-import static org.ab.sentinel.jooq.tables.Users.USERS;
-import static org.nanonative.nano.helper.config.ConfigRegister.registerConfig;
-
import org.ab.sentinel.db.DataSourceConfig;
import org.ab.sentinel.db.DataSourceFactory;
+import org.ab.sentinel.db.JooqDao;
import org.ab.sentinel.db.JooqFactory;
-import org.ab.sentinel.dto.AppDto;
-import org.ab.sentinel.dto.UserDto;
-import org.ab.sentinel.dto.integrations.AppIntegrationRequestDto;
-import org.ab.sentinel.jooq.tables.records.AppsRecord;
-import org.ab.sentinel.jooq.tables.records.IntegrationsRecord;
-import org.ab.sentinel.jooq.tables.records.UsersRecord;
+import org.ab.sentinel.DbEvents;
+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.DSLContext;
-import org.jooq.impl.DSL;
+import org.jooq.TableField;
+import org.jooq.Table;
+import org.jooq.Record;
import org.nanonative.nano.core.model.Service;
import org.nanonative.nano.helper.event.model.Event;
import javax.sql.DataSource;
-import java.nio.charset.StandardCharsets;
-import java.time.ZoneOffset;
-import java.util.Map;
-import java.util.LinkedHashMap;
-import java.util.stream.Collectors;
+import static org.nanonative.nano.helper.config.ConfigRegister.registerConfig;
public final class PostgreSqlService extends Service {
+ // Config keys
public static final String CONFIG_DB_USER = registerConfig("pg_db_user", "Database user");
public static final String CONFIG_DB_PASS = registerConfig("pg_db_pass", "Database password");
public static final String CONFIG_DB_NAME = registerConfig("pg_db_name", "Database name");
public static final String CONFIG_DB_HOST = registerConfig("pg_db_host", "Database host");
public static final String CONFIG_DB_PORT = registerConfig("pg_db_port", "Database port");
public static final String CONFIG_DB_OPTIONS = registerConfig("pg_db_opts", "Database options");
+
private String dbHost;
private Integer dbPort;
private String dbName;
private String dbUser;
private String dbPass;
private String dbOpts;
+
private DataSource ds;
private DataSourceConfig dsConfig;
private DSLContext dsl;
+ private JooqDao dao;
@Override
public void start() {
@@ -55,75 +51,61 @@ public void start() {
}
@Override
- public void stop() {
+ @SuppressWarnings("unchecked")
+ public void onEvent(final Event, ?> event) {
+ event.channel(DbEvents.FETCH_ONE).ifPresent(ev -> {
+ FetchOneByCondition p = ev.payload();
+ ev.respond((Record) dao.getOne((Table) p.table(), p.condition()).orElse(null));
+ });
- }
+ event.channel(DbEvents.INSERT_RETURNING).ifPresent(ev -> {
+ InsertAndReturn p = ev.payload();
+ ev.respond(dao.insertReturning((Table) p.table(), p.values()));
+ });
- @Override
- public Object onFailure(final Event event) {
- return null;
- }
+ event.channel(DbEvents.UPDATE_BY_ID_RETURNING).ifPresent(ev -> {
+ UpdateById p = ev.payload();
+ ev.respond((Record) dao.updateByIdReturning((Table) p.table(), (TableField) p.idField(), p.id(), p.values())
+ .orElse(null));
+ });
- @Override
- public void onEvent(final Event, ?> event) {
- event.channel(AppEvents.ADD_USER).ifPresent(ev -> ev.respond(saveUser(ev.payload())));
- event.channel(AppEvents.FETCH_USER).ifPresent(ev -> ev.respond(fetchUser(ev.payload())));
- event.channel(AppEvents.FETCH_APPS).ifPresent(ev -> ev.respond(getApps()));
- event.channel(AppEvents.APP_INT_REQ).ifPresent(ev -> ev.respond(saveNewUserIntegration(ev.payload())));
- }
+ event.channel(DbEvents.DELETE_WHERE).ifPresent(ev -> {
+ DeleteByCondition p = ev.payload();
+ ev.respond(dao.deleteWhere((Table) p.table(), p.condition()));
+ });
- private Map getApps() {
- final Map apps = dsl.selectFrom(APPS).fetchMap(APPS.NAME);
- return apps.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> {
- var r = e.getValue();
- var meta = (r.getMetadata() == null) ? null : r.getMetadata().data();
- return new AppDto(r.getId(), r.getName(), r.getLogoUrl(), meta);
- }, (a, b) -> a, // shouldn't hit since names are unique
- LinkedHashMap::new));
+ event.channel(DbEvents.FETCH_MAP).ifPresent(ev -> {
+ FetchMap payload = ev.payload();
+ ev.respond(dao.fetchMap((Table) payload.table(), payload.keyField()));
+ });
}
- private IntegrationsRecord saveNewUserIntegration(AppIntegrationRequestDto req) {
- final IntegrationsRecord res = dsl.insertInto(INTEGRATIONS).set(INTEGRATIONS.APP_ID, req.appId()).set(INTEGRATIONS.USER_ID, req.userId()).set(INTEGRATIONS.SCOPES, req.scopes().split(",")).set(INTEGRATIONS.ACCESS_TOKEN_ENC, req.accessToken().getBytes(StandardCharsets.UTF_8)).set(INTEGRATIONS.EXPIRES_AT, req.expiresAt().atOffset(ZoneOffset.UTC)).returning(INTEGRATIONS.USER_ID, INTEGRATIONS.APP_ID).fetchOne();
- return res;
- }
- private UsersRecord fetchUser(final String email) {
- final UsersRecord user = dsl.selectFrom(USERS).where(USERS.EMAIL.eq(email)).fetchOne();
- return user;
+ @Override
+ public void configure(final TypeMapI> changes, final TypeMapI> merged) {
+ this.dbName = merged.asString(CONFIG_DB_NAME);
+ this.dbUser = merged.asString(CONFIG_DB_USER);
+ this.dbPass = merged.asString(CONFIG_DB_PASS);
+ this.dbPort = merged.asInt(CONFIG_DB_PORT);
+ this.dbHost = merged.asString(CONFIG_DB_HOST);
+ this.dbOpts = merged.asString(CONFIG_DB_OPTIONS);
}
- private UsersRecord saveUser(final UserDto user) {
- final UsersRecord userRecord = dsl.transactionResult(configuration -> {
- DSLContext ctx = DSL.using(configuration);
- if (ctx.fetchExists(ctx.selectOne().from(USERS).where(USERS.EMAIL.eq(user.email())))) {
- return null;
- }
- return ctx.insertInto(USERS).set(USERS.EMAIL, user.email()).set(USERS.NAME, user.name()).set(USERS.PASSWORD_HASH, user.passwordHash()).returning(USERS.ID).fetchOne();
- });
- return userRecord;
+ @Override
+ public void stop() {
}
@Override
- public void configure(final TypeMapI> changes, final TypeMapI> merged) {
- this.dbName = changes.asStringOpt(CONFIG_DB_NAME).orElse(merged.asString(CONFIG_DB_NAME));
- this.dbUser = changes.asStringOpt(CONFIG_DB_USER).orElse(merged.asString(CONFIG_DB_USER));
-
- // On the config change event, this merged has values from application.properties and not application-.properties
- // even after doing NanoUtils.readProfiles() and using the context returned.
- this.dbPass = changes.asStringOpt(CONFIG_DB_PASS).orElse(merged.asString(CONFIG_DB_PASS));
- this.dbPort = changes.asIntOpt(CONFIG_DB_PORT).orElse(merged.asInt(CONFIG_DB_PORT));
- this.dbHost = changes.asStringOpt(CONFIG_DB_HOST).orElse(merged.asString(CONFIG_DB_HOST));
- this.dbOpts = changes.asStringOpt(CONFIG_DB_OPTIONS).orElse(merged.asString(CONFIG_DB_OPTIONS));
- if (changes.containsKey(CONFIG_DB_PORT)) {
- createOrUpdateDs(this.dbHost, changes.asInt(CONFIG_DB_PORT), this.dbName, this.dbUser, this.dbPass, this.dbOpts);
- }
+ public Object onFailure(final Event event) {
+ return null;
}
private void createOrUpdateDs(String host, Integer port, String name, String user, String pass, String options) {
DataSourceConfig newConfig = new DataSourceConfig(host, port, name, user, pass, options);
- if (null == this.dsConfig || !this.dsConfig.equals(newConfig)) {
+ if (this.dsConfig == null || !this.dsConfig.equals(newConfig)) {
this.ds = DataSourceFactory.create(newConfig);
this.dsl = JooqFactory.create(ds);
+ this.dao = new JooqDao(this.dsl);
this.dsConfig = newConfig;
}
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 073b337..a7e3a63 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,9 +1,5 @@
-app_service_http_port=8080
-app_log_level=DEBUG
-app_log_formatter=console
pg_db_host=localhost
pg_db_port=5432
pg_db_name=nanodb
pg_db_user=nanouser
pg_db_pass=CHANGEME
-# app_profiles=dev
diff --git a/src/test/java/org/ab/sentinel/controller/UserControllerTest.java b/src/test/java/org/ab/sentinel/controller/UserControllerTest.java
index d519007..4ed2f59 100644
--- a/src/test/java/org/ab/sentinel/controller/UserControllerTest.java
+++ b/src/test/java/org/ab/sentinel/controller/UserControllerTest.java
@@ -2,13 +2,12 @@
import org.ab.sentinel.PostgresContainer.SharedPostgresIT;
import org.ab.sentinel.model.Postgres;
+import org.ab.sentinel.service.AppService;
import org.ab.sentinel.service.PostgreSqlService;
import org.ab.sentinel.service.integrations.GithubIntegrationService;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.nanonative.nano.core.Nano;
-import org.nanonative.nano.core.model.Context;
-import org.nanonative.nano.helper.NanoUtils;
import org.nanonative.nano.services.http.HttpClient;
import org.nanonative.nano.services.http.HttpServer;
import org.nanonative.nano.services.http.model.HttpMethod;
@@ -16,11 +15,7 @@
import java.util.Map;
-import static org.ab.sentinel.service.PostgreSqlService.CONFIG_DB_HOST;
-import static org.ab.sentinel.service.PostgreSqlService.CONFIG_DB_PASS;
-import static org.ab.sentinel.service.PostgreSqlService.CONFIG_DB_PORT;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.nanonative.nano.core.model.Context.EVENT_CONFIG_CHANGE;
import static org.nanonative.nano.services.http.HttpServer.EVENT_HTTP_REQUEST;
class UserControllerTest {
@@ -36,22 +31,13 @@ public static void initializeDB() {
@Test
void registerUser() {
- final Nano nano = new Nano(Map.of("app_profiles", "dev"), new HttpServer(), new PostgreSqlService(), new GithubIntegrationService(), new HttpClient());
+ final Nano nano = new Nano(Map.of("app_profiles", "dev", "pg_db_port", dbProp.dbPort()), new HttpServer(), new PostgreSqlService(), new GithubIntegrationService(), new HttpClient(), new AppService());
- Context ctx = NanoUtils.readProfiles(nano.context(UserControllerTest.class));
- ctx.newEvent(EVENT_CONFIG_CHANGE, () -> Map.of(
- CONFIG_DB_HOST, dbProp.dbHost(),
- CONFIG_DB_PORT, dbProp.dbPort(),
- // TODO: Remove the below line as password is already mentioned in dev properties file and this is a work around
- CONFIG_DB_PASS, dbProp.dbPass()
- )).broadcast(true).send();
-
- ctx
- .subscribeEvent(EVENT_HTTP_REQUEST, UserController::registerUser);
+ nano.subscribeEvent(EVENT_HTTP_REQUEST, UserController::registerUser);
final HttpObject result = new HttpObject()
.methodType(HttpMethod.POST)
- .body(Map.of("email", "aa@berlin.iosk", "password", "abc","name","aj"))
+ .body(Map.of("email", "aa@berlin.iosk", "password", "abc", "name", "aj"))
.path(serverUrl + nano.service(HttpServer.class).port() + "/auth/register")
.send(nano.context(UserControllerTest.class));
@@ -62,15 +48,9 @@ void registerUser() {
@Test
void getApps() {
- final Nano nano = new Nano(Map.of("app_profiles", "dev"), new HttpServer(), new PostgreSqlService(), new GithubIntegrationService(), new HttpClient());
-
- nano.context(UserControllerTest.class).newEvent(EVENT_CONFIG_CHANGE, () -> Map.of(
- CONFIG_DB_HOST, dbProp.dbHost(),
- CONFIG_DB_PORT, dbProp.dbPort()
- )).send();
+ final Nano nano = new Nano(Map.of("app_profiles", "dev", "pg_db_port", dbProp.dbPort()), new HttpServer(), new PostgreSqlService(), new GithubIntegrationService(), new HttpClient(), new AppService());
- nano.context(AppController.class)
- .subscribeEvent(EVENT_HTTP_REQUEST, AppController::getApps);
+ nano.subscribeEvent(EVENT_HTTP_REQUEST, AppController::getApps);
final HttpObject result = new HttpObject()
.methodType(HttpMethod.GET)