-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #80 from art-institute-of-chicago/feature/csv-search
Add CSV search endpoonts [API-398]
- Loading branch information
Showing
5 changed files
with
160 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
namespace App\Http\Controllers; | ||
|
||
use Aic\Hub\Foundation\Exceptions\DetailedException; | ||
use App\Http\Search\Request as SearchRequest; | ||
use App\Http\Search\CsvResponse as SearchResponse; | ||
use Illuminate\Support\Facades\Request as RequestFacade; | ||
use Illuminate\Http\Request; | ||
use Elasticsearch; | ||
|
||
class CsvSearchController extends SearchController | ||
{ | ||
public function search(Request $request, $resource = null) | ||
{ | ||
$headers = [ | ||
'Content-Type' => 'text/csv', | ||
'Content-Disposition' => 'inline', | ||
]; | ||
$data = $this->query('getSearchParams', 'getSearchResponse', 'search', $resource); | ||
$titles = array_keys($data[0]); | ||
|
||
$callback = function () use ($data, $titles) { | ||
$output = fopen('php://output', 'w'); | ||
fputcsv($output, $titles); | ||
foreach ($data as $row) { | ||
fputcsv($output, $row); | ||
} | ||
fclose($output); | ||
}; | ||
return response()->stream($callback, 200, $headers); | ||
} | ||
|
||
/** | ||
* Helper method to perform a query against Elasticsearch endpoint. | ||
* | ||
* @param string $requestMethod Name of transformation method on SearchRequest class | ||
* @param string $responseMethod Name of transformation method on SearchResponse class | ||
* @param array $resource Resource to search (translates to index and type) | ||
* @param string $id Identifier of a resource (meant for explain) | ||
* | ||
* @return \Illuminate\Http\Response | ||
*/ | ||
protected function query($requestMethod, $responseMethod, $elasticsearchMethod, $resource, $id = null, $requestArgs = null) | ||
{ | ||
// Combine any configuration params | ||
$input = RequestFacade::all(); | ||
$input = $requestArgs ? array_merge($input, $requestArgs) : $input; | ||
|
||
// Transform our API's syntax into an Elasticsearch params array | ||
$params = ( new SearchRequest($resource, $id) )->{$requestMethod}($input); | ||
$results = null; | ||
|
||
try { | ||
$results = Elasticsearch::$elasticsearchMethod($params); | ||
} catch (\Exception $e) { | ||
// Elasticsearch occasionally returns a status code of zero | ||
$code = $e->getCode() > 0 ? $e->getCode() : 500; | ||
|
||
return response($e->getMessage(), $code)->header('Content-Type', 'text/csv'); | ||
} | ||
|
||
// Transform Elasticsearch results into our API standard | ||
$response = ( new SearchResponse($results, $params, $resource) )->{$responseMethod}(); | ||
|
||
return $response; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
namespace App\Http\Search; | ||
|
||
use Illuminate\Support\Arr; | ||
use Illuminate\Support\Facades\Gate; | ||
use Illuminate\Support\Facades\Request as RequestFacade; | ||
|
||
class CsvResponse extends Response | ||
{ | ||
/** | ||
* Transform response for search queries. | ||
* | ||
* @return array | ||
*/ | ||
public function getSearchResponse() | ||
{ | ||
// Strip off extraneous search fields to simplify output | ||
$except = ['_score', 'thumbnail']; | ||
|
||
return collect($this->data())->map(function ($item) use ($except) { | ||
return collect($item)->except($except)->map(function ($field) { | ||
// If the field is an array, smush it into one cell | ||
if (is_array($field)) { | ||
return implode(",", $field); | ||
} | ||
return $field; | ||
})->all(); | ||
})->toArray(); | ||
} | ||
|
||
/** | ||
* Add data (i.e. hits, results) to response. | ||
* | ||
* @return array | ||
*/ | ||
protected function data() | ||
{ | ||
return parent::data()['data']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
use Illuminate\Support\Facades\Route; | ||
use App\Http\Controllers\ResourceController; | ||
use App\Http\Controllers\RestrictedResourceController; | ||
use App\Http\Controllers\CsvSearchController; | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| API Routes | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here is where you can register API routes for your application. These | ||
| routes are loaded by the RouteServiceProvider and all of them will | ||
| be assigned to the "api" middleware group. Make something great! | ||
| | ||
*/ | ||
|
||
app('url')->forceRootUrl(config('aic.proxy_url')); | ||
app('url')->forceScheme(config('aic.proxy_scheme')); | ||
|
||
Route::group(['prefix' => 'v1'], function () { | ||
// Elasticsearch | ||
Route::match(['GET', 'POST'], 'search', [CsvSearchController::class, 'search']); | ||
Route::match(['GET', 'POST'], '{resource}/search', [CsvSearchController::class, 'search']); | ||
|
||
// Define all of our resource routes by looping through config | ||
foreach (config('resources.outbound.base') as $resource) { | ||
if (!isset($resource['endpoint'])) { | ||
continue; | ||
} | ||
|
||
$isScoped = $resource['scope_of'] ?? false; | ||
$isRestricted = $resource['is_restricted'] ?? false; | ||
|
||
$controller = $resource['controller'] ?? ( | ||
($isRestricted && env('APP_ENV') !== 'testing') ? RestrictedResourceController::class : ResourceController::class | ||
); | ||
|
||
Route::any($resource['endpoint'], [$controller, ($isScoped ? 'indexScope' : 'index')]); | ||
Route::any($resource['endpoint'] . '/{id}', [$controller, ($isScoped ? 'showScope' : 'show')]); | ||
} | ||
}); |