From dfacb89c8793a264a03e889ef033bf2fc64eba1d Mon Sep 17 00:00:00 2001 From: Regis Freyd Date: Sat, 1 Feb 2020 08:31:59 -0500 Subject: [PATCH] feat: get all contacts for a given tag in the api (#3534) --- .gitignore | 1 + CHANGELOG.md | 1 + .../Controllers/Api/ApiContactController.php | 25 +-- app/Http/Controllers/Api/ApiTagController.php | 42 +++- app/Models/Contact/Contact.php | 26 +-- routes/api.php | 1 + tests/Api/ApiTagControllerTest.php | 186 ++++++++++++++++++ .../Api/Contact/ApiContactControllerTest.php | 1 + 8 files changed, 252 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 0e4437e91..ba77ebc2a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ monicadump.sql .sentry-release .phpunit.result.cache .composer +.idea diff --git a/CHANGELOG.md b/CHANGELOG.md index 819699b90..626f039b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Add vue data validations * Add ability to edit activities * Associate a photo to a gift +* New API method: get all the contacts for a given tag ### Enhancements: diff --git a/app/Http/Controllers/Api/ApiContactController.php b/app/Http/Controllers/Api/ApiContactController.php index c60ec31e4..7dd4c8a8e 100644 --- a/app/Http/Controllers/Api/ApiContactController.php +++ b/app/Http/Controllers/Api/ApiContactController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use Illuminate\Http\Request; use App\Helpers\SearchHelper; use App\Models\Contact\Contact; +use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; use Illuminate\Database\QueryException; use App\Services\Contact\Contact\SetMeContact; @@ -12,6 +13,7 @@ use Illuminate\Validation\ValidationException; use App\Services\Contact\Contact\CreateContact; use App\Services\Contact\Contact\UpdateContact; use App\Services\Contact\Contact\DestroyContact; +use Illuminate\Http\Resources\Json\JsonResource; use App\Services\Contact\Contact\UpdateContactWork; use Illuminate\Database\Eloquent\ModelNotFoundException; use App\Http\Resources\Contact\Contact as ContactResource; @@ -37,7 +39,8 @@ class ApiContactController extends ApiController * We will only retrieve the contacts that are "real", not the partials * ones. * - * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse + * @param Request $request + * @return JsonResource|JsonResponse */ public function index(Request $request) { @@ -80,10 +83,10 @@ class ApiContactController extends ApiController * Get the detail of a given contact. * * @param Request $request - * - * @return ContactResource|\Illuminate\Http\JsonResponse|ContactWithContactFieldsResource + * @param int $id + * @return ContactResource|JsonResponse|ContactWithContactFieldsResource */ - public function show(Request $request, $id) + public function show(Request $request, int $id) { try { $contact = Contact::where('account_id', auth()->user()->account_id) @@ -107,7 +110,7 @@ class ApiContactController extends ApiController * * @param Request $request * - * @return ContactResource|\Illuminate\Http\JsonResponse + * @return ContactResource|JsonResponse */ public function store(Request $request) { @@ -135,7 +138,7 @@ class ApiContactController extends ApiController * * @param Request $request * - * @return ContactResource|\Illuminate\Http\JsonResponse + * @return ContactResource|JsonResponse */ public function update(Request $request, $contactId) { @@ -164,7 +167,7 @@ class ApiContactController extends ApiController * * @param Request $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function destroy(Request $request, $contactId) { @@ -180,7 +183,7 @@ class ApiContactController extends ApiController /** * Apply the `?with=` parameter. * @param Collection $contacts - * @return \Illuminate\Http\Resources\Json\JsonResource + * @return JsonResource */ private function applyWithParameter($contacts, string $parameter = null) { @@ -218,7 +221,7 @@ class ApiContactController extends ApiController * @param Request $request * @param int $contactId * - * @return ContactResource|\Illuminate\Http\JsonResponse + * @return ContactResource|JsonResponse */ public function updateWork(Request $request, $contactId) { @@ -247,7 +250,7 @@ class ApiContactController extends ApiController * @param Request $request * @param int $contactId * - * @return ContactResource|\Illuminate\Http\JsonResponse + * @return ContactResource|JsonResponse */ public function updateFoodPreferences(Request $request, $contactId) { @@ -276,7 +279,7 @@ class ApiContactController extends ApiController * @param Request $request * @param int $contactId * - * @return ContactResource|\Illuminate\Http\JsonResponse + * @return ContactResource|JsonResponse */ public function updateIntroduction(Request $request, $contactId) { diff --git a/app/Http/Controllers/Api/ApiTagController.php b/app/Http/Controllers/Api/ApiTagController.php index 45e29e962..c9bcf7062 100644 --- a/app/Http/Controllers/Api/ApiTagController.php +++ b/app/Http/Controllers/Api/ApiTagController.php @@ -4,13 +4,17 @@ namespace App\Http\Controllers\Api; use App\Models\Contact\Tag; use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; use App\Services\Contact\Tag\CreateTag; use App\Services\Contact\Tag\UpdateTag; use Illuminate\Database\QueryException; use App\Services\Contact\Tag\DestroyTag; +use Illuminate\Database\Eloquent\Builder; use App\Http\Resources\Tag\Tag as TagResource; use Illuminate\Validation\ValidationException; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Resources\Json\AnonymousResourceCollection; +use App\Http\Resources\Contact\ContactWithContactFields as ContactWithContactFieldsResource; class ApiTagController extends ApiController { @@ -19,7 +23,7 @@ class ApiTagController extends ApiController * We will only retrieve the contacts that are "real", not the partials * ones. * - * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse + * @return AnonymousResourceCollection|JsonResponse */ public function index(Request $request) { @@ -39,7 +43,7 @@ class ApiTagController extends ApiController * * @param Request $request * - * @return TagResource|\Illuminate\Http\JsonResponse + * @return TagResource|JsonResponse */ public function show(Request $request, $id) { @@ -59,7 +63,7 @@ class ApiTagController extends ApiController * * @param Request $request * - * @return TagResource|\Illuminate\Http\JsonResponse + * @return TagResource|JsonResponse */ public function store(Request $request) { @@ -84,10 +88,10 @@ class ApiTagController extends ApiController * Update the tag. * * @param Request $request - * - * @return TagResource|\Illuminate\Http\JsonResponse + * @param int $id + * @return TagResource|JsonResponse */ - public function update(Request $request, $id) + public function update(Request $request, int $id) { try { $tag = app(UpdateTag::class)->execute( @@ -112,7 +116,7 @@ class ApiTagController extends ApiController * * @param Request $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function destroy(Request $request, $id) { @@ -129,4 +133,28 @@ class ApiTagController extends ApiController return $this->respondObjectDeleted($id); } + + /** + * Show all the contacts for a given tag. + * + * @param Request $request + * @param int $tagId + * @return JsonResponse|AnonymousResourceCollection + */ + public function contacts(Request $request, int $tagId) + { + try { + $contacts = auth()->user()->account->contacts() + ->real() + ->active() + ->whereHas('tags', function (Builder $query) use ($tagId) { + $query->where('id', $tagId); + }) + ->paginate($this->getLimitPerPage()); + } catch (QueryException $e) { + return $this->respondInvalidQuery(); + } + + return ContactWithContactFieldsResource::collection($contacts); + } } diff --git a/app/Models/Contact/Contact.php b/app/Models/Contact/Contact.php index 89a448a26..a867a3f59 100644 --- a/app/Models/Contact/Contact.php +++ b/app/Models/Contact/Contact.php @@ -484,8 +484,8 @@ class Contact extends Model * Scope a query to only include contacts who are not only a kid or a * significant other without being a contact. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @return Builder */ public function scopeReal($query) { @@ -495,8 +495,8 @@ class Contact extends Model /** * Scope a query to only include contacts who are active. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @return Builder */ public function scopeActive($query) { @@ -506,8 +506,8 @@ class Contact extends Model /** * Scope a query to only include contacts who are alive. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @return Builder */ public function scopeAlive($query) { @@ -517,8 +517,8 @@ class Contact extends Model /** * Scope a query to only include contacts who are dead. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @return Builder */ public function scopeDead($query) { @@ -528,8 +528,8 @@ class Contact extends Model /** * Scope a query to only include contacts who are not active. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder + * @param Builder $query + * @return Builder */ public function scopeNotActive($query) { @@ -1299,9 +1299,9 @@ class Contact extends Model /** * Get the contacts that have all the provided $tags * or if $tags is NONE get contacts that have no tags. - * @param \Illuminate\Database\Eloquent\Builder $query + * @param Builder $query * @param mixed $tags string or Tag - * @return \Illuminate\Database\Eloquent\Builder $query + * @return Builder $query */ public function scopeTags($query, $tags) { @@ -1311,7 +1311,7 @@ class Contact extends Model } elseif (! empty($tags)) { // gets users who have all the tags foreach ($tags as $tag) { - $query = $query->whereHas('tags', function ($query) use ($tag) { + $query = $query->whereHas('tags', function (Builder $query) use ($tag) { $query->where('id', $tag->id); }); } diff --git a/routes/api.php b/routes/api.php index 575550bec..e6c0ef908 100644 --- a/routes/api.php +++ b/routes/api.php @@ -59,6 +59,7 @@ Route::group(['middleware' => ['auth:api']], function () { // Tags Route::apiResource('tags', 'ApiTagController'); + Route::get('/tags/{tag}/contacts', 'ApiTagController@contacts'); // Companies Route::apiResource('companies', 'Account\\ApiCompanyController'); diff --git a/tests/Api/ApiTagControllerTest.php b/tests/Api/ApiTagControllerTest.php index 5891bd7ee..1584344ec 100644 --- a/tests/Api/ApiTagControllerTest.php +++ b/tests/Api/ApiTagControllerTest.php @@ -23,6 +23,118 @@ class ApiTagControllerTest extends ApiTestCase 'updated_at', ]; + protected $jsonStructureContactWithContactFields = [ + 'id', + 'object', + 'hash_id', + 'first_name', + 'last_name', + 'gender', + 'gender_type', + 'is_starred', + 'is_partial', + 'is_dead', + 'last_called', + 'last_activity_together', + 'stay_in_touch_frequency', + 'stay_in_touch_trigger_date', + 'information' => [ + 'relationships' => [ + 'love' => [ + 'total', + 'contacts', + ], + 'family' => [ + 'total', + 'contacts', + ], + 'friend' => [ + 'total', + 'contacts', + ], + 'work' => [ + 'total', + 'contacts', + ], + ], + 'dates' => [ + 'birthdate' => [ + 'is_age_based', + 'is_year_unknown', + 'date', + ], + 'deceased_date' => [ + 'is_age_based', + 'is_year_unknown', + 'date', + ], + ], + 'career' => [ + 'job', + 'company', + ], + 'avatar' => [ + 'url', + 'source', + 'default_avatar_color', + ], + 'food_preferences', + 'how_you_met' => [ + 'general_information', + 'first_met_date' => [ + 'is_age_based', + 'is_year_unknown', + 'date', + ], + 'first_met_through_contact', + ], + ], + 'addresses' => [], + 'tags' => [], + 'statistics' => [ + 'number_of_calls', + 'number_of_notes', + 'number_of_activities', + 'number_of_reminders', + 'number_of_tasks', + 'number_of_gifts', + 'number_of_debts', + ], + 'contactFields' => [ + '*' => [ + 'id', + 'object', + 'content', + 'contact_field_type' => [ + 'id', + 'object', + 'name', + 'fontawesome_icon', + 'protocol', + 'delible', + 'type', + 'account' => [ + 'id', + ], + 'created_at', + 'updated_at', + ], + 'account' => [ + 'id', + ], + 'contact' => [], + 'created_at', + 'updated_at', + ], + ], + 'notes' => [], + 'account' => [ + 'id', + ], + 'created_at', + 'updated_at', + ]; + /** @test */ public function it_get_all_tags() { @@ -217,4 +329,78 @@ class ApiTagControllerTest extends ApiTestCase 'id' => $tag->id, ]); } + + /** @test */ + public function it_gets_all_the_contacts_for_a_given_tag() + { + $user = $this->signin(); + + $tag = factory(Tag::class)->create([ + 'account_id' => $user->account->id, + ]); + factory(Contact::class, 10)->create([ + 'account_id' => $user->account_id, + ]); + for ($i = 0; $i < 3; $i++) { + $contact = factory(Contact::class)->create([ + 'account_id' => $user->account_id, + ]); + + $contact->tags()->sync([ + $tag->id => [ + 'account_id' => $user->account_id, + ], + ]); + } + + $response = $this->json('GET', '/api/tags/'.$tag->id.'/contacts'); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => ['*' => $this->jsonStructureContactWithContactFields], + ]); + + $this->assertCount( + 3, + $response->decodeResponseJson()['data'] + ); + } + + /** @test */ + public function it_gets_all_the_contacts_for_a_given_tag_and_applies_pagination() + { + $user = $this->signin(); + + $tag = factory(Tag::class)->create([ + 'account_id' => $user->account->id, + ]); + factory(Contact::class, 10)->create([ + 'account_id' => $user->account_id, + ]); + for ($i = 0; $i < 3; $i++) { + $contact = factory(Contact::class)->create([ + 'account_id' => $user->account_id, + ]); + + $contact->tags()->sync([ + $tag->id => [ + 'account_id' => $user->account_id, + ], + ]); + } + + $response = $this->json('GET', '/api/tags/'.$tag->id.'/contacts?limit=1'); + + $response->assertStatus(200); + $response->assertJsonStructure([ + 'data' => ['*' => $this->jsonStructureContactWithContactFields], + ]); + + $response->assertJsonFragment([ + 'total' => 3, + 'current_page' => 1, + 'per_page' => '1', + 'last_page' => 3, + ]); + } } diff --git a/tests/Api/Contact/ApiContactControllerTest.php b/tests/Api/Contact/ApiContactControllerTest.php index 5fefdb207..fcf8adc86 100644 --- a/tests/Api/Contact/ApiContactControllerTest.php +++ b/tests/Api/Contact/ApiContactControllerTest.php @@ -182,6 +182,7 @@ class ApiContactControllerTest extends ApiTestCase 'updated_at', ], ], + 'notes' => [], 'account' => [ 'id', ],