-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement DAV client subscriptions (#6751)
- Loading branch information
Showing
85 changed files
with
5,620 additions
and
104 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
app/Console/Commands/Local/UpdateAddressBookSubscription.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
namespace App\Console\Commands\Local; | ||
|
||
use App\Domains\Contact\DavClient\Jobs\SynchronizeAddressBooks; | ||
use App\Models\AddressBookSubscription; | ||
use Illuminate\Console\Command; | ||
|
||
class UpdateAddressBookSubscription extends Command | ||
{ | ||
/** | ||
* The name and signature of the console command. | ||
* | ||
* @var string | ||
*/ | ||
protected $signature = 'monica:updateaddressbooksubscription | ||
{--subscriptionId= : Id of the subscription to synchronize} | ||
{--force : Force sync}'; | ||
|
||
/** | ||
* The console command description. | ||
* | ||
* @var string | ||
*/ | ||
protected $description = 'Update a subscription'; | ||
|
||
/** | ||
* Execute the console command. | ||
*/ | ||
public function handle() | ||
{ | ||
$subscription = AddressBookSubscription::findOrFail($this->option('subscriptionId')); | ||
|
||
SynchronizeAddressBooks::dispatch($subscription, $this->option('force'))->onQueue('high'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
namespace App\Console\Commands; | ||
|
||
use App\Domains\Contact\DavClient\Jobs\SynchronizeAddressBooks; | ||
use App\Domains\Contact\DavClient\Services\CreateAddressBookSubscription; | ||
use App\Models\AddressBookSubscription; | ||
use App\Models\User; | ||
use App\Models\Vault; | ||
use Illuminate\Console\Command; | ||
use Illuminate\Database\Eloquent\ModelNotFoundException; | ||
|
||
class NewAddressBookSubscription extends Command | ||
{ | ||
/** | ||
* The name and signature of the console command. | ||
* | ||
* @var string | ||
*/ | ||
protected $signature = 'monica:newaddressbooksubscription | ||
{--email= : Monica account to add subscription to} | ||
{--vaultId= : Id of the vault to add subscription to} | ||
{--url= : CardDAV url of the address book} | ||
{--login= : Login} | ||
{--password= : Password of the account} | ||
{--pushonly : Set only push way} | ||
{--getonly : Set only get way}'; | ||
|
||
/** | ||
* The console command description. | ||
* | ||
* @var string | ||
*/ | ||
protected $description = 'Add a new dav subscription'; | ||
|
||
/** | ||
* Execute the console command. | ||
*/ | ||
public function handle(): int | ||
{ | ||
if (($user = $this->user()) === null) { | ||
return 1; | ||
} | ||
if (($vault = $this->vault()) === null) { | ||
return 2; | ||
} | ||
|
||
if ($user->account_id !== $vault->account_id) { | ||
$this->error('Vault does not belong to this account'); | ||
|
||
return 3; | ||
} | ||
|
||
$pushonly = $this->option('pushonly'); | ||
$getonly = $this->option('getonly'); | ||
if ($pushonly && $getonly) { | ||
$this->error('Cannot set both pushonly and getonly'); | ||
|
||
return 4; | ||
} | ||
|
||
$url = $this->option('url') ?? $this->ask('CardDAV url of the address book'); | ||
$login = $this->option('login') ?? $this->ask('Login name'); | ||
$password = $this->option('password') ?? $this->secret('User password'); | ||
|
||
try { | ||
$subscription = app(CreateAddressBookSubscription::class)->execute([ | ||
'account_id' => $user->account_id, | ||
'vault_id' => $vault->id, | ||
'author_id' => $user->id, | ||
'base_uri' => $url, | ||
'username' => $login, | ||
'password' => $password, | ||
]); | ||
|
||
if ($pushonly) { | ||
$subscription->sync_way = AddressBookSubscription::WAY_PUSH; | ||
} elseif ($getonly) { | ||
$subscription->sync_way = AddressBookSubscription::WAY_GET; | ||
} | ||
$subscription->save(); | ||
} catch (\Exception $e) { | ||
$this->error('Could not add subscription'); | ||
$this->error($e->getMessage()); | ||
|
||
return -1; | ||
} | ||
|
||
$this->info("Subscription added: {$subscription->id}"); | ||
SynchronizeAddressBooks::dispatch($subscription, true)->onQueue('high'); | ||
|
||
return 0; | ||
} | ||
|
||
private function user(): ?User | ||
{ | ||
if (($email = $this->option('email')) === null) { | ||
$this->error('Please provide an email address'); | ||
|
||
return null; | ||
} | ||
|
||
try { | ||
return User::where('email', $email)->firstOrFail(); | ||
} catch (ModelNotFoundException) { | ||
$this->error('Could not find user'); | ||
|
||
return null; | ||
} | ||
} | ||
|
||
private function vault(): ?Vault | ||
{ | ||
if (($vaultId = $this->option('vaultId')) === null) { | ||
$this->error('Please provide an vaultId'); | ||
|
||
return null; | ||
} | ||
|
||
try { | ||
return Vault::findOrFail($vaultId); | ||
} catch (ModelNotFoundException) { | ||
$this->error('Could not find vault'); | ||
|
||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
app/Domains/Contact/DavClient/Jobs/DeleteMultipleVCard.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
namespace App\Domains\Contact\DavClient\Jobs; | ||
|
||
use App\Models\AddressBookSubscription; | ||
use Illuminate\Bus\Batchable; | ||
use Illuminate\Bus\Queueable; | ||
use Illuminate\Contracts\Queue\ShouldQueue; | ||
use Illuminate\Queue\InteractsWithQueue; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class DeleteMultipleVCard implements ShouldQueue | ||
{ | ||
use Batchable, InteractsWithQueue, Queueable, SerializesModels; | ||
|
||
/** | ||
* Create a new job instance. | ||
*/ | ||
public function __construct( | ||
private AddressBookSubscription $subscription, | ||
private array $hrefs | ||
) { | ||
$this->subscription = $subscription->withoutRelations(); | ||
} | ||
|
||
/** | ||
* Update the Last Consulted At field for the given contact. | ||
*/ | ||
public function handle(): void | ||
{ | ||
if (! $this->batching()) { | ||
return; // @codeCoverageIgnore | ||
} | ||
|
||
$jobs = collect($this->hrefs) | ||
->map(fn (string $href): DeleteVCard => $this->deleteVCard($href)); | ||
|
||
$this->batch()->add($jobs); | ||
} | ||
|
||
/** | ||
* Delete the contact. | ||
*/ | ||
private function deleteVCard(string $href): DeleteVCard | ||
{ | ||
return new DeleteVCard($this->subscription, $href); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
|
||
namespace App\Domains\Contact\DavClient\Jobs; | ||
|
||
use App\Models\AddressBookSubscription; | ||
use Illuminate\Bus\Batchable; | ||
use Illuminate\Bus\Queueable; | ||
use Illuminate\Contracts\Queue\ShouldQueue; | ||
use Illuminate\Queue\InteractsWithQueue; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class DeleteVCard implements ShouldQueue | ||
{ | ||
use Batchable, InteractsWithQueue, Queueable, SerializesModels; | ||
|
||
/** | ||
* Create a new job instance. | ||
*/ | ||
public function __construct( | ||
private AddressBookSubscription $subscription, | ||
private string $uri | ||
) { | ||
$this->subscription = $subscription->withoutRelations(); | ||
} | ||
|
||
/** | ||
* Send Delete contact. | ||
*/ | ||
public function handle(): void | ||
{ | ||
$this->subscription->getClient() | ||
->request('DELETE', $this->uri); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
|
||
namespace App\Domains\Contact\DavClient\Jobs; | ||
|
||
use App\Domains\Contact\Dav\Jobs\UpdateVCard; | ||
use App\Models\AddressBookSubscription; | ||
use Illuminate\Bus\Batchable; | ||
use Illuminate\Bus\Queueable; | ||
use Illuminate\Contracts\Queue\ShouldQueue; | ||
use Illuminate\Queue\InteractsWithQueue; | ||
use Illuminate\Queue\SerializesModels; | ||
use Illuminate\Support\Arr; | ||
use Sabre\CardDAV\Plugin as CardDav; | ||
|
||
class GetMultipleVCard implements ShouldQueue | ||
{ | ||
use Batchable, InteractsWithQueue, Queueable, SerializesModels; | ||
|
||
/** | ||
* Create a new job instance. | ||
*/ | ||
public function __construct( | ||
private AddressBookSubscription $subscription, | ||
private array $hrefs | ||
) { | ||
$this->subscription = $subscription->withoutRelations(); | ||
} | ||
|
||
/** | ||
* Update the Last Consulted At field for the given contact. | ||
*/ | ||
public function handle(): void | ||
{ | ||
if (! $this->batching()) { | ||
return; // @codeCoverageIgnore | ||
} | ||
|
||
$data = $this->addressbookMultiget(); | ||
|
||
$jobs = collect($data) | ||
->filter(fn (array $contact): bool => is_array($contact) && $contact['status'] === '200') | ||
->map(fn (array $contact, string $href): ?UpdateVCard => $this->updateVCard($contact, $href)) | ||
->filter(); | ||
|
||
$this->batch()->add($jobs); | ||
} | ||
|
||
/** | ||
* Update the contact. | ||
*/ | ||
private function updateVCard(array $contact, string $href): ?UpdateVCard | ||
{ | ||
$card = Arr::get($contact, 'properties.200.{'.CardDav::NS_CARDDAV.'}address-data'); | ||
|
||
return $card === null | ||
? null | ||
: new UpdateVCard([ | ||
'account_id' => $this->subscription->vault->account_id, | ||
'author_id' => $this->subscription->user_id, | ||
'vault_id' => $this->subscription->vault_id, | ||
'uri' => $href, | ||
'etag' => Arr::get($contact, 'properties.200.{DAV:}getetag'), | ||
'card' => $card, | ||
'external' => true, | ||
]); | ||
} | ||
|
||
/** | ||
* Get addressbook data. | ||
*/ | ||
private function addressbookMultiget(): array | ||
{ | ||
return $this->subscription->getClient() | ||
->addressbookMultiget([ | ||
'{DAV:}getetag', | ||
$this->getAddressDataProperty(), | ||
], $this->hrefs); | ||
} | ||
|
||
/** | ||
* Get data for address-data property. | ||
*/ | ||
private function getAddressDataProperty(): array | ||
{ | ||
$addressDataAttributes = Arr::get($this->subscription->capabilities, 'addressData', [ | ||
'content-type' => 'text/vcard', | ||
'version' => '4.0', | ||
]); | ||
|
||
return [ | ||
'name' => '{'.CardDav::NS_CARDDAV.'}address-data', | ||
'value' => null, | ||
'attributes' => $addressDataAttributes, | ||
]; | ||
} | ||
} |
Oops, something went wrong.