diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 4445c46040c2..3a3829f21b2f 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -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,7 +50,8 @@ class ClientController extends BaseController { use MakesHash; use Uploadable; - + use BulkOptions; + protected $entity_type = Client::class; protected $entity_transformer = ClientTransformer::class; @@ -98,7 +102,7 @@ class ClientController extends BaseController * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -107,9 +111,9 @@ class ClientController extends BaseController */ public function index(ClientFilters $filters) { - + $clients = Client::filter($filters); - + return $this->listResponse($clients); } @@ -157,7 +161,7 @@ class ClientController extends BaseController * * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -167,7 +171,7 @@ class ClientController extends BaseController public function show(ShowClientRequest $request, Client $client) { - return $this->itemResponse($client); + return $this->itemResponse($client); } @@ -177,7 +181,7 @@ class ClientController extends BaseController * @param int $id * @return \Illuminate\Http\Response * - * + * * @OA\Get( * path="/api/v1/clients/{id}/edit", * operationId="editClient", @@ -214,7 +218,7 @@ class ClientController extends BaseController * * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -235,8 +239,8 @@ class ClientController extends BaseController * @param App\Models\Client $client * @return \Illuminate\Http\Response * - * - * + * + * * @OA\Put( * path="/api/v1/clients/{id}", * operationId="updateClient", @@ -273,7 +277,7 @@ class ClientController extends BaseController * * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -296,8 +300,8 @@ class ClientController extends BaseController * * @return \Illuminate\Http\Response * - * - * + * + * * @OA\Get( * path="/api/v1/clients/create", * operationId="getClientsCreate", @@ -323,7 +327,7 @@ class ClientController extends BaseController * * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -332,7 +336,7 @@ class ClientController extends BaseController */ public function create(CreateClientRequest $request) { - + $client = ClientFactory::create(auth()->user()->company()->id, auth()->user()->id); return $this->itemResponse($client); @@ -372,7 +376,7 @@ class ClientController extends BaseController * * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -398,7 +402,7 @@ class ClientController extends BaseController * @param int $id * @return \Illuminate\Http\Response * - * + * * @OA\Delete( * path="/api/v1/clients/{id}", * operationId="deleteClient", @@ -434,7 +438,7 @@ class ClientController extends BaseController * * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), @@ -451,10 +455,11 @@ class ClientController extends BaseController /** * Perform bulk actions on the list view - * - * @return Collection * - * + * @param BulkClientRequest $request + * @return \Illuminate\Http\Response + * + * * @OA\Post( * path="/api/v1/clients/bulk", * operationId="bulkClients", @@ -492,39 +497,50 @@ class ClientController extends BaseController * response=422, * description="Validation error", * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * ), * @OA\Response( - * response="default", + * response="default", * description="Unexpected Error", * @OA\JsonContent(ref="#/components/schemas/Error"), * ), * ) - * */ - public function bulk() + public function bulk(BulkClientRequest $request) { + $action = $request->action; + $ids = []; - $action = request()->input('action'); - - $ids = request()->input('ids'); + if ($request->action !== self::$STORE_METHOD) { - $clients = Client::withTrashed()->find($this->transformKeys($ids)); - - $clients->each(function ($client, $key) use($action){ + $ids = $request->ids; - if(auth()->user()->can('edit', $client)) - $this->client_repo->{$action}($client); + $clients = Client::withTrashed()->find($this->transformKeys($ids)); - }); + $clients->each(function ($client, $key) use ($request, $action) { + + if (auth()->user()->can($request->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))); - } /** * Returns a client statement - * + * * @return [type] [description] */ public function statement() @@ -532,6 +548,4 @@ class ClientController extends BaseController //todo } - - } diff --git a/app/Http/Requests/Client/BulkClientRequest.php b/app/Http/Requests/Client/BulkClientRequest.php new file mode 100644 index 000000000000..f1db1ddfa9c3 --- /dev/null +++ b/app/Http/Requests/Client/BulkClientRequest.php @@ -0,0 +1,47 @@ +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; + } +} diff --git a/app/Jobs/Util/ProcessBulk.php b/app/Jobs/Util/ProcessBulk.php new file mode 100644 index 000000000000..25e53488d949 --- /dev/null +++ b/app/Jobs/Util/ProcessBulk.php @@ -0,0 +1,60 @@ +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); + } + } +} diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index d299baec4aa1..06d28231596d 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -11,6 +11,7 @@ namespace App\Repositories; +use App\Factory\ClientFactory; use App\Models\Client; use App\Repositories\ClientContactRepository; use App\Utils\Traits\GeneratesCounter; @@ -71,14 +72,26 @@ class ClientRepository extends BaseRepository if(isset($data['contacts'])) $contacts = $this->contact_repo->save($data['contacts'], $client); - + if(empty($data['name'])) $data['name'] = $client->present()->name(); return $client; - + } + /** + * 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) + ); + } -} \ No newline at end of file +} diff --git a/app/Utils/Traits/BulkOptions.php b/app/Utils/Traits/BulkOptions.php new file mode 100644 index 000000000000..eb39c63dde98 --- /dev/null +++ b/app/Utils/Traits/BulkOptions.php @@ -0,0 +1,55 @@ + ['required'], + ]; + } +} diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index a8d92cd3a872..1b2a88af0d81 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -65,7 +65,7 @@ class ClientTest extends TestCase $acc = $response->json(); - $account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id'])); + $account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id'])); $token = $account->default_company->tokens->first()->token; @@ -102,13 +102,13 @@ class ClientTest extends TestCase $acc = $response->json(); - $account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id'])); + $account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id'])); $company_token = $account->default_company->tokens()->first(); $token = $company_token->token; $company = $company_token->company; - + $user = $company_token->user; //$company_user = $company->company_users()->first(); @@ -244,7 +244,7 @@ class ClientTest extends TestCase $this->assertNotNull($client); /* Make sure we have a valid settings object*/ - $this->assertEquals($client->getSetting('timezone_id'), 1); + $this->assertEquals($client->getSetting('timezone_id'), 1); /* Make sure we are harvesting valid data */ $this->assertEquals($client->timezone()->name, 'Pacific/Midway'); @@ -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); + } }