From 9d1f53bb1187061e19e9c5e846624415b2c083ab Mon Sep 17 00:00:00 2001
From: Rodrigo Goncalves <rodrigo.g@ufsc.br>
Date: Tue, 27 Aug 2024 14:14:23 +0000
Subject: [PATCH] Routes for update SmartDataContext

---
 context/api/controller/crud.php              |  86 ++++++++++++-
 context/api/persistence/smartdatacontext.php |  30 +++++
 context/api/routes.php                       |   2 +
 context/api/swagger/openapi.yaml             | 123 +++++++++++++++++++
 context/tests/CrudTest.php                   |  77 +++++++++---
 5 files changed, 301 insertions(+), 17 deletions(-)

diff --git a/context/api/controller/crud.php b/context/api/controller/crud.php
index e7a262a..8c0c197 100644
--- a/context/api/controller/crud.php
+++ b/context/api/controller/crud.php
@@ -76,7 +76,10 @@ class Crud {
             return $this->return_error($response, $errors, 400);
         }
 
-        $smartdatacontextids = $this->_get_value_or_default($body, 'smartDataContextIds', []);
+        $smartdatacontextids = $this->_get_value_or_default($body, 'smartDataContextIds', null);
+        if (! isset($smartdatacontextids)) {
+            return $this->return_error($response, ['Missing smartDataContextIds'], 400);
+        }
         if (! is_array($smartdatacontextids)) {
             $smartdatacontextids = [$smartdatacontextids];
         }
@@ -106,7 +109,10 @@ class Crud {
             return $this->return_error($response, $errors, 400);
         }
 
-        $smartdatacontextids = $this->_get_value_or_default($body, 'smartDataContextIds', []);
+        $smartdatacontextids = $this->_get_value_or_default($body, 'smartDataContextIds', null);
+        if (! isset($smartdatacontextids)) {
+            return $this->return_error($response, ['Missing smartDataContextIds'], 400);
+        }
         if (! is_array($smartdatacontextids)) {
             $smartdatacontextids = [$smartdatacontextids];
         }
@@ -246,4 +252,80 @@ class Crud {
             return $body[$key];
         }
     }
+
+    function update(Request $request, Response $response, array $params) {
+        // Basic validation
+        [$domain, $body, $errors] = $this->_basicValidation($params, $request, $response);
+        if ($errors) {
+            return $this->return_error($response, $errors, 400);
+        }
+
+        $id = $this->_get_value_or_default($params, 'id', null);
+        if (!$id) {
+            return $this->return_error($response, ["missing SmartDataContextID"], 400);
+        }
+
+        // Validate properties only present in the request body
+        $updateData = [];
+        if (isset($body['content'])) {
+            $contentErrors = SmartDataContext::ValidateContent($body['content']);
+            if ($contentErrors) {
+                $errors = array_merge($errors, $contentErrors);
+            }
+            $updateData['content'] = $body['content'];
+        }
+
+        if (isset($body['features'])) {
+            $featuresErrors = SmartDataContext::ValidateFeatures($body['features']);
+            if ($featuresErrors) {
+                $errors = array_merge($errors, $featuresErrors);
+            }
+            $updateData['features'] = $body['features'];
+        }
+
+        $t0Set = isset($body['t0']);
+        $t1Set = isset($body['t1']);
+        if ($t0Set && $t1Set) {
+            $timestampErrors = SmartDataContext::ValidateTimestamp($body['t0'], $body['t1']);
+            if ($timestampErrors) {
+                $errors = array_merge($errors, $timestampErrors);
+            }
+            $updateData['t0'] = $body['t0'];
+            $updateData['t1'] = $body['t1'];
+        } elseif ($t0Set || $t1Set) {
+            // Error if only one of t0 or t1 is set
+            $errors[] = "Both t0 and t1 must be provided together.";
+        }
+
+        if (isset($body['smartDataSources'])) {
+            $smartDataSourcesErrors = SmartDataContext::ValidateSmartDataSources($body['smartDataSources']);
+            if ($smartDataSourcesErrors) {
+                $errors = array_merge($errors, $smartDataSourcesErrors);
+            }
+            $updateData['smartDataSources'] = $body['smartDataSources'];
+        }
+
+        if (isset($body['smartDataUnits'])) {
+            $smartDataUnitsErrors = SmartDataContext::ValidateSmartDataUnits($body['smartDataUnits']);
+            if ($smartDataUnitsErrors) {
+                $errors = array_merge($errors, $smartDataUnitsErrors);
+            }
+            $updateData['smartDataUnits'] = $body['smartDataUnits'];
+        }
+
+        if ($errors) {
+            return $this->return_error($response, $errors, 400);
+        }
+
+        // Call the update method from SmartDataContextCrud
+        $persistence = new SmartDataContextCrud($this->_get_db());
+        $result = $persistence->update($domain, $id, $updateData);
+
+        if ($result['success']) {
+            return $this->return_json($response, $result['contents']);
+        } else {
+            return $this->return_error($response, $result['errors'], 400);
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/context/api/persistence/smartdatacontext.php b/context/api/persistence/smartdatacontext.php
index 5b433e1..9db8324 100644
--- a/context/api/persistence/smartdatacontext.php
+++ b/context/api/persistence/smartdatacontext.php
@@ -196,4 +196,34 @@ class SmartDataContextCrud {
             return ["success" => false, "errors" => [$exception->getMessage()]];
         }
     }
+
+    public function update($domain, $id, array $updateData): array {
+        // Find the document in the MongoDB collection using the id field value
+        $collection = $this->getCollection($domain);
+        $document = $collection->findOne(["id" => $id]);
+
+        // Check if the document exists
+        if ($document === null) {
+            return ["success" => false, "errors" => ["SmartDataContext with id $id not found"]];
+        }
+
+        // Update the document's properties using the associative array received
+        $updateFields = [];
+        foreach ($updateData as $key => $value) {
+            $updateFields[$key] = $value;
+        }
+
+        // Prepare the update query
+        $updateQuery = ['$set' => $updateFields];
+
+        // Update the document in the MongoDB collection
+        try {
+            $collection->updateOne(["id" => $id], $updateQuery);
+            $updatedDocument = $collection->findOne(["id" => $id]);
+            return ["success" => true, "contents" => json_decode(json_encode($updatedDocument), false)];
+        } catch (Exception $e) {
+            return ["success" => false, "errors" => [$e->getMessage()]];
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/context/api/routes.php b/context/api/routes.php
index 06f6dbe..1b61cd3 100644
--- a/context/api/routes.php
+++ b/context/api/routes.php
@@ -12,6 +12,8 @@ function setupRoutes(App $app) {
     $app->post('/query/{domain}', '\SmartDataContext\Controller\Crud:query');
     $app->post('/contexts/{domain}', '\SmartDataContext\Controller\Crud:contexts');
 
+    $app->post('/update/{domain}/{id}', '\SmartDataContext\Controller\Crud:update');
+
     # Documentation
     $app->get('/docs', function ($request, Response $response) {
         $response->getBody()->write(file_get_contents(__DIR__ . '/swagger/swagger-ui.html'));
diff --git a/context/api/swagger/openapi.yaml b/context/api/swagger/openapi.yaml
index 24ca9ee..4803beb 100644
--- a/context/api/swagger/openapi.yaml
+++ b/context/api/swagger/openapi.yaml
@@ -487,3 +487,126 @@ paths:
                     items:
                       type: string
                     example: ["Invalid id"]
+  /api/v1_1/context.php#update:
+    post:
+      summary: Updates an existing SmartDataContext
+      description: Updates the properties of an existing SmartDataContext using the provided ID and request body.
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                command:
+                  type: string
+                  description: The command to update a SmartDataContext, including the ID.
+                  example: "/update/88690087-a4f4-4e1d-8c8d-8c67d19ddaed"
+                request:
+                  type: object
+                  description: The object containing the properties to be updated in the SmartDataContext.
+                  properties:
+                    domain:
+                      type: string
+                      description: The domain of the SmartDataContext.
+                      example: "test"
+                    t0:
+                      type: integer
+                      description: Start time for the SmartDataContext.
+                      example: 1000
+                    t1:
+                      type: integer
+                      description: End time for the SmartDataContext.
+                      example: 2000
+                    content:
+                      type: object
+                      description: The content to be updated in the SmartDataContext.
+                      example: {"meta": "updated"}
+                    features:
+                      type: object
+                      description: Updated features of the SmartDataContext.
+                      properties:
+                        tags:
+                          type: array
+                          items:
+                            type: string
+                          example: ["newtag"]
+                    smartDataSources:
+                      type: array
+                      items:
+                        oneOf:
+                          - type: string
+                          - type: array
+                            items:
+                              type: integer
+                      description: The updated SmartDataSources.
+                      example: ["signature", [2, 2, 2, 2]]
+                    smartDataUnits:
+                      type: array
+                      items:
+                        type: integer
+                      description: The updated SmartDataUnits.
+                      example: [20, 22]
+              required:
+                - command
+                - request
+      responses:
+        '200':
+          description: Successfully updated SmartDataContext.
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  result:
+                    type: object
+                    properties:
+                      id:
+                        type: string
+                        example: "88690087-a4f4-4e1d-8c8d-8c67d19ddaed"
+                      t0:
+                        type: integer
+                        example: 1000
+                      t1:
+                        type: integer
+                        example: 2000
+                      content:
+                        type: object
+                        properties:
+                          meta:
+                            type: string
+                            example: "updated"
+                      features:
+                        type: object
+                        properties:
+                          tags:
+                            type: array
+                            items:
+                              type: string
+                            example: ["newtag"]
+                      smartDataSources:
+                        type: array
+                        items:
+                          oneOf:
+                            - type: string
+                            - type: array
+                              items:
+                                type: integer
+                        example: ["signature", [2, 2, 2, 2]]
+                      smartDataUnits:
+                        type: array
+                        items:
+                          type: integer
+                        example: [20, 22]
+        '400':
+          description: Invalid request due to validation errors.
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  errors:
+                    type: array
+                    items:
+                      type: string
+                    example: ["Invalid command format", "Both t0 and t1 must be set together"]
diff --git a/context/tests/CrudTest.php b/context/tests/CrudTest.php
index 278742d..68a5e4d 100644
--- a/context/tests/CrudTest.php
+++ b/context/tests/CrudTest.php
@@ -101,8 +101,8 @@ class CrudTest extends BaseTest
         $response->getBody()->rewind();
         $body = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue(array_key_exists('result', $body), 'missing result');
-        $this->assertTrue(array_key_exists('smartdataid', $body['result']), 'missing smartdataid');
-        $id = $body['result']['smartdataid'];
+        $this->assertTrue(array_key_exists('smartDataContextId', $body['result']), 'missing smartdataid');
+        $id = $body['result']['smartDataContextId'];
         $response = $this->app->handle($this->_createRequest('GET', "/get/test/$id"));
         $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
         $response->getBody()->rewind();
@@ -120,10 +120,10 @@ class CrudTest extends BaseTest
         $response->getBody()->rewind();
         $body = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue(array_key_exists('result', $body), 'missing result');
-        $this->assertTrue(array_key_exists('smartdataid', $body['result']), 'missing smartdataid');
-        $id = $body['result']['smartdataid'];
+        $this->assertTrue(array_key_exists('smartDataContextId', $body['result']), 'missing smartdataid');
+        $id = $body['result']['smartDataContextId'];
 
-        $body = ["smartdataids" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
+        $body = ["smartDataContextIds" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
         $response = $this->app->handle($this->_createRequest('POST', '/associate/test', $body));
         $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
         $response->getBody()->rewind();
@@ -141,10 +141,10 @@ class CrudTest extends BaseTest
         $response->getBody()->rewind();
         $body = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue(array_key_exists('result', $body), 'missing result');
-        $this->assertTrue(array_key_exists('smartdataid', $body['result']), 'missing smartdataid');
-        $id = $body['result']['smartdataid'];
+        $this->assertTrue(array_key_exists('smartDataContextId', $body['result']), 'missing smartdataid');
+        $id = $body['result']['smartDataContextId'];
 
-        $body = ["smartdataids" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
+        $body = ["smartDataContextIds" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
         $response = $this->app->handle($this->_createRequest('POST', '/associate/test', $body));
         $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
         $response->getBody()->rewind();
@@ -152,7 +152,7 @@ class CrudTest extends BaseTest
         $this->assertEquals(4, count($body['result'][0]['result']['smartDataSources']), "Wrong number of smartDataSources associated");
         $this->assertEquals(2, count($body['result'][0]['result']['smartDataUnits']), "Wrong number of smartDataUnits associated");
 
-        $body = ["smartdataids" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7]], "smartDataUnits" => [222]];
+        $body = ["smartDataContextIds" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7]], "smartDataUnits" => [222]];
         $response = $this->app->handle($this->_createRequest('POST', '/unassociate/test', $body));
         $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
         $response->getBody()->rewind();
@@ -169,10 +169,10 @@ class CrudTest extends BaseTest
         $response->getBody()->rewind();
         $body = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue(array_key_exists('result', $body), 'missing result');
-        $this->assertTrue(array_key_exists('smartdataid', $body['result']), 'missing smartdataid');
-        $id = $body['result']['smartdataid'];
+        $this->assertTrue(array_key_exists('smartDataContextId', $body['result']), 'missing smartdataid');
+        $id = $body['result']['smartDataContextId'];
 
-        $body = ["smartdataids" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
+        $body = ["smartDataContextIds" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
         $response = $this->app->handle($this->_createRequest('POST', '/associate/test', $body));
         $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
         $response->getBody()->rewind();
@@ -198,10 +198,10 @@ class CrudTest extends BaseTest
         $response->getBody()->rewind();
         $body = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue(array_key_exists('result', $body), 'missing result');
-        $this->assertTrue(array_key_exists('smartdataid', $body['result']), 'missing smartdataid');
-        $id = $body['result']['smartdataid'];
+        $this->assertTrue(array_key_exists('smartDataContextId', $body['result']), 'missing smartdataid');
+        $id = $body['result']['smartDataContextId'];
 
-        $body = ["smartdataids" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
+        $body = ["smartDataContextIds" => [$id], "smartDataSources" => ["ae7666", [4,5,6,7], [1,2,3,5]], "smartDataUnits" => [111,222]];
         $response = $this->app->handle($this->_createRequest('POST', "/associate/$domain", $body));
         $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
         $response->getBody()->rewind();
@@ -217,4 +217,51 @@ class CrudTest extends BaseTest
         $body = json_decode($response->getBody()->getContents(), true);
         $this->assertGreaterThan(0, count($body['result']), "Wrong number of results");
     }
+
+
+    public function testUpdateWithTimestampsAndContent() {
+        // Step 1: Create a document
+        $body = ["content" => ["meta" => true], "features" => ["tags" => "sim"], "smartDataUnits" => [10, 12]];
+        $response = $this->app->handle($this->_createRequest('POST', '/create/test', $body));
+
+        $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
+        $response->getBody()->rewind();
+        $body = json_decode($response->getBody()->getContents(), true);
+        $this->assertTrue(array_key_exists('result', $body), 'missing result');
+        $this->assertTrue(array_key_exists('smartDataContextId', $body['result']), 'missing smartDataContextId');
+        $id = $body['result']['smartDataContextId'];
+
+        // Step 2: Update the document with t0 and t1
+        $updateBody = ["t0" => 1000, "t1" => 2000];
+        $response = $this->app->handle($this->_createRequest('POST', "/update/test/$id", $updateBody));
+
+        $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
+        $response->getBody()->rewind();
+        $updatedBody = json_decode($response->getBody()->getContents(), true);
+        $this->assertTrue(array_key_exists('result', $updatedBody), 'missing result');
+        $this->assertEquals($updatedBody['result']['t0'], 1000, 't0 was not updated correctly');
+        $this->assertEquals($updatedBody['result']['t1'], 2000, 't1 was not updated correctly');
+
+        // Step 3: Update the document with new content
+        $newContent = ["content" => ["meta" => "updated"]];
+        $response = $this->app->handle($this->_createRequest('POST', "/update/test/$id", $newContent));
+
+        $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
+        $response->getBody()->rewind();
+        $updatedContentBody = json_decode($response->getBody()->getContents(), true);
+        $this->assertTrue(array_key_exists('result', $updatedContentBody), 'missing result');
+        $this->assertEquals($updatedContentBody['result']['content']['meta'], "updated", 'content was not updated correctly');
+
+        // Step 4: Get the updated document and verify
+        $response = $this->app->handle($this->_createRequest('GET', "/get/test/$id"));
+        $this->assertEquals(200, $response->getStatusCode(), json_encode($response->getBody()));
+        $response->getBody()->rewind();
+        $finalBody = json_decode($response->getBody()->getContents(), true);
+        $this->assertTrue(array_key_exists('result', $finalBody), 'missing result');
+        $this->assertEquals($finalBody['result']['id'], $id, 'wrong smartdataid');
+        $this->assertEquals($finalBody['result']['t0'], 1000, 't0 mismatch');
+        $this->assertEquals($finalBody['result']['t1'], 2000, 't1 mismatch');
+        $this->assertEquals($finalBody['result']['content']['meta'], "updated", 'content meta mismatch');
+    }
+
 }
-- 
GitLab