From 4828560900b696447164c38c6a5ebd763c3db592 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Fri, 15 May 2020 07:10:18 -0400
Subject: [PATCH] Add website API access. (#1620)

---
 config/vufind/WebApiRecordFields.yaml         | 27 +++++++
 module/VuFindApi/config/module.config.php     | 27 ++++++-
 .../Controller/ApiControllerFactory.php       |  1 +
 .../Controller/Search2ApiController.php       | 14 ++++
 .../Controller/SearchApiController.php        | 16 ++++
 .../VuFindApi/Controller/WebApiController.php | 78 +++++++++++++++++++
 .../Controller/WebApiControllerFactory.php    | 70 +++++++++++++++++
 .../Formatter/WebRecordFormatter.php          | 42 ++++++++++
 .../Formatter/WebRecordFormatterFactory.php   | 48 ++++++++++++
 themes/root/templates/searchapi/swagger.phtml | 18 ++---
 10 files changed, 331 insertions(+), 10 deletions(-)
 create mode 100644 config/vufind/WebApiRecordFields.yaml
 create mode 100644 module/VuFindApi/src/VuFindApi/Controller/WebApiController.php
 create mode 100644 module/VuFindApi/src/VuFindApi/Controller/WebApiControllerFactory.php
 create mode 100644 module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatter.php
 create mode 100644 module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatterFactory.php

diff --git a/config/vufind/WebApiRecordFields.yaml b/config/vufind/WebApiRecordFields.yaml
new file mode 100644
index 00000000000..ac94e097696
--- /dev/null
+++ b/config/vufind/WebApiRecordFields.yaml
@@ -0,0 +1,27 @@
+# Key is the field name that can be requested. It has the following subkeys:
+# - vufind.method: name to call (either in the SearchApiController class or the record driver)
+# - vufind.default: true if the field is displayed by default when the request does not specify fields
+# - Swagger specification fields describing the returned data.
+#
+# See http://swagger.io/specification/ for information on the Swagger-specific fields
+#
+id:
+  vufind.method: getUniqueID
+  vufind.default: true
+  description: Record unique ID (can be used in the record endpoint)
+  type: string
+lastModified:
+  vufind.method: getLastModified
+  vufind.default: true
+  description: Last modification date
+  type: string
+title:
+  vufind.method: getTitle
+  vufind.default: true
+  description: Title including any subtitle
+  type: string
+url:
+  vufind.method: getUrl
+  vufind.default: true
+  description: URL for resource
+  type: string
diff --git a/module/VuFindApi/config/module.config.php b/module/VuFindApi/config/module.config.php
index 6c0df9838eb..35d91142f48 100644
--- a/module/VuFindApi/config/module.config.php
+++ b/module/VuFindApi/config/module.config.php
@@ -7,11 +7,13 @@ $config = [
             'VuFindApi\Controller\ApiController' => 'VuFindApi\Controller\ApiControllerFactory',
             'VuFindApi\Controller\SearchApiController' => 'VuFindApi\Controller\SearchApiControllerFactory',
             'VuFindApi\Controller\Search2ApiController' => 'VuFindApi\Controller\Search2ApiControllerFactory',
+            'VuFindApi\Controller\WebApiController' => 'VuFindApi\Controller\WebApiControllerFactory',
         ],
         'aliases' => [
             'Api' => 'VuFindApi\Controller\ApiController',
             'SearchApi' => 'VuFindApi\Controller\SearchApiController',
             'Search2Api' => 'VuFindApi\Controller\Search2ApiController',
+            'WebApi' => 'VuFindApi\Controller\WebApiController',
         ],
     ],
     'service_manager' => [
@@ -19,6 +21,7 @@ $config = [
             'VuFindApi\Formatter\FacetFormatter' => 'Laminas\ServiceManager\Factory\InvokableFactory',
             'VuFindApi\Formatter\RecordFormatter' => 'VuFindApi\Formatter\RecordFormatterFactory',
             'VuFindApi\Formatter\Search2RecordFormatter' => 'VuFindApi\Formatter\Search2RecordFormatterFactory',
+            'VuFindApi\Formatter\WebRecordFormatter' => 'VuFindApi\Formatter\WebRecordFormatterFactory',
         ],
     ],
     'router' => [
@@ -77,7 +80,29 @@ $config = [
                         'action'     => 'record',
                     ]
                 ]
-            ]
+            ],
+            'websearchApiv1' => [
+                'type' => 'Laminas\Router\Http\Literal',
+                'verb' => 'get,post,options',
+                'options' => [
+                    'route'    => '/api/v1/web/search',
+                    'defaults' => [
+                        'controller' => 'WebApi',
+                        'action'     => 'search',
+                    ]
+                ]
+            ],
+            'webrecordApiv1' => [
+                'type' => 'Laminas\Router\Http\Literal',
+                'verb' => 'get,post,options',
+                'options' => [
+                    'route'    => '/api/v1/web/record',
+                    'defaults' => [
+                        'controller' => 'WebApi',
+                        'action'     => 'record',
+                    ]
+                ]
+            ],
         ],
     ],
 ];
diff --git a/module/VuFindApi/src/VuFindApi/Controller/ApiControllerFactory.php b/module/VuFindApi/src/VuFindApi/Controller/ApiControllerFactory.php
index 62c41e24b68..a4e1bb5d42c 100644
--- a/module/VuFindApi/src/VuFindApi/Controller/ApiControllerFactory.php
+++ b/module/VuFindApi/src/VuFindApi/Controller/ApiControllerFactory.php
@@ -64,6 +64,7 @@ class ApiControllerFactory implements FactoryInterface
         $controller = new $requestedName($container);
         $controller->addApi($container->get('ControllerManager')->get('SearchApi'));
         $controller->addApi($container->get('ControllerManager')->get('Search2Api'));
+        $controller->addApi($container->get('ControllerManager')->get('WebApi'));
         return $controller;
     }
 }
diff --git a/module/VuFindApi/src/VuFindApi/Controller/Search2ApiController.php b/module/VuFindApi/src/VuFindApi/Controller/Search2ApiController.php
index 1e636d20689..196f2564522 100644
--- a/module/VuFindApi/src/VuFindApi/Controller/Search2ApiController.php
+++ b/module/VuFindApi/src/VuFindApi/Controller/Search2ApiController.php
@@ -61,4 +61,18 @@ class Search2ApiController extends SearchApiController
      * @var string
      */
     protected $searchRoute = 'index2/search';
+
+    /**
+     * Descriptive label for the index managed by this controller
+     *
+     * @var string
+     */
+    protected $indexLabel = 'secondary';
+
+    /**
+     * Prefix for use in model names used by API
+     *
+     * @var string
+     */
+    protected $modelPrefix = 'Secondary';
 }
diff --git a/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php b/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php
index d26db72868a..c618fa2e2c3 100644
--- a/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php
+++ b/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php
@@ -96,6 +96,20 @@ class SearchApiController extends \VuFind\Controller\AbstractSearch
      */
     protected $searchRoute = 'search';
 
+    /**
+     * Descriptive label for the index managed by this controller
+     *
+     * @var string
+     */
+    protected $indexLabel = 'primary';
+
+    /**
+     * Prefix for use in model names used by API
+     *
+     * @var string
+     */
+    protected $modelPrefix = '';
+
     /**
      * Constructor
      *
@@ -142,6 +156,8 @@ class SearchApiController extends \VuFind\Controller\AbstractSearch
             'recordRoute' => $this->recordRoute,
             'searchRoute' => $this->searchRoute,
             'searchIndex' => $this->searchClassId,
+            'indexLabel' => $this->indexLabel,
+            'modelPrefix' => $this->modelPrefix,
         ];
         $json = $this->getViewRenderer()->render(
             'searchapi/swagger', $viewParams
diff --git a/module/VuFindApi/src/VuFindApi/Controller/WebApiController.php b/module/VuFindApi/src/VuFindApi/Controller/WebApiController.php
new file mode 100644
index 00000000000..89d9033080b
--- /dev/null
+++ b/module/VuFindApi/src/VuFindApi/Controller/WebApiController.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * Class WebApiController
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2020.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  VuFindApi\Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  https://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://knihovny.cz Main Page
+ */
+namespace VuFindApi\Controller;
+
+/**
+ * Web API Controller
+ *
+ * Controls the Search API functionality on website index
+ *
+ * @category VuFind
+ * @package  VuFindApi\Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  https://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
+ */
+class WebApiController extends SearchApiController
+{
+    /**
+     * Search class family to use.
+     *
+     * @var string
+     */
+    protected $searchClassId = 'SolrWeb';
+
+    /**
+     * Record route uri
+     *
+     * @var string
+     */
+    protected $recordRoute = 'web/record';
+
+    /**
+     * Search route uri
+     *
+     * @var string
+     */
+    protected $searchRoute = 'web/search';
+
+    /**
+     * Descriptive label for the index managed by this controller
+     *
+     * @var string
+     */
+    protected $indexLabel = 'website';
+
+    /**
+     * Prefix for use in model names used by API
+     *
+     * @var string
+     */
+    protected $modelPrefix = 'Web';
+}
diff --git a/module/VuFindApi/src/VuFindApi/Controller/WebApiControllerFactory.php b/module/VuFindApi/src/VuFindApi/Controller/WebApiControllerFactory.php
new file mode 100644
index 00000000000..09804ec356b
--- /dev/null
+++ b/module/VuFindApi/src/VuFindApi/Controller/WebApiControllerFactory.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Factory for WebApiController.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
+ */
+namespace VuFindApi\Controller;
+
+use Interop\Container\ContainerInterface;
+use Laminas\ServiceManager\Factory\FactoryInterface;
+
+/**
+ * Factory for WebApiController.
+ *
+ * @category VuFind
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
+ */
+class WebApiControllerFactory implements FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options sent to factory.');
+        }
+        return new $requestedName(
+            $container,
+            $container->get(\VuFindApi\Formatter\WebRecordFormatter::class),
+            $container->get(\VuFindApi\Formatter\FacetFormatter::class)
+        );
+    }
+}
diff --git a/module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatter.php b/module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatter.php
new file mode 100644
index 00000000000..4e7e342eb69
--- /dev/null
+++ b/module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatter.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Class WebRecordFormatter
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2020.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  API_Formatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  https://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFindApi\Formatter;
+
+/**
+ * Record formatter for web API responses
+ *
+ * @category VuFind
+ * @package  API_Formatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class WebRecordFormatter extends RecordFormatter
+{
+}
diff --git a/module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatterFactory.php b/module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatterFactory.php
new file mode 100644
index 00000000000..067b33dab82
--- /dev/null
+++ b/module/VuFindApi/src/VuFindApi/Formatter/WebRecordFormatterFactory.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Class WebRecordFormatterFactory
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2020.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  API_Formatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  https://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFindApi\Formatter;
+
+/**
+ * Web Record Formatter factory.
+ *
+ * @category VuFind
+ * @package  API_Formatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class WebRecordFormatterFactory extends RecordFormatterFactory
+{
+    /**
+     * Record fields configuration file name
+     *
+     * @var string
+     */
+    protected $configFile = 'WebApiRecordFields.yaml';
+}
diff --git a/themes/root/templates/searchapi/swagger.phtml b/themes/root/templates/searchapi/swagger.phtml
index ed27073c35d..56d16141646 100644
--- a/themes/root/templates/searchapi/swagger.phtml
+++ b/themes/root/templates/searchapi/swagger.phtml
@@ -40,8 +40,8 @@
     "paths": {
         "/<?=$recordRoute?>": {
             "get": {
-                "summary": "Fetch records from <?=($this->searchIndex == 'Solr') ? "primary" : "secondary" ?> index",
-                "description": "Return a single record or multiple records from <?=($this->searchIndex == 'Solr') ? "primary" : "secondary" ?> index. POST method may also be used if sending a long request.",
+                "summary": "Fetch records from <?=$this->indexLabel?> index",
+                "description": "Return a single record or multiple records from <?=$this->indexLabel?> index. POST method may also be used if sending a long request.",
                 "parameters": [
                     {
                         "name": "id",
@@ -71,7 +71,7 @@
                     "200": {
                         "description": "Response containing result count and records",
                         "schema": {
-                            "$ref": "#/definitions/SearchResponse"
+                            "$ref": "#/definitions/<?=$this->modelPrefix?>SearchResponse"
                         }
                     },
                     "default": {
@@ -85,8 +85,8 @@
         },
         "/<?=$searchRoute?>": {
             "get": {
-                "summary": "Search <?=($this->searchIndex == 'Solr') ? "primary" : "secondary" ?> index",
-                "description": "Search <?=($this->searchIndex == 'Solr') ? "primary" : "secondary" ?> index with given terms and filters. POST method may also be used if sending a long request.\n\nThe URL syntax here is the as the one used in VuFind user interface. It is possible to make a search in VuFind and copy the query parameters here to make the same search via the API.",
+                "summary": "Search <?=$this->indexLabel?> index",
+                "description": "Search <?=$this->indexLabel?> index with given terms and filters. POST method may also be used if sending a long request.\n\nThe URL syntax here is the as the one used in VuFind user interface. It is possible to make a search in VuFind and copy the query parameters here to make the same search via the API.",
                 "parameters": [
                     {
                         "name": "lookfor",
@@ -168,7 +168,7 @@
                     "200": {
                         "description": "Response containing result count, records and/or facets.",
                         "schema": {
-                            "$ref": "#/definitions/SearchResponse"
+                            "$ref": "#/definitions/<?=$this->modelPrefix?>SearchResponse"
                         }
                     },
                     "default": {
@@ -287,7 +287,7 @@
                 }
             }
         },
-        "Record": {
+        "<?=$this->modelPrefix?>Record": {
             "type": "object",
             "properties": <?=json_encode($this->recordFields) ?>
         },
@@ -307,7 +307,7 @@
                 }
             }
         },
-        "SearchResponse": {
+        "<?=$this->modelPrefix?>SearchResponse": {
             "type": "object",
             "properties": {
                 "resultCount": {
@@ -318,7 +318,7 @@
                     "description": "Records",
                     "type": "array",
                     "items": {
-                        "$ref": "#/definitions/Record"
+                        "$ref": "#/definitions/<?=$this->modelPrefix?>Record"
                     }
                 },
                 "facets": {
-- 
GitLab