diff --git a/gmcserver-server/migrations/202402162056.sql b/gmcserver-server/migrations/202402162056.sql new file mode 100644 index 0000000..ca73959 --- /dev/null +++ b/gmcserver-server/migrations/202402162056.sql @@ -0,0 +1,110 @@ +-- DROP SCHEMA public; + +CREATE SCHEMA public AUTHORIZATION postgres; + +COMMENT ON SCHEMA public IS 'standard public schema'; +-- public.users definition + +-- Drop table + +-- DROP TABLE public.users; + +CREATE TABLE public.users ( + id uuid NOT NULL DEFAULT gen_random_uuid(), + username varchar NOT NULL, + "password" varchar NULL, + email varchar NOT NULL, + "deviceLimit" int4 NOT NULL, + "gmcId" int8 NOT NULL, + "admin" bool NOT NULL, + mfa bool NOT NULL, + "alertEmails" bool NOT NULL, + "mfaKey" jsonb NULL, + CONSTRAINT users_pk PRIMARY KEY (id) +); +CREATE INDEX users_email_idx ON public.users USING btree (email); +CREATE UNIQUE INDEX users_gmcid_idx ON public.users USING btree ("gmcId"); +CREATE INDEX users_username_idx ON public.users USING btree (username); + + +-- public.calendar definition + +-- Drop table + +-- DROP TABLE public.calendar; + +CREATE TABLE public.calendar ( + id uuid NOT NULL DEFAULT gen_random_uuid(), + "deviceId" uuid NOT NULL, + "createdAt" timestamp NOT NULL, + recs jsonb NOT NULL, + "inProgress" bool NOT NULL +); +COMMENT ON TABLE public.calendar IS 'Single day averages of devices'; + + +-- public.devices definition + +-- Drop table + +-- DROP TABLE public.devices; + +CREATE TABLE public.devices ( + id uuid NOT NULL DEFAULT gen_random_uuid(), + model varchar NULL, + "name" varchar NULL, + importedfrom varchar NULL, + "location" point NULL, + "owner" uuid NOT NULL, + lastrecordid uuid NULL, + gmcid int8 NULL, + disabled bool NOT NULL, + lastemailalert timestamp NOT NULL, + stddevalertlimit float8 NULL, + proxiessettings jsonb NULL, + CONSTRAINT devices_pk PRIMARY KEY (id) +); +CREATE UNIQUE INDEX devices_gmcid_idx ON public.devices USING btree (gmcid); + + +-- public.records definition + +-- Drop table + +-- DROP TABLE public.records; + +CREATE TABLE public.records ( + id uuid NOT NULL DEFAULT gen_random_uuid(), + "deviceId" uuid NOT NULL, + "date" timestamp NOT NULL, + ip inet NULL, + "type" varchar NULL, + "location" point NULL, + cpm float8 NULL, + acpm float8 NULL, + usv float8 NULL, + co2 float8 NULL, + hcho float8 NULL, + tmp float8 NULL, + ap float8 NULL, + hmdt float8 NULL, + accy float8 NULL, + CONSTRAINT records_pk PRIMARY KEY (id) +); +CREATE INDEX records_deviceid_date_idx ON public.records USING btree ("deviceId", date); + + +-- public.calendar foreign keys + +ALTER TABLE public.calendar ADD CONSTRAINT calendar_devices_fk FOREIGN KEY ("deviceId") REFERENCES public.devices(id) ON DELETE CASCADE; + + +-- public.devices foreign keys + +ALTER TABLE public.devices ADD CONSTRAINT devices_lastrecordid_fk FOREIGN KEY (lastrecordid) REFERENCES public.records(id) ON DELETE CASCADE; +ALTER TABLE public.devices ADD CONSTRAINT devices_owner_fk FOREIGN KEY ("owner") REFERENCES public.users(id) ON DELETE CASCADE; + + +-- public.records foreign keys + +ALTER TABLE public.records ADD CONSTRAINT records_fk FOREIGN KEY ("deviceId") REFERENCES public.devices(id) ON DELETE CASCADE; diff --git a/gmcserver-server/pom.xml b/gmcserver-server/pom.xml index 133b885..73f525d 100644 --- a/gmcserver-server/pom.xml +++ b/gmcserver-server/pom.xml @@ -40,8 +40,9 @@ ./mail.json ./gmcserver-email/out/ - 4.3.8 + 4.5.3 2.15.0 + 2.22.1 @@ -110,53 +111,42 @@ argon2-jvm 2.7 - org.apache.logging.log4j log4j-core - 2.17.1 - + ${log4j.version} + org.apache.logging.log4j log4j-slf4j-impl - 2.17.1 + ${log4j.version} - commons-codec commons-codec 1.14 - org.apache.commons commons-collections4 4.4 - com.eatthepath java-otp 0.2.0 - org.jsoup jsoup 1.15.3 - org.ldaptive ldaptive - 2.1.1 + 2.3.0 - org.junit.jupiter junit-jupiter-engine diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/DatabaseManager.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/DatabaseManager.java index 4173f1f..ad68fad 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/DatabaseManager.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/DatabaseManager.java @@ -19,8 +19,10 @@ import java.util.Map; +import io.vertx.core.tracing.TracingPolicy; +import io.vertx.pgclient.PgBuilder; import io.vertx.pgclient.PgConnectOptions; -import io.vertx.pgclient.PgPool; +import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.RowSet; @@ -29,24 +31,28 @@ import me.vinceh121.gmcserver.managers.AbstractManager; public class DatabaseManager extends AbstractManager { - private final PgPool pool; + private final Pool pool; public DatabaseManager(final GMCServer srv) { super(srv); + final PgConnectOptions optsConfig; - if (srv.getConfig().contains("db.uri")) { + + if (srv.getConfig().containsKey("db.uri")) { optsConfig = PgConnectOptions.fromUri(srv.getConfig().getProperty("db.uri")); } else { optsConfig = new PgConnectOptions(); } final PgConnectOptions optsEnv = PgConnectOptions.fromEnv(); - final PgConnectOptions optsEffective = optsConfig.merge(optsEnv.toJson()); + final PgConnectOptions optsEffective = optsEnv.merge(optsConfig.toJson()); + + optsEffective.setTracingPolicy(TracingPolicy.ALWAYS); final PoolOptions poolOpts = new PoolOptions(); poolOpts.setMaxSize(5); - this.pool = PgPool.pool(srv.getVertx(), optsEffective, poolOpts); + this.pool = PgBuilder.pool().with(poolOpts).connectingTo(optsEffective).build(); this.checkIndexes(); } @@ -55,7 +61,7 @@ public DatabaseManager(final GMCServer srv) { private void checkIndexes() { } - public PgPool getPool() { + public Pool getPool() { return this.pool; } diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/auth/InternalAuthenticator.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/auth/InternalAuthenticator.java index d75a652..930c417 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/auth/InternalAuthenticator.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/auth/InternalAuthenticator.java @@ -20,6 +20,7 @@ import java.util.Collections; import io.vertx.core.Future; +import io.vertx.sqlclient.RowIterator; import me.vinceh121.gmcserver.GMCServer; import me.vinceh121.gmcserver.entities.User; import me.vinceh121.gmcserver.exceptions.AuthenticationException; @@ -45,13 +46,15 @@ public Future login(final String username, final String password) { .mapTo(User.class) .execute(Collections.singletonMap("username", username)) .onSuccess(rowSet -> { - final User user = rowSet.iterator().next(); + final RowIterator iter = rowSet.iterator(); - if (user == null) { + if (!iter.hasNext()) { promise.fail(new EntityNotFoundException("User not found")); return; } + final User user = iter.next(); + if (user.getPassword() == null) { promise.fail(new IllegalStateException("User account disabled")); return; diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/AbstractEntity.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/AbstractEntity.java index 281db2c..89191f9 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/AbstractEntity.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/AbstractEntity.java @@ -17,11 +17,15 @@ */ package me.vinceh121.gmcserver.entities; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import io.vertx.core.json.JsonObject; +import io.vertx.core.json.jackson.DatabindCodec; public abstract class AbstractEntity { private UUID id = UUID.randomUUID(); @@ -46,4 +50,14 @@ public JsonObject toJson() { public JsonObject toPublicJson() { return this.toJson(); } + + public static String sqlFields(final Class cls) { + final List list = DatabindCodec.mapper() + .writerFor(cls) + .getConfig() + .introspect(DatabindCodec.mapper().constructType(cls)) + .findProperties(); + + return list.stream().map(p -> "#{" + p.getName() + "}").collect(Collectors.joining(",")); + } } diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Device.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Device.java index a8c497a..b15449f 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Device.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Device.java @@ -184,4 +184,8 @@ public JsonObject toMapJson() { obj.put("lastRecord", this.getLastRecord().toPublicJson()); return obj; } + + public static String sqlFields() { + return AbstractEntity.sqlFields(Device.class); + } } diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceCalendar.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceCalendar.java index 193e40b..779c5b2 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceCalendar.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceCalendar.java @@ -59,4 +59,8 @@ public boolean isInProgress() { public void setInProgress(final boolean inProgress) { this.inProgress = inProgress; } + + public static String sqlFields() { + return AbstractEntity.sqlFields(DeviceCalendar.class); + } } diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceStats.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceStats.java index d24ecb4..7e41cbc 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceStats.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/DeviceStats.java @@ -89,4 +89,8 @@ public JsonObject toJson() { obj.remove("id"); return obj; } + + public static String sqlFields() { + return AbstractEntity.sqlFields(DeviceStats.class); + } } diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Record.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Record.java index 07cbed1..24024a2 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Record.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/Record.java @@ -318,6 +318,10 @@ public String toString() { + ", location=" + this.location + "]"; } + public static String sqlFields() { + return AbstractEntity.sqlFields(Record.class); + } + public static class Builder { // XXX this will need a big clean up but at least it splits stuff private final Record record = new Record(); diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/User.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/User.java index 1a06688..5b34e78 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/User.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/entities/User.java @@ -148,4 +148,7 @@ public String toString() { return this.getUsername() + " (" + this.getId().toString() + ")"; } + public static String sqlFields() { + return AbstractEntity.sqlFields(User.class); + } } diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceCalendarManager.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceCalendarManager.java index 5b060d2..b59cde5 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceCalendarManager.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceCalendarManager.java @@ -109,7 +109,7 @@ protected void executeSync(final Promise promise) { cal.setInProgress(true); this.srv.getDatabaseManager() - .update("INSERT INTO calendar VALUES") + .update("INSERT INTO calendar VALUES (" + DeviceCalendar.sqlFields() + ")") .mapFrom(DeviceCalendar.class) .execute(cal) .onSuccess(r -> { diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceManager.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceManager.java index 41cee9f..e62dd10 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceManager.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/DeviceManager.java @@ -612,15 +612,15 @@ protected void executeSync(final Promise promise) { } dev.setLocation(location); - promise.complete(dev); - if (this.insertInDb) { this.srv.getDatabaseManager() - .update("INSERT INTO devices VALUES") + .update("INSERT INTO devices VALUES (" + Device.sqlFields() + ")") .mapFrom(Device.class) .execute(dev) .onSuccess(rs -> promise.complete(dev)) .onFailure(promise::fail); + } else { + promise.complete(dev); } }) .onFailure(promise::fail); diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/ImportManager.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/ImportManager.java index ed47c1e..21cc160 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/ImportManager.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/ImportManager.java @@ -106,7 +106,7 @@ protected void executeSync(Promise promise) { } this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .executeBatch(recs) .onSuccess(res -> { @@ -123,7 +123,7 @@ private void importPageRecurse(final int page) { if (recs.size() != 0) { this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .executeBatch(recs) .onSuccess(res -> { @@ -396,7 +396,7 @@ protected void executeSync(Promise promise) { } this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .executeBatch(recs) .onSuccess(res -> { @@ -415,7 +415,7 @@ private void importPageRecurse(final int page) { if (recs.size() != 0) { this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .executeBatch(recs) .onSuccess(res -> { @@ -693,7 +693,7 @@ protected void executeSync(Promise promise) { } this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .executeBatch(recs) .onSuccess(r -> { @@ -760,7 +760,7 @@ protected void executeSync(final Promise promise) { rec.setDate(date); // TODO batch inserts this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .execute(rec) .onSuccess(r -> { diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/LoggingManager.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/LoggingManager.java index 43a4f99..6ac82bf 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/LoggingManager.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/LoggingManager.java @@ -75,7 +75,7 @@ protected void executeSync(final Promise promise) { if (this.insertInDb) { joinedFutures.add(Future.future(p -> { this.srv.getDatabaseManager() - .update("INSERT INTO records VALUES") + .update("INSERT INTO records VALUES (" + Record.sqlFields() + ")") .mapFrom(Record.class) .execute(this.record) .onSuccess(e -> p.complete()) diff --git a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/UserManager.java b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/UserManager.java index 979a762..ff4d9c7 100644 --- a/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/UserManager.java +++ b/gmcserver-server/src/main/java/me/vinceh121/gmcserver/managers/UserManager.java @@ -20,7 +20,6 @@ import java.security.SecureRandom; import java.security.SignatureException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; @@ -32,6 +31,7 @@ import io.vertx.core.CompositeFuture; import io.vertx.core.Future; import io.vertx.core.Promise; +import io.vertx.sqlclient.RowIterator; import io.vertx.sqlclient.Tuple; import me.vinceh121.gmcserver.GMCServer; import me.vinceh121.gmcserver.actions.AbstractAction; @@ -96,14 +96,17 @@ protected void executeSync(final Promise promise) { this.srv.getDatabaseManager() .query("SELECT * FROM users WHERE " + (this.gmcId == null ? "id = #{id}" : "gmcId = #{gmcId}")) .mapTo(User.class) - .execute(Map.of("id", this.id, "gmcId", this.gmcId)) + .execute(this.gmcId == null ? Map.of("id", this.id) : Map.of("gmcId", this.gmcId)) .onSuccess(rowSet -> { - User user = rowSet.iterator().next(); - if (user != null) { - promise.complete(user); - } else { + final RowIterator iter = rowSet.iterator(); + + if (!iter.hasNext()) { promise.fail(new EntityNotFoundException("Failed to get user")); + return; } + + final User user = iter.next(); + promise.complete(user); }) .onFailure(promise::fail); } @@ -256,8 +259,7 @@ protected void executeSync(final Promise promise) { user.setGmcId(this.gmcId); } - @SuppressWarnings("rawtypes") - List checks = new ArrayList<>(2); + List> checks = new ArrayList<>(2); // FIXME rely on UNIQUE constraints if (this.checkUsernameAvailable) { checks.add(Future.future(p -> { @@ -291,10 +293,10 @@ protected void executeSync(final Promise promise) { })); } - CompositeFuture.all(checks).onSuccess(f -> { + Future.all(checks).onSuccess(f -> { if (this.insertInDb) { this.srv.getDatabaseManager() - .update("INSERT INTO users VALUES") + .update("INSERT INTO users VALUES (" + User.sqlFields() + ")") .mapFrom(User.class) .execute(user) .onSuccess(e -> promise.complete(user)) diff --git a/gmcserver-server/src/main/resources/log4j2.xml b/gmcserver-server/src/main/resources/log4j2.xml index 99bf28d..b18c141 100644 --- a/gmcserver-server/src/main/resources/log4j2.xml +++ b/gmcserver-server/src/main/resources/log4j2.xml @@ -1,5 +1,5 @@ - +