Introduce API v6

Message ID 20190212233846.16002-1-kevr.gtalk@gmail.com
State New
Headers show
Series
  • Introduce API v6
Related show

Commit Message

Kevin Morris Feb. 12, 2019, 11:38 p.m. UTC
Add new method of multiple by[] search in API v6.
The 'provides' by field is also supported in the new
multiple by[] method included with this patch.

'provides' may not be used in legacy (v5) singular by search.
A helpful json error message is returned to the user to indicate so.

This patch is a follow-up implementation to https://patchwork.archlinux.org/patch/479/

Signed-off-by: Kevin Morris <kevr.gtalk@gmail.com>
---
 doc/rpc.txt               |  24 ++++++---
 web/lib/aurjson.class.php | 105 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 116 insertions(+), 13 deletions(-)

Patch

diff --git a/doc/rpc.txt b/doc/rpc.txt
index 3148ebe..486a49d 100644
--- a/doc/rpc.txt
+++ b/doc/rpc.txt
@@ -5,7 +5,7 @@  Package Search
 --------------
 
 Package searches can be performed by issuing HTTP GET requests of the form
-+/rpc/?v=5&type=search&by=_field_&arg=_keywords_+ where _keywords_ is the
++/rpc/?v=6&type=search&by[]=_field_&arg[]=_keywords_+ where _keywords_ is the
 search argument and _field_ is one of the following values:
 
 * `name` (search by package name only)
@@ -15,31 +15,39 @@  search argument and _field_ is one of the following values:
 * `makedepends` (search for packages that makedepend on _keywords_)
 * `optdepends` (search for packages that optdepend on _keywords_)
 * `checkdepends` (search for packages that checkdepend on _keywords_)
+* `provides` (search by package provides; v6 multiple by[] only)
 
 The _by_ parameter can be skipped and defaults to `name-desc`.
 
 If a maintainer search is performed and the search argument is left empty, a
 list of orphan packages is returned.
 
+Note: Legacy v5 support is still enabled for singular _by_ searches.
+
 Package Details
 ---------------
 
 Package information can be obtained by issuing HTTP GET requests of the form
-+/rpc/?v=5&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ...
++/rpc/?v=6&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ...
 are the names of packages to retrieve package details for.
 
 Examples
 --------
 
 `search`::
-  `/rpc/?v=5&type=search&arg=foobar`
+  `/rpc/?v=6&type=search&arg=foobar`
 `search` by maintainer::
-  `/rpc/?v=5&type=search&by=maintainer&arg=john`
+  `/rpc/?v=6&type=search&by[]=maintainer&arg=john`
 `search` packages that have _boost_ as `makedepends`::
-  `/rpc/?v=5&type=search&by=makedepends&arg=boost`
+  `/rpc/?v=6&type=search&by[]=makedepends&arg=boost`
 `search` with callback::
-  `/rpc/?v=5&type=search&arg=foobar&callback=jsonp1192244621103`
+  `/rpc/?v=6&type=search&arg=foobar&callback=jsonp1192244621103`
+`search` by provides::
+  `/rpc/?v=6&type=search&by[]=provides&arg[]=gcc`
+`search` by provides and name::
+  `/rpc/?v=6&type=search&by[]=name&by[]=provides&arg[]=gcc`
 `info`::
-  `/rpc/?v=5&type=info&arg[]=foobar`
+  `/rpc/?v=6&type=info&arg[]=foobar`
 `info` with multiple packages::
-  `/rpc/?v=5&type=info&arg[]=foo&arg[]=bar`
+  `/rpc/?v=6&type=info&arg[]=foo&arg[]=bar`
+
diff --git a/web/lib/aurjson.class.php b/web/lib/aurjson.class.php
index c275d21..ab4eb19 100644
--- a/web/lib/aurjson.class.php
+++ b/web/lib/aurjson.class.php
@@ -80,7 +80,8 @@  class AurJSON {
 		if (isset($http_data['v'])) {
 			$this->version = intval($http_data['v']);
 		}
-		if ($this->version < 1 || $this->version > 5) {
+
+		if ($this->version < 1 || $this->version > 6) {
 			return $this->json_error('Invalid version specified.');
 		}
 
@@ -94,8 +95,14 @@  class AurJSON {
 		if (isset($http_data['search_by']) && !isset($http_data['by'])) {
 			$http_data['by'] = $http_data['search_by'];
 		}
-		if (isset($http_data['by']) && !in_array($http_data['by'], self::$exposed_fields)) {
-			return $this->json_error('Incorrect by field specified.');
+
+		if (isset($http_data['by']) && !is_array($http_data['by'])) {
+			if ($http_data['by'] === 'provides') {
+				return $this->json_error("The 'provides' by field " .
+					"may only be used via multiple by[] search.");
+			} elseif (!in_array($http_data['by'], self::$exposed_fields)) {
+				return $this->json_error('Incorrect by field specified.');
+			}
 		}
 
 		$this->dbh = DB::connect();
@@ -360,7 +367,7 @@  class AurJSON {
 		} elseif ($this->version >= 2) {
 			if ($this->version == 2 || $this->version == 3) {
 				$fields = implode(',', self::$fields_v2);
-			} else if ($this->version == 4 || $this->version == 5) {
+			} else if ($this->version == 4 || $this->version == 5 || $this->version == 6) {
 				$fields = implode(',', self::$fields_v4);
 			}
 			$query = "SELECT {$fields} " .
@@ -470,6 +477,10 @@  class AurJSON {
 	 * @return mixed Returns an array of package matches.
 	 */
 	private function search($http_data) {
+		if ($this->version == 6 && is_array($http_data['by'])) {
+			return $this->search_v6($http_data);
+		}
+
 		$keyword_string = $http_data['arg'];
 
 		if (isset($http_data['by'])) {
@@ -497,6 +508,8 @@  class AurJSON {
 				$keyword_string = $this->dbh->quote($keyword_string);
 				$where_condition = "Users.Username = $keyword_string ";
 			}
+		} else if ($search_by === 'provides') {
+			return $this->json_error("The 'provides' by field is only available via multiple by[] search.");
 		} else if (in_array($search_by, self::$exposed_depfields)) {
 			if (empty($keyword_string)) {
 				return $this->json_error('Query arg is empty.');
@@ -515,6 +528,89 @@  class AurJSON {
 		return $this->process_query('search', $where_condition);
 	}
 
+	/*
+	 * Returns multiple-by, multiple-arg and/or search information
+	 * a feature not included in the v5 and before RPC API,
+	 * which introduces the beginnings of the v6 API.
+	 *
+	 * @param array $http_data Query parameters.
+	 *
+	 * @return mixed Returns an array of results containing the package data
+	 */
+	private function search_v6($http_data) {
+		if (!is_array($http_data['arg'])) {
+			$http_data['arg'] = array($http_data['arg']);
+		}
+
+		$max_results = config_get_int('options', 'max_rpc_results');
+		$fields = implode(',', self::$fields_v4);
+
+		$query = "SELECT {$fields} FROM " .
+			"Packages LEFT JOIN PackageBases " .
+			"ON PackageBases.ID = Packages.PackageBaseID " .
+			"LEFT JOIN Users " .
+			"ON PackageBases.MaintainerUID = Users.ID " .
+			"LEFT JOIN PackageRelations " .
+			"ON PackageRelations.PackageID = Packages.ID " .
+			"AND PackageRelations.RelTypeID = 2 ";
+
+		$where = array();
+
+		foreach($http_data['by'] as $by) {
+			if($by == 'provides') {
+				foreach ($http_data['arg'] as $provide) {
+					array_push($where,
+						"PackageRelations.RelName = " .
+						$this->dbh->quote($provide));
+				}
+			} elseif($by == 'name') {
+				foreach ($http_data['arg'] as $name) {
+					array_push($where, "Packages.NAME LIKE " .
+						$this->dbh->quote('%' . $name . '%_'));
+					array_push($where, "Packages.Name = " .
+						$this->dbh->quote($name));
+				}
+			} elseif($by == 'name-desc') {
+				foreach ($http_data['arg'] as $name) {
+					array_push($where, "Packages.NAME LIKE " .
+						$this->dbh->quote('%' . $name . '%_'));
+					array_push($where, "Packages.Name = " .
+						$this->dbh->quote($name));
+					array_push($where, "Packages.Description LIKE " .
+						$this->dbh->quote('%' . $name . '%_'));
+				}
+			} elseif (in_array($by, self::$exposed_depfields)) {
+				foreach ($http_data['arg'] as $dep) {
+					$subquery = "SELECT PackageDepends.DepName FROM PackageDepends ";
+					$subquery .= "LEFT JOIN DependencyTypes ";
+					$subquery .= "ON PackageDepends.DepTypeID = DependencyTypes.ID ";
+					$subquery .= "WHERE PackageDepends.PackageID = Packages.ID ";
+					$subquery .= "AND DependencyTypes.Name = " . $this->dbh->quote($by);
+					$sub_condition = $this->dbh->quote($dep) . " IN (${subquery})";
+					array_push($where, $sub_condition);
+				}
+			} else {
+				return $this->json_error(
+					"${by} is not supported in v6 multi-argument by search."
+				);
+			}
+		}
+
+		$query .= "WHERE " . implode(" OR ", $where) . " ";
+			"AND PackageBases.PackagerUID IS NOT NULL " .
+			"LIMIT ${max_results}";
+
+		$packages = array(); // Final list of packages
+		$result = $this->dbh->query($query);
+
+		if ($result) {
+			while ($row = $result->fetch(PDO::FETCH_ASSOC))
+				array_push($packages, $row);
+		}
+
+		return $this->json_results('search', count($packages), $packages, NULL);
+	}
+
 	/*
 	 * Returns the info on a specific package.
 	 *
@@ -569,7 +665,6 @@  class AurJSON {
 
 		return $this->process_query('multiinfo', $where_condition);
 	}
-
 	/*
 	 * Returns all the packages for a specific maintainer.
 	 *