Skip to content
This repository was archived by the owner on May 30, 2023. It is now read-only.

Commit 4f4c1e2

Browse files
author
Robert Kummer
authored
Merge pull request #1 from ipunkt/features/add-query-endpoint
Features/add query endpoint
2 parents ee084f4 + dab7f2c commit 4f4c1e2

File tree

9 files changed

+288
-14
lines changed

9 files changed

+288
-14
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ npm-debug.log
1111
yarn-error.log
1212
.env
1313
composer.lock
14+
.rancherize
15+
rancherize.json

README.md

+71
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,77 @@ You can set up validation rules for every attribute found in the input request.
7171

7272
Your local environment will be available [here](http://localhost:15540/).
7373

74+
We (ipunkt) provide a package called [rancherize](https://www.github.com/ipunkt/rancherize) for hosting our web stacks in a rancher environment with various docker images. For local development you can use rancherize command in the following way.
75+
76+
You need locally docker daemon installed.
77+
78+
Copy following content in a rancherize.json file in your project root:
79+
80+
```json
81+
{
82+
"blueprints": {
83+
"webserver": "Rancherize\\Blueprint\\Webserver\\WebserverBlueprint"
84+
},
85+
"blueprint": "webserver",
86+
"default": {
87+
"add-redis": true,
88+
"add-database": false,
89+
"add-version": "SVN_REVISION",
90+
"queues": [
91+
{
92+
"connection": "redis",
93+
"name": "default"
94+
}
95+
],
96+
"rancher": {
97+
"in-service": true,
98+
"account": "your-rancher-account"
99+
},
100+
"docker": {
101+
"account": "docker-account",
102+
"repository": "docker-repository",
103+
"version-prefix": "laravel-indexer-service_",
104+
"base-image": "busybox"
105+
},
106+
"healthcheck": {
107+
"url": "\/healthcheck"
108+
},
109+
"nginx-config": "",
110+
"service-name": "LaravelIndexerService",
111+
"environment": {
112+
"QUEUE_DRIVER": "redis",
113+
"REDIS_HOST": "redis",
114+
"NO_MIGRATE": true,
115+
"NO_SEED": true,
116+
"SENTRY_DSN": "",
117+
"APP_KEY": "base64:WMNa3A7KdAq8NMABeXrQDVZ0tg3BfaV1stkZ5melL6g="
118+
}
119+
},
120+
"environments": {
121+
"local": {
122+
"debug-image": true,
123+
"sync-user-into-container": true,
124+
"expose-port": 15540,
125+
"use-app-container": false,
126+
"mount-workdir": true,
127+
"php": "7.0",
128+
"environment": {
129+
"APP_ENV": "local",
130+
"APP_DEBUG": true,
131+
"APP_KEY": "base64:14Bp/oUv0Va4MMdT/cK8rKrypEIrj5MW0dlIbUcSFK0=",
132+
"SOLR_HOST": "127.0.0.1",
133+
"SOLR_PORT": 8983,
134+
"SOLR_PATH": "/solr/",
135+
"SOLR_CORE": "gettingstarted",
136+
"SOLR_USERNAME": "",
137+
"SOLR_PASSWORD": "",
138+
"SOLR_TIMEOUT": 50
139+
}
140+
}
141+
}
142+
}
143+
```
144+
74145
Starting with `vendor/bin/rancherize start local` and stopping with `vendor/bin/rancherize stop local`.
75146

76147
### Artisan inside docker

app/Http/Api/Items/ItemsRequestHandler.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
use Carbon\Carbon;
66
use Illuminate\Contracts\Cache\Repository;
77
use Illuminate\Database\Eloquent\Model;
8-
use Ipunkt\LaravelJsonApi\Contracts\RequestHandlers\HandlesDeleteRequest;
9-
use Ipunkt\LaravelJsonApi\Contracts\RequestHandlers\HandlesPostRequest;
10-
use Ipunkt\LaravelJsonApi\Http\RequestHandlers\RequestHandler;
11-
use Ipunkt\LaravelJsonApi\Http\Requests\ApiRequest;
128
use Ipunkt\LaravelIndexer\EnvironmentValidation;
139
use Ipunkt\LaravelIndexer\Jobs\Items\CreateItem;
1410
use Ipunkt\LaravelIndexer\Jobs\Items\DeleteItem;
1511
use Ipunkt\LaravelIndexer\Jobs\Solr\Optimize;
12+
use Ipunkt\LaravelJsonApi\Contracts\RequestHandlers\HandlesDeleteRequest;
13+
use Ipunkt\LaravelJsonApi\Contracts\RequestHandlers\HandlesPostRequest;
14+
use Ipunkt\LaravelJsonApi\Http\RequestHandlers\RequestHandler;
15+
use Ipunkt\LaravelJsonApi\Http\Requests\ApiRequest;
1616
use Tobscure\JsonApi\Parameters;
1717

1818
class ItemsRequestHandler extends RequestHandler implements HandlesPostRequest, HandlesDeleteRequest
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Ipunkt\LaravelIndexer\Http\Controllers\Api;
4+
5+
use Illuminate\Foundation\Validation\ValidatesRequests;
6+
use Illuminate\Http\JsonResponse;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Http\Response;
9+
use Illuminate\Pagination\LengthAwarePaginator;
10+
use Ipunkt\LaravelIndexer\Http\Controllers\Controller;
11+
use Ipunkt\LaravelJsonApi\Contracts\RequestHandlers\ApiRequestHandler;
12+
use Ipunkt\LaravelJsonApi\Repositories\PaginatedResult;
13+
use Solarium\Client;
14+
use Solarium\QueryType\Select\Result\Document;
15+
16+
class ItemsQueryController extends Controller
17+
{
18+
use ValidatesRequests;
19+
20+
/**
21+
* query input rules
22+
* @see http://solarium.readthedocs.io/en/stable/queries/select-query/building-a-select-query/building-a-select-query/
23+
*
24+
* @var array
25+
*/
26+
private $selectQueryRules = [
27+
'query' => 'required',
28+
'start' => 'sometimes|numeric|min:0',
29+
'rows' => 'sometimes|numeric|min:0',
30+
'fields' => 'sometimes|array',
31+
'sort' => 'sometimes|array',
32+
'filterquery' => 'sometimes|array',
33+
'component' => 'sometimes|array',
34+
];
35+
36+
/**
37+
* executing a select query
38+
*
39+
* @param Client $client
40+
* @param Request $request
41+
* @return JsonResponse
42+
*/
43+
public function select(Client $client, Request $request)
44+
{
45+
$this->validate($request, $this->selectQueryRules);
46+
$query = $client->createSelect($request->all());
47+
$resultset = $client->select($query);
48+
49+
$documentsCount = $resultset->getNumFound();
50+
$resultCount = $resultset->count();
51+
$maxScore = $resultset->getMaxScore();
52+
53+
$page = $documentsCount > 0
54+
? ceil($request->get('start', 0) / $documentsCount) + 1
55+
: 1;
56+
57+
$items = collect();
58+
59+
if ($resultCount > 0) {
60+
/** @var Document $document */
61+
foreach ($resultset as $document) {
62+
$items->push($document->getFields());
63+
}
64+
}
65+
66+
return response()->json([
67+
'data' => $items->toArray(),
68+
'meta' => [
69+
'pagination' => [
70+
'start' => +$request->get('start', 0),
71+
'rows' => min($resultCount, $request->get('rows', $resultCount)),
72+
'total' => $documentsCount,
73+
'page' => $page,
74+
],
75+
'result' => [
76+
'max-score' => $maxScore,
77+
]
78+
],
79+
], Response::HTTP_OK)
80+
->header('Content-Type', ApiRequestHandler::CONTENT_TYPE);
81+
}
82+
}

app/Providers/RouteServiceProvider.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ protected function mapWebRoutes()
6565
*/
6666
protected function mapApiRoutes()
6767
{
68-
Route::prefix('api')
69-
->middleware('api')
68+
Route::middleware(['secure-api', 'fixed.token'])
7069
->namespace($this->namespace)
7170
->group(base_path('routes/api.php'));
7271
}

config/app.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
| any other location as required by the application or its packages.
1313
*/
1414

15-
'name' => env('APP_NAME', 'Solr Updater Service'),
15+
'name' => env('APP_NAME', 'Laravel Indexer Service'),
1616

1717
/*
1818
|--------------------------------------------------------------------------

rancherize.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
"REDIS_HOST": "redis",
3434
"NO_MIGRATE": true,
3535
"NO_SEED": true,
36-
"SENTRY_DSN": "YOUR-SENTRY-DSN",
37-
"APP_KEY": "base64:WMNa3A7KdAq8NMABeXrQDVZ0tg3BfaV1stkZ5melL6g="
36+
"SENTRY_DSN": "",
37+
"APP_KEY": "base64:OHYjLsSl/pOqEcBx5lEAAXlJu0V1+NGKer6qIcLKQ7E="
3838
}
3939
},
4040
"environments": {
@@ -48,8 +48,8 @@
4848
"environment": {
4949
"APP_ENV": "local",
5050
"APP_DEBUG": true,
51-
"APP_KEY": "base64:14Bp/oUv0Va4MMdT/cK8rKrypEIrj5MW0dlIbUcSFK0=",
52-
"SOLR_HOST": "127.0.0.1",
51+
"APP_KEY": "base64:GQJLEf0JkXLIv6smwmASBXr0pcnYSEiGPxBr4pz46JU=",
52+
"SOLR_HOST": "192.168.190.24",
5353
"SOLR_PORT": 8983,
5454
"SOLR_PATH": "/solr/",
5555
"SOLR_CORE": "gettingstarted",

routes/api.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,4 @@
1313
|
1414
*/
1515

16-
//Route::middleware('auth:api')->get('/user', function (Request $request) {
17-
// return $request->user();
18-
//});
16+
Route::get('/secure/v1/select', 'Api\ItemsQueryController@select');
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
namespace Tests\Feature\Items;
4+
5+
use Tests\TestCase;
6+
7+
class ItemQueryingTest extends TestCase
8+
{
9+
/** @test */
10+
public function it_is_not_a_public_api()
11+
{
12+
// ARRANGE
13+
$this->logout();
14+
15+
// ACT
16+
$response = $this->getJson('/public/v1/items', $this->headers());
17+
18+
// ASSERT
19+
$response->assertStatus(400);
20+
$response->assertExactJson([
21+
'errors' => [
22+
[
23+
'status' => '400',
24+
'title' => 'Token invalid',
25+
]
26+
]
27+
]);
28+
}
29+
30+
/** @test */
31+
public function it_can_query_items_from_solr_with_creating_and_deletion()
32+
{
33+
// ARRANGE
34+
$this->setToken(env('SERVICE_SECURE_TOKEN'));
35+
36+
$data = collect();
37+
$data->push([
38+
'id' => 1,
39+
'title' => $this->faker()->name,
40+
'content' => $this->faker()->sentence(),
41+
'tags' => [
42+
$this->faker()->randomElement(['red', 'green', 'blue']),
43+
$this->faker()->randomElement(['bold', 'tall', 'tiny', 'full'])
44+
],
45+
'color' => '#' . $this->faker()->hexColor,
46+
])->push([
47+
'id' => 2,
48+
'title' => $this->faker()->name,
49+
'content' => $this->faker()->sentence(),
50+
'tags' => [
51+
$this->faker()->randomElement(['red', 'green', 'blue']),
52+
$this->faker()->randomElement(['bold', 'tall', 'tiny', 'full'])
53+
],
54+
'color' => '#' . $this->faker()->hexColor,
55+
])->push([
56+
'id' => 3,
57+
'title' => $this->faker()->name,
58+
'content' => $this->faker()->sentence(),
59+
'tags' => [
60+
$this->faker()->randomElement(['red', 'green', 'blue']),
61+
$this->faker()->randomElement(['bold', 'tall', 'tiny', 'full'])
62+
],
63+
'color' => '#' . $this->faker()->hexColor,
64+
])->push([
65+
'id' => 4,
66+
'title' => $this->faker()->name,
67+
'content' => $this->faker()->sentence(),
68+
'tags' => [
69+
$this->faker()->randomElement(['red', 'green', 'blue']),
70+
$this->faker()->randomElement(['bold', 'tall', 'tiny', 'full'])
71+
],
72+
'color' => '#' . $this->faker()->hexColor,
73+
])->push([
74+
'id' => 5,
75+
'title' => $this->faker()->name,
76+
'content' => $this->faker()->sentence(),
77+
'tags' => [
78+
$this->faker()->randomElement(['red', 'green', 'blue']),
79+
$this->faker()->randomElement(['bold', 'tall', 'tiny', 'full'])
80+
],
81+
'color' => $this->faker()->hexColor,
82+
]);
83+
84+
$data->each(function (array $item) {
85+
$this->postJson('/secure/v1/items', $this->createRequestModel('items', $item), $this->headers());
86+
});
87+
88+
// ACT
89+
$response = $this->getJson('/secure/v1/select?query=*:*&start=0&rows=3&sort[id]=desc', $this->headers());
90+
91+
// ASSERT
92+
$response->assertStatus(200);
93+
$data = $response->json();
94+
$i = 5;
95+
foreach ($data['data'] as $item) {
96+
$this->assertEquals((string)$i, $item['id']);
97+
$i--;
98+
}
99+
$this->assertEquals($i, 2);
100+
101+
foreach ($data['data'] as $item) {
102+
$response = $this->deleteJson('/secure/v1/items/' . $item['id'], [], $this->headers());
103+
$response->assertStatus(204);
104+
}
105+
106+
$response = $this->getJson('/secure/v1/select?query=*:*&start=0&rows=3', $this->headers());
107+
108+
$response->assertStatus(200);
109+
$data = $response->json();
110+
111+
$this->assertEquals(0, $data['meta']['pagination']['start']);
112+
$this->assertEquals(2, $data['meta']['pagination']['rows']);
113+
$this->assertEquals(2, $data['meta']['pagination']['total']);
114+
$this->assertEquals(1, $data['meta']['pagination']['page']);
115+
116+
$response = $this->deleteJson('/secure/v1/items/1', [], $this->headers());
117+
$response->assertStatus(204);
118+
$response = $this->deleteJson('/secure/v1/items/2', [], $this->headers());
119+
$response->assertStatus(204);
120+
}
121+
122+
}

0 commit comments

Comments
 (0)