mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-02 19:04:33 -04:00
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:
parent
b6cb0172c3
commit
da49880733
@ -14,6 +14,7 @@ namespace App\Http\Controllers;
|
|||||||
use App\DataMapper\ClientSettings;
|
use App\DataMapper\ClientSettings;
|
||||||
use App\Factory\ClientFactory;
|
use App\Factory\ClientFactory;
|
||||||
use App\Filters\ClientFilters;
|
use App\Filters\ClientFilters;
|
||||||
|
use App\Http\Requests\Client\BulkClientRequest;
|
||||||
use App\Http\Requests\Client\CreateClientRequest;
|
use App\Http\Requests\Client\CreateClientRequest;
|
||||||
use App\Http\Requests\Client\DestroyClientRequest;
|
use App\Http\Requests\Client\DestroyClientRequest;
|
||||||
use App\Http\Requests\Client\EditClientRequest;
|
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\StoreClient;
|
||||||
use App\Jobs\Client\UpdateClient;
|
use App\Jobs\Client\UpdateClient;
|
||||||
use App\Jobs\Entity\ActionEntity;
|
use App\Jobs\Entity\ActionEntity;
|
||||||
|
use App\Jobs\Util\ProcessBulk;
|
||||||
use App\Jobs\Util\UploadAvatar;
|
use App\Jobs\Util\UploadAvatar;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
@ -32,6 +34,7 @@ use App\Models\Size;
|
|||||||
use App\Repositories\BaseRepository;
|
use App\Repositories\BaseRepository;
|
||||||
use App\Repositories\ClientRepository;
|
use App\Repositories\ClientRepository;
|
||||||
use App\Transformers\ClientTransformer;
|
use App\Transformers\ClientTransformer;
|
||||||
|
use App\Utils\Traits\BulkOptions;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use App\Utils\Traits\Uploadable;
|
use App\Utils\Traits\Uploadable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -47,6 +50,7 @@ class ClientController extends BaseController
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
use Uploadable;
|
use Uploadable;
|
||||||
|
use BulkOptions;
|
||||||
|
|
||||||
protected $entity_type = Client::class;
|
protected $entity_type = Client::class;
|
||||||
|
|
||||||
@ -452,7 +456,8 @@ class ClientController extends BaseController
|
|||||||
/**
|
/**
|
||||||
* Perform bulk actions on the list view
|
* Perform bulk actions on the list view
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @param BulkClientRequest $request
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @OA\Post(
|
* @OA\Post(
|
||||||
@ -492,7 +497,6 @@ class ClientController extends BaseController
|
|||||||
* response=422,
|
* response=422,
|
||||||
* description="Validation error",
|
* description="Validation error",
|
||||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||||
|
|
||||||
* ),
|
* ),
|
||||||
* @OA\Response(
|
* @OA\Response(
|
||||||
* response="default",
|
* response="default",
|
||||||
@ -500,26 +504,38 @@ class ClientController extends BaseController
|
|||||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
* @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 = 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);
|
$this->client_repo->{$action}($client);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
return $this->listResponse(Client::withTrashed()->whereIn('id', $this->transformKeys($ids)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -532,6 +548,4 @@ class ClientController extends BaseController
|
|||||||
//todo
|
//todo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
47
app/Http/Requests/Client/BulkClientRequest.php
Normal file
47
app/Http/Requests/Client/BulkClientRequest.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
60
app/Jobs/Util/ProcessBulk.php
Normal file
60
app/Jobs/Util/ProcessBulk.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\Repositories;
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Factory\ClientFactory;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Repositories\ClientContactRepository;
|
use App\Repositories\ClientContactRepository;
|
||||||
use App\Utils\Traits\GeneratesCounter;
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
55
app/Utils/Traits/BulkOptions.php
Normal file
55
app/Utils/Traits/BulkOptions.php
Normal 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'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -253,4 +253,51 @@ class ClientTest extends TestCase
|
|||||||
$this->assertEquals($client->contacts->count(), 3);
|
$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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user