From 40fad4eb9ba614d4ec5e382d9aa8a14564e5574e Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Sun, 7 Apr 2019 01:55:13 +0530 Subject: [PATCH 1/4] Test cases : AUthentication - Auth, Forgot Password, Collections - Create, Delete --- tests/api/AuthTest.php | 55 ++++++++++++ tests/api/CollectionTest.php | 156 +++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 tests/api/AuthTest.php create mode 100644 tests/api/CollectionTest.php diff --git a/tests/api/AuthTest.php b/tests/api/AuthTest.php new file mode 100644 index 0000000000..9e590b6a23 --- /dev/null +++ b/tests/api/AuthTest.php @@ -0,0 +1,55 @@ +http = new GuzzleHttp\Client([ + 'base_uri' => 'http://localhost/directus-api/public/_/', + 'exceptions' => false + ]); + } + + public function tearDown() { + $this->http = null; + } + + public function testAuthentication() + { + + $data = [ + 'form_params' => [ + 'email' => "admin@example.com", + 'password' => "password" + ] + ]; + + $response = $this->http->request('POST', 'auth/authenticate', $data); + + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($response->getBody(true), true); + $this->assertArrayHasKey('token', $data['data']); + $this->assertTrue(!empty($data['data']['token'])); + + } + + public function testForgotPassword() + { + + $data = [ + 'form_params' => [ + 'email' => "admin@example.com" + ] + ]; + + $response = $this->http->request('POST', 'auth/password/request', $data); + + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($response->getBody(true), true); + $this->assertTrue($data['public']); + + } +} \ No newline at end of file diff --git a/tests/api/CollectionTest.php b/tests/api/CollectionTest.php new file mode 100644 index 0000000000..c97cf7f681 --- /dev/null +++ b/tests/api/CollectionTest.php @@ -0,0 +1,156 @@ +http = new GuzzleHttp\Client([ + 'base_uri' => 'http://localhost/directus-api/public/_/', + 'exceptions' => false + ]); + + //Get token + $data = [ + 'form_params' => [ + 'email' => "admin@example.com", + 'password' => "password" + ] + ]; + + $response = $this->http->request('POST', 'auth/authenticate', $data); + $data = json_decode($response->getBody(true), true); + $this->token = $data['data']['token']; + + } + + public function tearDown() { + $this->http = null; + } + + public function testCreateCollection() + { + $data = [ + 'headers' => ['Authorization' => 'bearer '.$this->token], + 'form_params' => json_decode('{ + "collection": "test111_collection", + "hidden": 0, + "fields": [{ + "type": "integer", + "datatype": "INT", + "length": 15, + "field": "id", + "interface": "primary-key", + "auto_increment": true, + "primary_key": true, + "hidden_detail": true, + "hidden_browse": true + }, { + "type": "status", + "datatype": "VARCHAR", + "length": 20, + "field": "status", + "interface": "status", + "options": { + "status_mapping": { + "published": { + "name": "Published", + "text_color": "white", + "background_color": "accent", + "browse_subdued": false, + "browse_badge": true, + "soft_delete": false, + "published": true + }, + "draft": { + "name": "Draft", + "text_color": "white", + "background_color": "blue-grey-200", + "browse_subdued": true, + "browse_badge": true, + "soft_delete": false, + "published": false + }, + "deleted": { + "name": "Deleted", + "text_color": "white", + "background_color": "red", + "browse_subdued": true, + "browse_badge": true, + "soft_delete": true, + "published": false + } + } + } + }, { + "type": "sort", + "datatype": "INT", + "field": "sort", + "interface": "sort" + }, { + "type": "user_created", + "datatype": "INT", + "field": "created_by", + "interface": "user-created", + "options": { + "template": "{{first_name}} {{last_name}}", + "display": "both" + }, + "readonly": true, + "hidden_detail": true, + "hidden_browse": true + }, { + "type": "datetime_created", + "datatype": "DATETIME", + "field": "created_on", + "interface": "datetime-created", + "readonly": true, + "hidden_detail": true, + "hidden_browse": true + }, { + "type": "user_updated", + "datatype": "INT", + "field": "modified_by", + "interface": "user-updated", + "options": { + "template": "{{first_name}} {{last_name}}", + "display": "both" + }, + "readonly": true, + "hidden_detail": true, + "hidden_browse": true + }, { + "type": "datetime_updated", + "datatype": "DATETIME", + "field": "modified_on", + "interface": "datetime-updated", + "readonly": true, + "hidden_detail": true, + "hidden_browse": true + }] + }',true) + ]; + + //echo "
";
+        //print_r($data);exit;
+        $response = $this->http->request('POST', 'collections', $data);
+        $this->assertEquals(200, $response->getStatusCode());
+        $data = json_decode($response->getBody(true), true);
+        $this->assertArrayHasKey('collection', $data['data']);
+        $this->assertTrue(!empty($data['data']['collection']));
+    }
+    
+    public function testDeleteCollection()
+    {
+        $data = [
+            'headers' => ['Authorization' => 'bearer '.$this->token],
+        ];
+        
+        $response = $this->http->request('DELETE', 'collections/test111_collection', $data);
+        
+        $this->assertEquals(204, $response->getStatusCode());        
+    }
+    
+}
\ No newline at end of file

From 47bf851d7cab1050be5e98e57fe32c952e156bce Mon Sep 17 00:00:00 2001
From: Rahul Patel 
Date: Fri, 3 May 2019 02:17:09 +0530
Subject: [PATCH 2/4] #576 - In progress

---
 src/core/Directus/Database/SchemaService.php  |   6 +-
 .../TableGateway/RelationalTableGateway.php   |  27 ++-
 tests/api/AuthTest.php                        |  55 ------
 tests/api/CollectionTest.php                  | 156 ------------------
 4 files changed, 28 insertions(+), 216 deletions(-)
 delete mode 100644 tests/api/AuthTest.php
 delete mode 100644 tests/api/CollectionTest.php

diff --git a/src/core/Directus/Database/SchemaService.php b/src/core/Directus/Database/SchemaService.php
index f14d889a82..e86e8ef779 100644
--- a/src/core/Directus/Database/SchemaService.php
+++ b/src/core/Directus/Database/SchemaService.php
@@ -352,8 +352,10 @@ public static function getRelatedCollectionName($tableName, $columnName)
 
         $tableObject = static::getCollection($tableName);
         $columnObject = $tableObject->getField($columnName);
-
-        return $columnObject->getRelationship()->getCollectionOne();
+        if($columnObject->getRelationship()->getType() == FieldRelationship::ONE_TO_MANY)
+            return $columnObject->getRelationship()->getCollectionMany();
+        else
+            return $columnObject->getRelationship()->getCollectionOne();
     }
 
     /**
diff --git a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
index a177336285..83f64d0239 100644
--- a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
+++ b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
@@ -1246,7 +1246,28 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
 
             $query = new Builder($this->getAdapter());
             $mainTableObject = $this->getTableSchema($table);
-            $query->columns([$mainTableObject->getPrimaryField()->getName()]);
+            $selectColumn = $mainTableObject->getPrimaryField()->getName();
+            
+            //check if column type is alias and relationship is O2M
+            $collection = $this->getTableSchema($mainTable);
+            $field = $collection->getField($mainColumn);
+            
+            if ($field->isAlias() && $field->getRelationship()->getType() == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
+                $mainColumn = $mainTableObject->getPrimaryField()->getName();
+                //$column = $collection->getPrimaryField()->getName();
+                $relationalTable = $field->getRelationship()->getCollectionMany();
+                $relationalCollection = $this->getTableSchema($relationalTable);
+                $relationFields = $relationalCollection->getRelationalFieldsName();
+                foreach($relationFields as $relationField){
+                    $fieldObject = $relationalCollection->getField($relationField);
+                    if($fieldObject->getRelationship()->getType() == \Directus\Database\Schema\Object\FieldRelationship::MANY_TO_ONE && $fieldObject->getRelationship()->getCollectionOne() == $mainTable){
+                        $selectColumn = $relationField;
+                    }
+                }
+            }
+            
+            $query->columns([$selectColumn]);
+            
             $query->from($table);
 
             $this->doFilter($query, $column, $condition, $table);
@@ -1279,7 +1300,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
             // TODO: Make all this whereIn duplication into a function
             // TODO: Can we make the O2M simpler getting the parent id from itself
             //       right now is creating one unnecessary select
-            if ($field->isOneToMany()) {
+            /*if ($field->isOneToMany()) {
                 $mainColumn = $collection->getPrimaryField()->getName();
                 $oldQuery = $query;
                 $query = new Builder($this->getAdapter());
@@ -1292,7 +1313,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
                     $column,
                     $oldQuery
                 );
-            }
+            }*/
 
             $this->doFilter(
                 $mainQuery,
diff --git a/tests/api/AuthTest.php b/tests/api/AuthTest.php
deleted file mode 100644
index 9e590b6a23..0000000000
--- a/tests/api/AuthTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-http = new GuzzleHttp\Client([
-            'base_uri' => 'http://localhost/directus-api/public/_/',
-            'exceptions' => false
-        ]);
-    }
-
-    public function tearDown() {
-        $this->http = null;
-    }
-    
-    public function testAuthentication()
-    {
-
-        $data = [
-            'form_params' => [
-                'email' => "admin@example.com",
-                'password' => "password"
-            ]
-        ];
-        
-        $response = $this->http->request('POST', 'auth/authenticate', $data);
-        
-        $this->assertEquals(200, $response->getStatusCode());
-        $data = json_decode($response->getBody(true), true);
-        $this->assertArrayHasKey('token', $data['data']);
-        $this->assertTrue(!empty($data['data']['token']));
-        
-    }
-    
-    public function testForgotPassword()
-    {
-
-        $data = [
-            'form_params' => [
-                'email' => "admin@example.com"
-            ]
-        ];
-        
-        $response = $this->http->request('POST', 'auth/password/request', $data);
-        
-        $this->assertEquals(200, $response->getStatusCode());
-        $data = json_decode($response->getBody(true), true);
-        $this->assertTrue($data['public']);
-        
-    }
-}
\ No newline at end of file
diff --git a/tests/api/CollectionTest.php b/tests/api/CollectionTest.php
deleted file mode 100644
index c97cf7f681..0000000000
--- a/tests/api/CollectionTest.php
+++ /dev/null
@@ -1,156 +0,0 @@
-http = new GuzzleHttp\Client([
-            'base_uri' => 'http://localhost/directus-api/public/_/',
-            'exceptions' => false
-        ]);
-        
-        //Get token
-        $data = [
-            'form_params' => [
-                'email' => "admin@example.com",
-                'password' => "password"
-            ]
-        ];
-        
-        $response = $this->http->request('POST', 'auth/authenticate', $data);
-        $data = json_decode($response->getBody(true), true);
-        $this->token = $data['data']['token'];
-        
-    }
-
-    public function tearDown() {
-        $this->http = null;
-    }
-    
-    public function testCreateCollection()
-    {
-        $data = [
-            'headers' => ['Authorization' => 'bearer '.$this->token],
-            'form_params' => json_decode('{
-                    "collection": "test111_collection",
-                    "hidden": 0,
-                    "fields": [{
-                        "type": "integer",
-                        "datatype": "INT",
-                        "length": 15,
-                        "field": "id",
-                        "interface": "primary-key",
-                        "auto_increment": true,
-                        "primary_key": true,
-                        "hidden_detail": true,
-                        "hidden_browse": true
-                    }, {
-                        "type": "status",
-                        "datatype": "VARCHAR",
-                        "length": 20,
-                        "field": "status",
-                        "interface": "status",
-                        "options": {
-                            "status_mapping": {
-                                "published": {
-                                    "name": "Published",
-                                    "text_color": "white",
-                                    "background_color": "accent",
-                                    "browse_subdued": false,
-                                    "browse_badge": true,
-                                    "soft_delete": false,
-                                    "published": true
-                                },
-                                "draft": {
-                                    "name": "Draft",
-                                    "text_color": "white",
-                                    "background_color": "blue-grey-200",
-                                    "browse_subdued": true,
-                                    "browse_badge": true,
-                                    "soft_delete": false,
-                                    "published": false
-                                },
-                                "deleted": {
-                                    "name": "Deleted",
-                                    "text_color": "white",
-                                    "background_color": "red",
-                                    "browse_subdued": true,
-                                    "browse_badge": true,
-                                    "soft_delete": true,
-                                    "published": false
-                                }
-                            }
-                        }
-                    }, {
-                        "type": "sort",
-                        "datatype": "INT",
-                        "field": "sort",
-                        "interface": "sort"
-                    }, {
-                        "type": "user_created",
-                        "datatype": "INT",
-                        "field": "created_by",
-                        "interface": "user-created",
-                        "options": {
-                                "template": "{{first_name}} {{last_name}}",
-                                "display": "both"
-                        },
-                        "readonly": true,
-                        "hidden_detail": true,
-                        "hidden_browse": true
-                    }, {
-                        "type": "datetime_created",
-                        "datatype": "DATETIME",
-                        "field": "created_on",
-                        "interface": "datetime-created",
-                        "readonly": true,
-                        "hidden_detail": true,
-                        "hidden_browse": true
-                    }, {
-                        "type": "user_updated",
-                        "datatype": "INT",
-                        "field": "modified_by",
-                        "interface": "user-updated",
-                        "options": {
-                            "template": "{{first_name}} {{last_name}}",
-                            "display": "both"
-                        },
-                        "readonly": true,
-                        "hidden_detail": true,
-                        "hidden_browse": true
-                    }, {
-                        "type": "datetime_updated",
-                        "datatype": "DATETIME",
-                        "field": "modified_on",
-                        "interface": "datetime-updated",
-                        "readonly": true,
-                        "hidden_detail": true,
-                        "hidden_browse": true
-                    }]
-                }',true)
-        ];
-        
-        //echo "
";
-        //print_r($data);exit;
-        $response = $this->http->request('POST', 'collections', $data);
-        $this->assertEquals(200, $response->getStatusCode());
-        $data = json_decode($response->getBody(true), true);
-        $this->assertArrayHasKey('collection', $data['data']);
-        $this->assertTrue(!empty($data['data']['collection']));
-    }
-    
-    public function testDeleteCollection()
-    {
-        $data = [
-            'headers' => ['Authorization' => 'bearer '.$this->token],
-        ];
-        
-        $response = $this->http->request('DELETE', 'collections/test111_collection', $data);
-        
-        $this->assertEquals(204, $response->getStatusCode());        
-    }
-    
-}
\ No newline at end of file

From 4587d344db4aee3b2a88a8bc4e816ec8282b28d5 Mon Sep 17 00:00:00 2001
From: Rahul Patel 
Date: Sat, 4 May 2019 22:28:38 +0530
Subject: [PATCH 3/4] #576 O2M and M20 nested filters

---
 .../TableGateway/RelationalTableGateway.php   | 56 ++++++++++++-------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
index 83f64d0239..b13edef761 100644
--- a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
+++ b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
@@ -1117,7 +1117,6 @@ public function fetchItems(array $params = [], \Closure $queryCallback = null)
         if (ArrayUtils::get($params, 'single')) {
             $results = reset($results);
         }
-
         return $results ? $results : [];
     }
 
@@ -1198,7 +1197,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
                 continue;
             }
 
-            $columnList = $columns = explode('.', $column);
+            $columnList = $filterColumns = explode('.', $column);
             $columnsTable = [
                 $this->getTable()
             ];
@@ -1206,11 +1205,13 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
             $nextColumn = array_shift($columnList);
             $nextTable = $this->getTable();
             $relational = SchemaService::hasRelationship($nextTable, $nextColumn);
-
+            $relationalTables = [];
             while ($relational) {
+                $relationalTables[$nextColumn] = $nextTable;
                 $nextTable = SchemaService::getRelatedCollectionName($nextTable, $nextColumn);
                 $nextColumn = array_shift($columnList);
-
+                if(empty($nextColumn))
+                    break;
                 // Confirm the user has permission to all chained (dot) fields
                 if ($this->acl && !$this->acl->canRead($nextTable)) {
                     throw new Exception\ForbiddenFieldAccessException($nextColumn);
@@ -1219,7 +1220,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
                 $relational = SchemaService::hasRelationship($nextTable, $nextColumn);
                 $columnsTable[] = $nextTable;
             }
-
+            
             // if one of the column in the list has not relationship
             // it will break the loop before going over all the columns
             // which we will call this as column not found
@@ -1230,7 +1231,23 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
 
             // Remove the original filter column with dot-notation
             unset($filters[$column]);
-
+            
+            //Prepare relational data for all the fields
+            $columnRelationalData = [];
+            foreach($filterColumns as $filterColumn){
+                if(isset($relationalTables[$filterColumn])){
+                    $collection = $this->getTableSchema($relationalTables[$filterColumn]);
+                    $fieldRelation = $collection->getField($filterColumn)->getRelationship();
+                    $columnRelationalData[$filterColumn] = [
+                        "type" => $fieldRelation->getType(),
+                        "collection_many" => $fieldRelation->getCollectionMany(),
+                        "field_many" => $fieldRelation->getFieldMany(),
+                        "collection_one" => $fieldRelation->getCollectionOne(),
+                        "field_one" => $fieldRelation->getFieldOne()
+                    ];
+                }
+            }
+            
             // Reverse all the columns from comments.author.id to id.author.comments
             // To filter from the most deep relationship to their parents
             $columns = explode('.', \Directus\column_identifier_reverse($column));
@@ -1249,29 +1266,21 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
             $selectColumn = $mainTableObject->getPrimaryField()->getName();
             
             //check if column type is alias and relationship is O2M
-            $collection = $this->getTableSchema($mainTable);
-            $field = $collection->getField($mainColumn);
+            $previousRelation = isset($filterColumns[array_search($column, $filterColumns)-1])?$filterColumns[array_search($column, $filterColumns)-1]:'';
+            if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {                
+                $selectColumn = $columnRelationalData[$previousRelation]['field_many'];
+            }
             
-            if ($field->isAlias() && $field->getRelationship()->getType() == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
+            //get last relationship
+            $lastRelationShip = !empty($columns) ? end($columns) : $column;
+            if ($lastRelationShip && $columnRelationalData[$lastRelationShip]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
                 $mainColumn = $mainTableObject->getPrimaryField()->getName();
-                //$column = $collection->getPrimaryField()->getName();
-                $relationalTable = $field->getRelationship()->getCollectionMany();
-                $relationalCollection = $this->getTableSchema($relationalTable);
-                $relationFields = $relationalCollection->getRelationalFieldsName();
-                foreach($relationFields as $relationField){
-                    $fieldObject = $relationalCollection->getField($relationField);
-                    if($fieldObject->getRelationship()->getType() == \Directus\Database\Schema\Object\FieldRelationship::MANY_TO_ONE && $fieldObject->getRelationship()->getCollectionOne() == $mainTable){
-                        $selectColumn = $relationField;
-                    }
-                }
             }
-            
             $query->columns([$selectColumn]);
             
             $query->from($table);
 
             $this->doFilter($query, $column, $condition, $table);
-
             $index = 0;
             foreach ($columns as $key => $column) {
                 ++$index;
@@ -1282,6 +1291,11 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
                 $field = $collection->getField($column);
 
                 $selectColumn = $collection->getPrimaryField()->getName();
+                //check if column type is alias and relationship is O2M
+                $previousRelation = isset($filterColumns[array_search($column, $filterColumns)-1])?$filterColumns[array_search($column, $filterColumns)-1]:'';
+                if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
+                    $selectColumn = $columnRelationalData[$previousRelation]['field_many'];
+                }
                 $table = $columnsTable[$key];
 
                 if ($field->isAlias()) {

From 2032078a6acb35db1a0c96e144b68eb147c92ec7 Mon Sep 17 00:00:00 2001
From: Rahul Patel 
Date: Sun, 5 May 2019 00:43:44 +0530
Subject: [PATCH 4/4] #576 Fix O2M and M2O nested filters

---
 .../Directus/Database/TableGateway/RelationalTableGateway.php  | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
index b13edef761..cee98b6f23 100644
--- a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
+++ b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php
@@ -1272,8 +1272,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
             }
             
             //get last relationship
-            $lastRelationShip = !empty($columns) ? end($columns) : $column;
-            if ($lastRelationShip && $columnRelationalData[$lastRelationShip]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
+            if ($mainColumn && !empty($mainColumn) && $columnRelationalData[$mainColumn]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
                 $mainColumn = $mainTableObject->getPrimaryField()->getName();
             }
             $query->columns([$selectColumn]);