Ver código fonte

Added legacy endpoint for inserting observations as static units.

Lukas Cerny 1 ano atrás
pai
commit
ab4cf8cdda

+ 1 - 1
docker.dev.env

@@ -17,6 +17,6 @@ AUTH_KEYSTORE_TYPE=PKCS12
 AUTH_KEYSTORE_PASSWORD=SENSlog
 
 # Modules
-MODULE_ANALYTIC_DISABLED=false
+MODULE_ANALYTIC_DISABLED=true
 MODULE_ANALYTIC_HOST=172.17.0.1
 MODULE_ANALYTIC_PORT=8080

+ 6 - 0
init.sql

@@ -247,6 +247,12 @@ CREATE TABLE maplog.alert (
     time_received TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
 );
 
+CREATE TABLE maplog.unit_static_to_mobile (
+    static_unit_id  BIGINT NOT NULL,
+    mobile_unit_id  BIGINT NOT NULL,
+    PRIMARY KEY (static_unit_id, mobile_unit_id)
+);
+
 ALTER TABLE maplog.alert OWNER TO senslog;
 
 CREATE SEQUENCE maplog.alert_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;

+ 30 - 0
src/main/java/cz/senslog/telemetry/database/domain/StaticUnitBasicInfo.java

@@ -0,0 +1,30 @@
+package cz.senslog.telemetry.database.domain;
+
+public class StaticUnitBasicInfo {
+
+    private final long staticUnitId;
+    private final long mobileUnitId;
+    private final long campaignId;
+
+    public static StaticUnitBasicInfo of(long staticUnitId, long mobileUnitId, long campaignId) {
+        return new StaticUnitBasicInfo(staticUnitId, mobileUnitId, campaignId);
+    }
+
+    private StaticUnitBasicInfo(long staticUnitId, long mobileUnitId, long campaignId) {
+        this.staticUnitId = staticUnitId;
+        this.mobileUnitId = mobileUnitId;
+        this.campaignId = campaignId;
+    }
+
+    public long getStaticUnitId() {
+        return staticUnitId;
+    }
+
+    public long getMobileUnitId() {
+        return mobileUnitId;
+    }
+
+    public long getCampaignId() {
+        return campaignId;
+    }
+}

+ 21 - 2
src/main/java/cz/senslog/telemetry/database/repository/MapLogRepository.java

@@ -192,6 +192,25 @@ public class MapLogRepository implements SensLogRepository {
                 )).toList());
     }
 
+    private static final Function<Row, StaticUnitBasicInfo> ROW_TO_STATIC_UNIT_BASIC_INFO = row -> StaticUnitBasicInfo.of(
+        row.getLong("static_unit_id"),
+        row.getLong("mobile_unit_id"),
+        row.getLong("campaign_id")
+    );
+
+    @Override
+    public Future<StaticUnitBasicInfo> findMobileUnitByStaticUnitIdAndTimestamp(long unitId, OffsetDateTime timestamp) {
+        return client.preparedQuery("SELECT ustm.static_unit_id, ustm.mobile_unit_id, utc.campaign_id FROM maplog.unit_static_to_mobile ustm " +
+                        "JOIN maplog.unit_to_campaign utc on utc.unit_id = mobile_unit_id " +
+                        "JOIN maplog.campaign c on c.id = utc.campaign_id " +
+                        "WHERE ustm.static_unit_id = $1 " +
+                            "and c.from_time <= $2 and $2 <= (case when c.to_time is null then now() else c.to_time end) " +
+                        "LIMIT 1")
+                .execute(Tuple.of(unitId, timestamp))
+                .map(RowSet::iterator)
+                .map(it -> it.hasNext() ? ROW_TO_STATIC_UNIT_BASIC_INFO.apply(it.next()) : null);
+    }
+
     @Override
     public Future<Boolean> createSensor(Sensor sensor, long unitId) {
         return client.preparedQuery("WITH rows AS " +
@@ -1497,7 +1516,7 @@ public class MapLogRepository implements SensLogRepository {
 
     @Override
     public Future<List<UnitTelemetry>> findObservationsByCampaignIdAndUnitId(
-            long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters
+            long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, SortType sortType, List<Filter> filters
     ) {
         String whereTimestampClause;
         Tuple tupleParams;
@@ -1552,7 +1571,7 @@ public class MapLogRepository implements SensLogRepository {
                     "FROM maplog.obs_telemetry AS tel " +
                     "JOIN maplog.unit_to_campaign utc on tel.unit_id = utc.unit_id " +
                     "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND " + whereTimestampClause + whereFiltersClause + " " +
-                    "ORDER BY tel.time_stamp OFFSET $3 LIMIT $4;";
+                    "ORDER BY tel.time_stamp "+sortType.name()+" OFFSET $3 LIMIT $4;";
 
         return client.preparedQuery("SELECT s.sensor_id, s.name FROM maplog.sensor s " +
                         "JOIN maplog.unit_to_sensor uts ON uts.sensor_id = s.sensor_id " +

+ 4 - 5
src/main/java/cz/senslog/telemetry/database/repository/SensLogRepository.java

@@ -7,7 +7,6 @@ import cz.senslog.telemetry.database.domain.Filter;
 import io.vertx.core.Future;
 
 import java.time.OffsetDateTime;
-import java.time.ZoneId;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -22,7 +21,7 @@ public interface SensLogRepository {
     Future<UnitTelemetry> saveTelemetry(UnitTelemetry data);
     Future<List<UnitTelemetry>> saveAllTelemetry(List<UnitTelemetry> data);
 
-
+    Future<StaticUnitBasicInfo> findMobileUnitByStaticUnitIdAndTimestamp(long unitId, OffsetDateTime timestamp);
     Future<Boolean> createSensor(Sensor sensor, long unitId);
 
 
@@ -124,9 +123,9 @@ public interface SensLogRepository {
                 });
     }
 
-    Future<List<UnitTelemetry>> findObservationsByCampaignIdAndUnitId(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters);
-    default Future<PagingRetrieve<List<UnitTelemetry>>> findObservationsByCampaignIdAndUnitIdWithPaging(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters) {
-        return findObservationsByCampaignIdAndUnitId(campaignId, unitId, from, to, offset, limit+1, filters)
+    Future<List<UnitTelemetry>> findObservationsByCampaignIdAndUnitId(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, SortType sortType, List<Filter> filters);
+    default Future<PagingRetrieve<List<UnitTelemetry>>> findObservationsByCampaignIdAndUnitIdWithPaging(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, SortType sortType, List<Filter> filters) {
+        return findObservationsByCampaignIdAndUnitId(campaignId, unitId, from, to, offset, limit+1, sortType, filters)
                 .map(data -> {
                     boolean hasNext = data.size() > limit;
                     if (hasNext) {

+ 2 - 0
src/main/java/cz/senslog/telemetry/server/HttpVertxServer.java

@@ -136,6 +136,8 @@ public final class HttpVertxServer extends AbstractVerticle {
                     openAPIRouterBuilder.operation("campaignIdUnitIdAlertsGET").handler(apiHandler::campaignIdUnitIdAlertsGET);
                     openAPIRouterBuilder.operation("entityIdActionIdUnitIdAlertsGET").handler(apiHandler::entityIdActionIdUnitIdAlertsGET);
 
+                    openAPIRouterBuilder.operation("legacyInsertObservationsGET").handler(apiHandler::legacyInsertObservationsGET);
+
                     Router mainRouter = openAPIRouterBuilder.createRouter();
 
                     mainRouter.route().failureHandler(ExceptionHandler.createAsJSON());

+ 43 - 1
src/main/java/cz/senslog/telemetry/server/ws/OpenAPIHandler.java

@@ -21,12 +21,18 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.OffsetDateTime;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.function.*;
 import java.util.stream.Collectors;
 
+import static cz.senslog.telemetry.database.SortType.DESC;
 import static cz.senslog.telemetry.database.validation.UnitTelemetryValidation.*;
 import static cz.senslog.telemetry.module.EventBusModulePaths.SENSLOG_ALERTS;
 import static cz.senslog.telemetry.server.ws.AuthorizationScope.READ_INFRASTRUCTURE;
@@ -41,6 +47,7 @@ import static io.vertx.core.http.HttpHeaders.ACCEPT;
 import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
 import static java.time.OffsetDateTime.ofInstant;
 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+import static java.util.Collections.emptyList;
 import static java.util.Comparator.comparing;
 import static java.util.stream.Collectors.*;
 
@@ -290,7 +297,7 @@ public class OpenAPIHandler {
         };
 
         CascadeCondition.<AuthBearerUser, Future<PagingRetrieve<List<UnitTelemetry>>>>ofNullable(user)
-                .ifThenAsDefault(u -> u.hasPermissionScope(READ_INFRASTRUCTURE), () -> repo.findObservationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, offset, limit, filters))
+                .ifThenAsDefault(u -> u.hasPermissionScope(READ_INFRASTRUCTURE), () -> repo.findObservationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, offset, limit, SortType.ASC, filters))
                 .ifThen(u -> u.hasPermissionScope(READ_PERSONAL), u -> repo.findObservationsByIdentityAndCampaignIdAndUnitIdWithPaging(u.getId(), campaignId, unitId, from, to, offset, limit, filters))
                 .execute(r -> {
                     switch (format) {
@@ -1762,4 +1769,39 @@ public class OpenAPIHandler {
                 ).encode()))
                 .onFailure(rc::fail);
     }
+
+    public void legacyInsertObservationsGET(RoutingContext rc) {
+
+        double value;
+        long unitId;
+        String sensorIdStr;
+        OffsetDateTime timestamp;
+
+        try {
+            value = Double.parseDouble(rc.queryParam("value").get(0));
+            unitId = Long.parseLong(rc.queryParam("unit_id").get(0));
+            sensorIdStr = rc.queryParam("sensor_id").get(0);
+            timestamp = OffsetDateTime.parse(rc.queryParam("date").get(0), ISO_OFFSET_DATE_TIME);
+        } catch (Exception e) {
+            logger.catching(e);
+            rc.response().end("false"); return;
+        }
+
+        final int minuteDiff = 30;
+
+        repo.findMobileUnitByStaticUnitIdAndTimestamp(unitId, timestamp)
+                .compose(u -> repo
+                    .findObservationsByCampaignIdAndUnitId(u.getCampaignId(), u.getMobileUnitId(), timestamp.minusMinutes(minuteDiff), timestamp.plusMinutes(minuteDiff), 0, 1, DESC, emptyList()))
+                .compose(mobileTelemetry -> {
+                    if (mobileTelemetry.isEmpty()) {
+                        // TODO no unit location within -30 - +30
+                        return Future.succeededFuture(false);
+                    } else {
+                        UnitTelemetry lastUnitTelemetry = mobileTelemetry.get(0);
+                        return repo.saveTelemetry(UnitTelemetry.of(unitId, timestamp, lastUnitTelemetry.getLocation(), lastUnitTelemetry.getSpeed(), JsonObject.of(sensorIdStr, value))).map(r -> r.getId() > 0);
+                    }
+                })
+                .onSuccess(res -> rc.response().end(res.toString()))
+                .onFailure(rc::fail);
+    }
 }

+ 48 - 0
src/main/resources/openAPISpec.yaml

@@ -1452,6 +1452,54 @@ paths:
               schema:
                 $ref: '#/components/schemas/Error'
 
+  /legacy/observations:
+    get:
+      operationId: legacyInsertObservationsGET
+      description: Legacy endpoint from SensLog v1 to insert observations from static units that are assigned to a mobile unit to get locations.
+      tags:
+        - Legacy
+      parameters:
+        - in: query
+          name: Operation
+          required: true
+          schema:
+            type: string
+            enum:
+              - InsertObservation
+        - in: query
+          name: value
+          required: true
+          schema:
+            type: number
+            format: double
+        - in: query
+          name: date
+          required: true
+          schema:
+            type: string
+            format: date-time
+        - in: query
+          name: unit_id
+          required: true
+          schema:
+            type: integer
+            format: int64
+            minimum: 1
+        - in: query
+          name: sensor_id
+          required: true
+          schema:
+            type: integer
+            format: int64
+            minimum: 1
+      responses:
+        200:
+          description: Boolean value representing success of the insert operation.
+          content:
+            text/plain:
+              schema:
+                type: boolean
+
 components:
   securitySchemes:
     bearerAuth:

+ 9 - 4
src/test/java/cz/senslog/telemetry/MockSensLogRepository.java

@@ -47,6 +47,11 @@ public class MockSensLogRepository implements SensLogRepository {
     }
 
     @Override
+    public Future<StaticUnitBasicInfo> findMobileUnitByStaticUnitIdAndTimestamp(long unitId, OffsetDateTime timestamp) {
+        return Future.succeededFuture(StaticUnitBasicInfo.of(10L, 11L, 1L));
+    }
+
+    @Override
     public Future<Boolean> createSensor(Sensor sensor, long unitId) {
         return Future.succeededFuture(true);
     }
@@ -492,24 +497,24 @@ public class MockSensLogRepository implements SensLogRepository {
     }
 
     @Override
-    public Future<List<UnitTelemetry>> findObservationsByCampaignIdAndUnitId(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters) {
+    public Future<List<UnitTelemetry>> findObservationsByCampaignIdAndUnitId(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, SortType sortType, List<Filter> filters) {
         return Future.succeededFuture(mockUnitTelemetries());
     }
 
     @Override
-    public Future<PagingRetrieve<List<UnitTelemetry>>> findObservationsByCampaignIdAndUnitIdWithPaging(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters) {
+    public Future<PagingRetrieve<List<UnitTelemetry>>> findObservationsByCampaignIdAndUnitIdWithPaging(long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, SortType sortType, List<Filter> filters) {
         List<UnitTelemetry> observations = mockUnitTelemetries();
         return Future.succeededFuture(new PagingRetrieve<>(true, observations.size(), observations));
     }
 
     @Override
     public Future<List<UnitTelemetry>> findObservationsByIdentityAndCampaignIdAndUnitId(String userIdentity, long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters) {
-        return findObservationsByCampaignIdAndUnitId(campaignId, unitId, from, to, offset, limit, filters);
+        return findObservationsByCampaignIdAndUnitId(campaignId, unitId, from, to, offset, limit, SortType.ASC, filters);
     }
 
     @Override
     public Future<PagingRetrieve<List<UnitTelemetry>>> findObservationsByIdentityAndCampaignIdAndUnitIdWithPaging(String userIdentity, long campaignId, long unitId, OffsetDateTime from, OffsetDateTime to, int offset, int limit, List<Filter> filters) {
-        return findObservationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, offset, limit, filters);
+        return findObservationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, offset, limit, SortType.ASC, filters);
     }
 
     @Override

+ 15 - 15
src/test/java/cz/senslog/telemetry/database/repository/MapLogRepositoryTest.java

@@ -797,7 +797,7 @@ class MapLogRepositoryTest {
     @Test
     @DisplayName("DB Test: findObservationsByCampaignIdAndUnitIdWithPaging#1")
     void findObservationsByCampaignIdAndUnitIdWithPaging_NM1(Vertx vertx, VertxTestContext testContext) {
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 4, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 4, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(4);
             assertThat(data.hasNext()).isTrue();
 
@@ -838,7 +838,7 @@ class MapLogRepositoryTest {
 
         OffsetDateTime from = OffsetDateTime.of(2023, 12, 20, 0, 0, 0, 0, UTC);
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, null, 0, 5, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, null, 0, 5, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(5);
             assertThat(data.hasNext()).isTrue();
 
@@ -861,7 +861,7 @@ class MapLogRepositoryTest {
         OffsetDateTime from = OffsetDateTime.of(2023, 12, 20, 0, 0, 0, 0, UTC);
         OffsetDateTime to = OffsetDateTime.of(2023, 12, 25, 0, 0, 0, 0, UTC);
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, to, 0, 10, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, to, 0, 10, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(3);
             assertThat(data.hasNext()).isFalse();
 
@@ -884,7 +884,7 @@ class MapLogRepositoryTest {
         OffsetDateTime from = OffsetDateTime.of(2023, 12, 25, 0, 0, 0, 0, UTC);
         OffsetDateTime to = OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, UTC);
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, to, 0, 10, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, to, 0, 10, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(1);
             assertThat(data.hasNext()).isFalse();
 
@@ -902,7 +902,7 @@ class MapLogRepositoryTest {
 
         OffsetDateTime to = OffsetDateTime.of(2023, 12, 25, 0, 0, 0, 0, UTC);
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, to, 0, 10, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, to, 0, 10, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(3);
             assertThat(data.hasNext()).isFalse();
 
@@ -921,7 +921,7 @@ class MapLogRepositoryTest {
     @Test
     @DisplayName("DB Test: findObservationsByCampaignIdAndUnitIdWithPaging#6")
     void findObservationsByCampaignIdAndUnitIdWithPaging_NM6(Vertx vertx, VertxTestContext testContext) {
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 4, 1, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 4, 1, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(1);
             assertThat(data.hasNext()).isTrue();
 
@@ -941,7 +941,7 @@ class MapLogRepositoryTest {
                 Filter.of(FilterType.UNIT, FilterAttribute.SPEED, null, FilterOperation.NE, 0f)
         );
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 1, filters).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 1, SortType.ASC, filters).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(1);
             assertThat(data.hasNext()).isTrue();
 
@@ -964,7 +964,7 @@ class MapLogRepositoryTest {
                 Filter.of(FilterType.UNIT, FilterAttribute.LATITUDE, null, FilterOperation.LT, 45.f)
         );
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 1, filters).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 1, SortType.ASC, filters).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(1);
 
             UnitTelemetry t = data.data().get(0);
@@ -982,7 +982,7 @@ class MapLogRepositoryTest {
         // No observations before the date 'to'.
         OffsetDateTime to = OffsetDateTime.of(2023, 12, 20, 0, 0, 0, 0, UTC);
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, to, 0, 1, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, to, 0, 1, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(0);
             assertThat(data.hasNext()).isFalse();
 
@@ -997,7 +997,7 @@ class MapLogRepositoryTest {
 
         OffsetDateTime from = OffsetDateTime.of(2024, 2, 1, 0, 0, 0, 0, UTC);
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, null, 0, 1, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, from, null, 0, 1, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(0);
             assertThat(data.hasNext()).isFalse();
 
@@ -1009,7 +1009,7 @@ class MapLogRepositoryTest {
     @DisplayName("DB Test: findObservationsByCampaignIdAndUnitIdWithPaging#11")
     void findObservationsByCampaignIdAndUnitIdWithPaging_NM11(Vertx vertx, VertxTestContext testContext) {
         //  Campaign ID 10 does not exist.
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(10, 1000, null, null, 0, 1, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(10, 1000, null, null, 0, 1, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(0);
             assertThat(data.hasNext()).isFalse();
 
@@ -1020,7 +1020,7 @@ class MapLogRepositoryTest {
     @Test
     @DisplayName("DB Test: findObservationsByCampaignIdAndUnitIdWithPaging#12")
     void findObservationsByCampaignIdAndUnitIdWithPaging_NM12(Vertx vertx, VertxTestContext testContext) {
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 2000, null, null, 0, 1, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 2000, null, null, 0, 1, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(0);
             assertThat(data.hasNext()).isFalse();
 
@@ -1032,7 +1032,7 @@ class MapLogRepositoryTest {
     @DisplayName("DB Test: findObservationsByCampaignIdAndUnitIdWithPaging#13")
     void findObservationsByCampaignIdAndUnitIdWithPaging_NM13(Vertx vertx, VertxTestContext testContext) {
         //  No access to the unit 1000 from the campaign 2
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(2, 1000, null, null, 0, 1, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(2, 1000, null, null, 0, 1, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(0);
             assertThat(data.hasNext()).isFalse();
 
@@ -1044,7 +1044,7 @@ class MapLogRepositoryTest {
     @DisplayName("DB Test: findObservationsByCampaignIdAndUnitIdWithPaging#14")
     void findObservationsByCampaignIdAndUnitIdWithPaging_NM14(Vertx vertx, VertxTestContext testContext) {
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 3000, null, null, 0, 5, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 3000, null, null, 0, 5, SortType.ASC, emptyList()).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(1);
             assertThat(data.hasNext()).isFalse();
 
@@ -1074,7 +1074,7 @@ class MapLogRepositoryTest {
                 Filter.of(FilterType.SENSOR, FilterAttribute.ID, "1002", FilterOperation.EQ, 12)
         );
 
-        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 10, filters).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
+        repo.findObservationsByCampaignIdAndUnitIdWithPaging(1, 1000, null, null, 0, 10, SortType.ASC, filters).onComplete(testContext.succeeding(data -> testContext.verify(() -> {
             assertThat(data.size()).isEqualTo(1);
 
             UnitTelemetry t = data.data().get(0);

+ 59 - 2
src/test/java/cz/senslog/telemetry/server/ws/OpenAPIHandlerTest.java

@@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.NullSource;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.openapi4j.core.exception.EncodeException;
 import org.openapi4j.core.exception.ResolutionException;
@@ -47,12 +47,14 @@ import java.util.stream.Stream;
 import static cz.senslog.telemetry.server.ws.ContentType.JSON;
 import static cz.senslog.telemetry.server.ws.OpenAPIHandlerTest.OpenAPIResponseTyp.SUCCESS;
 import static cz.senslog.telemetry.server.ws.OpenAPIHandlerTest.OpenAPIResponseTyp.ERROR;
+import static java.time.ZoneOffset.UTC;
 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
 import static java.util.Collections.emptyList;
 import static java.util.stream.Collectors.toSet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.when;
 
 @ExtendWith({VertxExtension.class, SystemStubsExtension.class})
 class OpenAPIHandlerTest {
@@ -2448,7 +2450,7 @@ class OpenAPIHandlerTest {
     @DisplayName("API Spec Test: campaignIdUnitIdObservationsGET")
     void campaignIdUnitIdObservationsGET_bySpec(Future<PagingRetrieve<List<UnitTelemetry>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
 
-        Mockito.when(repo.findObservationsByCampaignIdAndUnitIdWithPaging(anyLong(), anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+        Mockito.when(repo.findObservationsByCampaignIdAndUnitIdWithPaging(anyLong(), anyLong(), any(), any(), anyInt(), anyInt(), any(), any())).thenReturn(dbFuture);
 
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdObservationsGET");
         VISITED_TEST_NODES.add(operation.getOperationId());
@@ -2877,4 +2879,59 @@ class OpenAPIHandlerTest {
                     testContext.completeNow();
                 })));
     }
+
+    @Test
+    @DisplayName("API Spec Test: legacyInsertObservationsGET")
+    void legacyInsertObservationsGET_bySpec(Vertx vertx, VertxTestContext testContext) {
+
+        final long staticSensorId = 10L;
+        final long sensorId = 1001L;
+        final double sensorValue = 4.5;
+        final String sensorTimestamp = "2024-01-01T15:30:10Z";
+        final OffsetDateTime unitTimestamp = OffsetDateTime.of(2024, 1, 1, 15, 30, 10, 0, UTC);
+        final Location lastMobileUnitLocation = Location.of(50f, 49f, 350f, 0f);
+
+        Mockito.when(repo.findMobileUnitByStaticUnitIdAndTimestamp(anyLong(), any())).thenReturn(Future.succeededFuture(
+                StaticUnitBasicInfo.of(staticSensorId, 11L, 1L)
+        ));
+
+        Mockito.when(repo.findObservationsByCampaignIdAndUnitId(anyLong(), anyLong(), any(), any(), anyInt(), anyInt(), any(), any()))
+                .thenReturn(Future.succeededFuture(
+                    List.of(UnitTelemetry.of(1L, 11L, unitTimestamp, lastMobileUnitLocation, 0, JsonObject.of()))
+                ));
+
+        ArgumentCaptor<UnitTelemetry> obsTelemetryCapture = ArgumentCaptor.forClass(UnitTelemetry.class);
+
+        when(repo.saveTelemetry(obsTelemetryCapture.capture()))
+                .thenReturn(Future.succeededFuture(UnitTelemetry.of(
+                        1L,
+                        staticSensorId,
+                        unitTimestamp,
+                        lastMobileUnitLocation,
+                        0,
+                        JsonObject.of(Long.toString(sensorId), sensorValue)
+                )));
+
+        Operation operation = OPEN_API.getOperationById("legacyInsertObservationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(serverPort).setHost(HOST)
+                .setURI(String.format("/legacy/observations?Operation=InsertObservation&value=%.2f&unit_id=%d&sensor_id=%d&date=%s", sensorValue, staticSensorId, sensorId, sensorTimestamp));
+
+        vertx.createHttpClient().request(reqOpt)
+                .compose(req -> req.send().compose(HttpClientResponse::body))
+                .onComplete(testContext.succeeding(b -> testContext.verify(() -> {
+                    assertThat(b.toString()).isEqualToIgnoringCase("true");
+
+                    UnitTelemetry savedTelemetry = obsTelemetryCapture.getValue();
+                    assertThat(savedTelemetry.getUnitId()).isEqualTo(staticSensorId);
+                    assertThat(savedTelemetry.getTimestamp()).isEqualTo(unitTimestamp);
+                    assertThat(savedTelemetry.getLocation()).isEqualTo(lastMobileUnitLocation);
+                    assertThat(savedTelemetry.getSpeed()).isEqualTo(0);
+                    assertThat(savedTelemetry.getObservedValues()).isEqualTo(JsonObject.of(Long.toString(sensorId), sensorValue));
+
+                    testContext.completeNow();
+                })));
+    }
 }