Massively creating resources (#3152)

* Bulk storing request & accesing policy service

* Transform hardcoded action to self-property

* Testing, support for multiple resources

* Uncomment, response(200)

* Change authorize to isAdmin() property
This commit is contained in:
Benjamin Beganović 2019-12-17 11:58:23 +01:00 committed by David Bomba
parent b6cb0172c3
commit da49880733
6 changed files with 281 additions and 45 deletions

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers;
use App\DataMapper\ClientSettings;
use App\Factory\ClientFactory;
use App\Filters\ClientFilters;
use App\Http\Requests\Client\BulkClientRequest;
use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\DestroyClientRequest;
use App\Http\Requests\Client\EditClientRequest;
@ -23,6 +24,7 @@ use App\Http\Requests\Client\UpdateClientRequest;
use App\Jobs\Client\StoreClient;
use App\Jobs\Client\UpdateClient;
use App\Jobs\Entity\ActionEntity;
use App\Jobs\Util\ProcessBulk;
use App\Jobs\Util\UploadAvatar;
use App\Models\Client;
use App\Models\ClientContact;
@ -32,6 +34,7 @@ use App\Models\Size;
use App\Repositories\BaseRepository;
use App\Repositories\ClientRepository;
use App\Transformers\ClientTransformer;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
@ -47,6 +50,7 @@ class ClientController extends BaseController
{
use MakesHash;
use Uploadable;
use BulkOptions;
protected $entity_type = Client::class;
@ -452,7 +456,8 @@ class ClientController extends BaseController
/**
* Perform bulk actions on the list view
*
* @return Collection
* @param BulkClientRequest $request
* @return \Illuminate\Http\Response
*
*
* @OA\Post(
@ -492,7 +497,6 @@ class ClientController extends BaseController
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
@ -500,26 +504,38 @@ class ClientController extends BaseController
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function bulk()
public function bulk(BulkClientRequest $request)
{
$action = $request->action;
$ids = [];
$action = request()->input('action');
if ($request->action !== self::$STORE_METHOD) {
$ids = request()->input('ids');
$ids = $request->ids;
$clients = Client::withTrashed()->find($this->transformKeys($ids));
$clients->each(function ($client, $key) use($action){
$clients->each(function ($client, $key) use ($request, $action) {
if(auth()->user()->can('edit', $client))
if (auth()->user()->can($request->action, $client))
$this->client_repo->{$action}($client);
});
return $this->listResponse(Client::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
if($request->action == self::$STORE_METHOD) {
/** Small hunks of data (originally 100) */
$chunks = array_chunk($request->clients, self::$CHUNK_SIZE);
foreach($chunks as $data) {
dispatch(new ProcessBulk($data, $this->client_repo, self::$STORE_METHOD))->onQueue(self::$DEFAULT_QUEUE);
}
}
return $this->listResponse(Client::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
/**
@ -532,6 +548,4 @@ class ClientController extends BaseController
//todo
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Client;
use App\Utils\Traits\BulkOptions;
use Illuminate\Foundation\Http\FormRequest;
use App\Models\Client;
class BulkClientRequest extends FormRequest
{
use BulkOptions;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if (!$this->has('action')) {
return false;
}
if (!in_array($this->action, $this->getBulkOptions(), true)) {
return false;
}
return auth()->user()->can(auth()->user()->isAdmin(), Client::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = $this->getGlobalRules();
/** We don't require IDs on bulk storing. */
if ($this->action !== self::$STORE_METHOD) {
$rules['ids'] = ['required'];
}
return $rules;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Jobs\Util;
use App\Utils\Traits\BulkOptions;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessBulk implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, BulkOptions;
/**
* Repository for target resource.
*/
protected $repo;
/**
* Method aka 'action' to process.
*
* @var string
*/
protected $method;
/**
* Chunks of data to process.
*
* @var array
*/
private $data;
/**
* Create a new job instance.
*
* @param array $data
* @param $repo
* @param string $method
*/
public function __construct(array $data, $repo, string $method)
{
$this->repo = $repo;
$this->method = $method;
$this->data = $data;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
foreach($this->data as $resource) {
$this->repo->{$this->method}($resource);
}
}
}

View File

@ -11,6 +11,7 @@
namespace App\Repositories;
use App\Factory\ClientFactory;
use App\Models\Client;
use App\Repositories\ClientContactRepository;
use App\Utils\Traits\GeneratesCounter;
@ -80,5 +81,17 @@ class ClientRepository extends BaseRepository
}
/**
* Store clients in bulk.
*
* @param array $client
* @return Client|null
*/
public function create($client): ?Client
{
return $this->save(
$client, ClientFactory::create(auth()->user()->company()->id, auth()->user()->id)
);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Utils\Traits;
trait BulkOptions
{
/**
* Store method in requests.
*
* @var string
*/
public static $STORE_METHOD = 'create';
/**
* Default chunk size.
*
* @var int
*/
public static $CHUNK_SIZE = 100;
/**
* Default queue for bulk processing.
*
* @var string
*/
public static $DEFAULT_QUEUE = 'bulk_processing';
/**
* Available bulk options - used in requests (eg. BulkClientRequests)
*
* @return array
* @var array
*/
public function getBulkOptions()
{
return [
'create', 'edit', 'view',
];
}
/**
* Shared rules for bulk requests.
*
* @return array
* @var array
*/
public function getGlobalRules()
{
return [
'action' => ['required'],
];
}
}

View File

@ -253,4 +253,51 @@ class ClientTest extends TestCase
$this->assertEquals($client->contacts->count(), 3);
}
/** @test */
public function testMassivelyCreatingClients()
{
$data = [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'name' => $this->faker->company,
'email' => $this->faker->unique()->safeEmail,
'password' => 'ALongAndBrilliantPassword123',
'_token' => csrf_token(),
'privacy_policy' => 1,
'terms_of_service' => 1
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
])->post('/api/v1/signup?include=account', $data);
$response->assertStatus(200);
$acc = $response->json();
$account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id']));
$token = $account->default_company->tokens->first()->token;
$body = [
'action' => 'create',
'clients' => [
['name' => $this->faker->firstName, 'website' => 'my-awesome-website-1.com'],
['name' => $this->faker->firstName, 'website' => 'my-awesome-website-2.com'],
],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $token,
])->post(route('clients.bulk'), $body);
$response->assertStatus(200);
$first_record = Client::where('website', 'my-awesome-website-1.com')->first();
$second_record = Client::where('website', 'my-awesome-website-2.com')->first();
$this->assertNotNull($first_record);
$this->assertNotNull($second_record);
}
}