Skip to content

Commit

Permalink
feat: sync carddav delete contact requests (#5835)
Browse files Browse the repository at this point in the history
  • Loading branch information
asbiin authored Jan 5, 2022
1 parent d76af04 commit 30d97f9
Show file tree
Hide file tree
Showing 14 changed files with 500 additions and 23 deletions.
72 changes: 72 additions & 0 deletions app/Jobs/Dav/DeleteMultipleVCard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Jobs\Dav;

use Illuminate\Bus\Batch;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;

class DeleteMultipleVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* @var AddressBookSubscription
*/
private $subscription;

/**
* @var array
*/
private $hrefs;

/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param array $hrefs
* @return void
*/
public function __construct(AddressBookSubscription $subscription, array $hrefs)
{
$this->subscription = $subscription->withoutRelations();
$this->hrefs = $hrefs;
}

/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return; // @codeCoverageIgnore
}

$batch = $this->batch();

collect($this->hrefs)
->each(function ($href) use ($batch) {
$this->deleteVCard($href, $batch);
});
}

/**
* Delete the contact.
*
* @param string $href
* @param \Illuminate\Bus\Batch $batch
* @return void
*/
private function deleteVCard(string $href, Batch $batch): void
{
$batch->add([
new DeleteVCard($this->subscription, $href),
]);
}
}
56 changes: 56 additions & 0 deletions app/Jobs/Dav/DeleteVCard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Jobs\Dav;

use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;

class DeleteVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* @var AddressBookSubscription
*/
private $subscription;

/**
* @var string
*/
private $uri;

/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param string $uri
* @return void
*/
public function __construct(AddressBookSubscription $subscription, string $uri)
{
$this->subscription = $subscription->withoutRelations();
$this->uri = $uri;
}

/**
* Send Delete contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}

Log::info(__CLASS__.' '.$this->uri);

$this->subscription->getClient()
->request('DELETE', $this->uri);
}
}
2 changes: 1 addition & 1 deletion app/Jobs/Dav/GetMultipleVCard.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function __construct(AddressBookSubscription $subscription, array $hrefs)
public function handle(): void
{
if (! $this->batching()) {
return;
return; // @codeCoverageIgnore
}

$datas = $this->subscription->getClient()
Expand Down
21 changes: 20 additions & 1 deletion app/Services/DavClient/Utils/AddressBookContactsPush.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Services\DavClient\Utils;

use App\Jobs\Dav\PushVCard;
use App\Jobs\Dav\DeleteVCard;
use Illuminate\Support\Collection;
use IlluminateAgnostic\Collection\Support\Arr;
use App\Services\DavClient\Utils\Model\SyncDto;
Expand All @@ -28,8 +29,11 @@ public function execute(SyncDto $sync, Collection $changes, ?array $localChanges

$changes = $this->preparePushChangedContacts($changes, Arr::get($localChanges, 'modified', []));
$added = $this->preparePushAddedContacts(Arr::get($localChanges, 'added', []));
$deleted = $this->prepareDeletedContacts(Arr::get($localChanges, 'deleted', []));

return $changes->union($added)
return $changes
->union($added)
->union($deleted)
->filter(function ($c) {
return $c !== null;
});
Expand Down Expand Up @@ -60,6 +64,21 @@ private function preparePushAddedContacts(array $contacts): Collection
});
}

/**
* Get list of requests to delete contacts.
*
* @param array $contacts
* @return Collection
*/
private function prepareDeletedContacts(array $contacts): Collection
{
// All removed contact must be deleted
return collect($contacts)
->map(function (string $uri): DeleteVCard {
return new DeleteVCard($this->sync->subscription, $uri);
});
}

/**
* Get list of requests to push modified contacts.
*
Expand Down
34 changes: 27 additions & 7 deletions app/Services/DavClient/Utils/AddressBookContactsUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
namespace App\Services\DavClient\Utils;

use App\Jobs\Dav\GetVCard;
use App\Jobs\Dav\DeleteVCard;
use App\Jobs\Dav\GetMultipleVCard;
use Illuminate\Support\Collection;
use App\Jobs\Dav\DeleteMultipleVCard;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
use App\Services\DavClient\Utils\Traits\WithSyncDto;
use App\Services\DavClient\Utils\Traits\HasCapability;
use App\Services\DavClient\Utils\Model\ContactDeleteDto;

class AddressBookContactsUpdater
{
Expand Down Expand Up @@ -37,23 +41,39 @@ public function execute(SyncDto $sync, Collection $refresh): Collection
*/
private function refreshMultigetContacts(Collection $refresh): Collection
{
$hrefs = $refresh->pluck('uri')->toArray();
$updated = $refresh
->filter(function ($item): bool {
return ! ($item instanceof ContactDeleteDto);
})
->pluck('uri')->toArray();

$deleted = $refresh
->filter(function ($item): bool {
return $item instanceof ContactDeleteDto;
})
->pluck('uri')->toArray();

return collect([
new GetMultipleVCard($this->sync->subscription, $hrefs),
new GetMultipleVCard($this->sync->subscription, $updated),
new DeleteMultipleVCard($this->sync->subscription, $deleted),
]);
}

/**
* Get contacts data with request.
*
* @param Collection<array-key, \App\Services\DavClient\Utils\Model\ContactDto> $requests
* @param Collection<array-key, \App\Services\DavClient\Utils\Model\ContactDto> $refresh
* @return Collection
*/
private function refreshSimpleGetContacts(Collection $requests): Collection
private function refreshSimpleGetContacts(Collection $refresh): Collection
{
return $requests->map(function ($contact): GetVCard {
return new GetVCard($this->sync->subscription, $contact);
});
return $refresh
->map(function (ContactDto $contact) {
if ($contact instanceof ContactDeleteDto) {
return new DeleteVCard($this->sync->subscription, $contact->uri);
} else {
return new GetVCard($this->sync->subscription, $contact);
}
});
}
}
36 changes: 27 additions & 9 deletions app/Services/DavClient/Utils/AddressBookSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Services\DavClient\Utils\Model\ContactDto;
use App\Services\DavClient\Utils\Traits\WithSyncDto;
use App\Services\DavClient\Utils\Traits\HasCapability;
use App\Services\DavClient\Utils\Model\ContactDeleteDto;
use App\Services\DavClient\UpdateSubscriptionLocalSyncToken;

class AddressBookSynchronizer
Expand Down Expand Up @@ -113,13 +114,22 @@ private function forcesync()
*/
private function getDistantChanges(): Collection
{
return collect($this->getDistantEtags())
->filter(function ($contact, $href): bool {
return $this->filterDistantContacts($contact, $href);
})
$etags = collect($this->getDistantEtags());
$contacts = $etags->filter(function ($contact, $href): bool {
return $this->filterDistantContacts($contact, $href);
})
->map(function ($contact, $href): ContactDto {
return new ContactDto($href, Arr::get($contact, '200.{DAV:}getetag'));
});

$deleted = $etags->filter(function ($contact): bool {
return isset($contact['404']);
})
->map(function ($contact, $href): ContactDto {
return new ContactDeleteDto($href);
});

return $contacts->union($deleted);
}

/**
Expand Down Expand Up @@ -214,14 +224,22 @@ private function getAllContactsEtag(): Collection
return collect();
}

$datas = $this->sync->addressbookQuery('{DAV:}getetag');
$data = $this->sync->addressbookQuery('{DAV:}getetag');
$data = collect($data);

return collect($datas)
->filter(function ($contact) {
return isset($contact[200]);
})
$updated = $data->filter(function ($contact): bool {
return isset($contact[200]);
})
->map(function ($contact, $href): ContactDto {
return new ContactDto($href, Arr::get($contact, '200.{DAV:}getetag'));
});
$deleted = $data->filter(function ($contact): bool {
return isset($contact[404]);
})
->map(function ($contact, $href): ContactDto {
return new ContactDeleteDto($href);
});

return $updated->union($deleted);
}
}
7 changes: 7 additions & 0 deletions app/Services/DavClient/Utils/Model/ContactDeleteDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace App\Services\DavClient\Utils\Model;

class ContactDeleteDto extends ContactDto
{
}
2 changes: 1 addition & 1 deletion app/Services/DavClient/Utils/Model/ContactDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ContactDto
* @param string $uri
* @param string|null $etag
*/
public function __construct(string $uri, ?string $etag)
public function __construct(string $uri, ?string $etag = null)
{
$this->uri = $uri;
$this->etag = $etag;
Expand Down
52 changes: 52 additions & 0 deletions tests/Unit/Jobs/Dav/DeleteMultipleVCardTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Tests\Unit\Jobs\Dav;

use Tests\TestCase;
use App\Models\User\User;
use App\Jobs\Dav\DeleteVCard;
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;
use App\Jobs\Dav\DeleteMultipleVCard;
use Illuminate\Bus\DatabaseBatchRepository;
use App\Models\Account\AddressBookSubscription;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class DeleteMultipleVCardTest extends TestCase
{
use DatabaseTransactions;

/** @test */
public function it_delete_cards()
{
$fake = Bus::fake();

$user = factory(User::class)->create();
$addressBookSubscription = AddressBookSubscription::factory()->create([
'account_id' => $user->account_id,
'user_id' => $user->id,
]);

$pendingBatch = $fake->batch([
$job = new DeleteMultipleVCard($addressBookSubscription, ['https://test/dav/uri']),
]);
$batch = $pendingBatch->dispatch();

$fake->assertBatched(function (PendingBatch $pendingBatch) {
$this->assertCount(1, $pendingBatch->jobs);
$this->assertInstanceOf(DeleteMultipleVCard::class, $pendingBatch->jobs->first());

return true;
});

$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
$job->withBatchId($batch->id)->handle();

$fake->assertDispatched(function (DeleteVCard $updateVCard) {
$uri = $this->getPrivateValue($updateVCard, 'uri');
$this->assertEquals('https://test/dav/uri', $uri);

return true;
});
}
}
Loading

0 comments on commit 30d97f9

Please sign in to comment.