Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement DAV client subscriptions #6751

Merged
merged 78 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a882794
feat: implement DAV client subscriptions
asbiin Jul 8, 2023
5ce031e
fixes
asbiin Jul 8, 2023
f030cff
tests
asbiin Jul 14, 2023
a3badf5
fix
asbiin Jul 14, 2023
0141686
add test
asbiin Jul 14, 2023
a393d1f
fix
asbiin Jul 14, 2023
c04c65d
fix
asbiin Jul 14, 2023
ac31743
test
asbiin Jul 14, 2023
0b4d0d4
update
asbiin Jul 14, 2023
7e89b78
update
asbiin Jul 14, 2023
8c228a9
fix
asbiin Jul 14, 2023
73b671e
simplifications
asbiin Jul 15, 2023
7ef3583
fix test
asbiin Jul 15, 2023
d83de89
test
asbiin Jul 15, 2023
00396f9
test
asbiin Jul 15, 2023
4e4951c
test
asbiin Jul 15, 2023
988d8e7
fix
asbiin Jul 15, 2023
8840697
update
asbiin Jul 16, 2023
b24fe4e
update
asbiin Jul 21, 2023
157e28d
Add tests
asbiin Jul 21, 2023
915c75e
add test
asbiin Jul 22, 2023
6d3f921
update
asbiin Jul 22, 2023
27e8556
Merge branch '20230708-davclient' of github.com:monicahq/monica into …
asbiin Jul 22, 2023
c16c54d
update and fixes
asbiin Jul 24, 2023
aba60a2
fix phpstan
asbiin Jul 24, 2023
a2d3c1d
fix
asbiin Jul 24, 2023
2411418
fix
asbiin Jul 24, 2023
876e6eb
update
asbiin Jul 24, 2023
18a32d8
missing change
asbiin Jul 24, 2023
dc36cd2
forgot to save those files
asbiin Jul 25, 2023
622a644
add debug infos
asbiin Jul 25, 2023
1cf6613
I don't get it
asbiin Jul 25, 2023
edf1c81
add more test
asbiin Jul 26, 2023
2146357
RunClassInSeparateProcess
asbiin Jul 27, 2023
067c22b
test
asbiin Jul 28, 2023
832bc1b
change order
asbiin Jul 28, 2023
6b55a70
separate tests
asbiin Jul 29, 2023
c723273
fix tests
asbiin Jul 29, 2023
9c75588
update
asbiin Jul 29, 2023
8451d36
fix
asbiin Jul 29, 2023
f16b800
update
asbiin Jul 29, 2023
12e1d94
update
asbiin Jul 29, 2023
34684e8
finalize
asbiin Jul 29, 2023
97aadcf
big update
asbiin Jul 31, 2023
d5fff21
fix
asbiin Jul 31, 2023
88678ca
fixes
asbiin Jul 31, 2023
4efd85d
updates
asbiin Aug 1, 2023
23e08ff
fix
asbiin Aug 2, 2023
5670724
fix phpstan
asbiin Aug 2, 2023
bf977a5
update
asbiin Aug 3, 2023
3fcf454
update
asbiin Aug 3, 2023
b6de98c
update
asbiin Aug 4, 2023
1169eb7
import groups
asbiin Aug 4, 2023
1bc9b10
Add ability to export groups
asbiin Aug 10, 2023
429702c
fixes
asbiin Aug 11, 2023
fb89c04
improve export
asbiin Aug 12, 2023
a59c5f2
fixes
asbiin Aug 12, 2023
4cd560f
remove vcardtype
asbiin Aug 12, 2023
cb2b511
update
asbiin Aug 13, 2023
d362f08
fix phpstan
asbiin Aug 14, 2023
9d28209
factorize
asbiin Aug 15, 2023
76f5548
Merge branch 'chandler' into 20230708-davclient
asbiin Aug 20, 2023
7afb1cc
Merge branch 'chandler' into 20230708-davclient
asbiin Aug 20, 2023
965d1c8
Merge branch 'chandler' into 20230708-davclient
asbiin Aug 23, 2023
95fd567
up
asbiin Aug 23, 2023
7f3ab56
Merge remote-tracking branch 'origin/chandler' into 20230708-davclient
asbiin Aug 23, 2023
aaf5593
Merge remote-tracking branch 'origin/chandler' into 20230708-davclient
asbiin Aug 24, 2023
e445feb
remove useless change
asbiin Aug 25, 2023
ec91d6e
remove some logs
asbiin Aug 25, 2023
27c7376
add withlocale
asbiin Aug 25, 2023
54c5b68
fix
asbiin Aug 25, 2023
8f2c7fb
fix
asbiin Aug 25, 2023
e1bc2b1
add dump
asbiin Aug 25, 2023
76e78b4
try
asbiin Aug 26, 2023
5fe40cc
try
asbiin Aug 26, 2023
4e1b18d
fiiiiix
asbiin Aug 26, 2023
8f67818
update
asbiin Aug 28, 2023
1caccfc
fixes
asbiin Aug 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions app/Console/Commands/Local/UpdateAddressBookSubscription.php
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');
}
}
128 changes: 128 additions & 0 deletions app/Console/Commands/NewAddressBookSubscription.php
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;
}
}
}
2 changes: 2 additions & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Console\Scheduling\CronEvent;
use App\Domains\Contact\Dav\Jobs\CleanSyncToken;
use App\Domains\Contact\DavClient\Jobs\UpdateAddressBooks;
use App\Domains\Contact\ManageReminders\Jobs\ProcessScheduledContactReminders;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
Expand Down Expand Up @@ -40,6 +41,7 @@ protected function schedule(Schedule $schedule)
if (config('telescope.enabled')) {
$this->scheduleCommand($schedule, 'telescope:prune', 'daily');
}
$this->scheduleJob($schedule, UpdateAddressBooks::class, 'hourly');
$this->scheduleJob($schedule, ProcessScheduledContactReminders::class, 'minutes', 1);
$this->scheduleJob($schedule, CleanSyncToken::class, 'daily');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,12 +485,12 @@ public function deleteCard($addressBookId, $cardUri): bool

return true;
} elseif ($obj !== null && $obj instanceof Group) {
(new DestroyGroup)->execute([
DestroyGroup::dispatch([
'account_id' => $this->user->account_id,
'author_id' => $this->user->id,
'vault_id' => $obj->vault_id,
'group_id' => $obj->id,
]);
])->onQueue('high');

return true;
}
Expand Down
48 changes: 48 additions & 0 deletions app/Domains/Contact/DavClient/Jobs/DeleteMultipleVCard.php
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);
}
}
34 changes: 34 additions & 0 deletions app/Domains/Contact/DavClient/Jobs/DeleteVCard.php
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);
}
}
96 changes: 96 additions & 0 deletions app/Domains/Contact/DavClient/Jobs/GetMultipleVCard.php
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,
];
}
}
Loading