refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s

This commit is contained in:
Kroonk
2026-05-22 16:19:55 +02:00
parent 38cc4c09ca
commit a213a1dba0
2215 changed files with 242567 additions and 6015 deletions

View File

@@ -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),
]);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Jobs\Dav;
use Illuminate\Support\Arr;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Sabre\CardDAV\Plugin as CardDAVPlugin;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class GetMultipleVCard 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
}
$datas = $this->subscription->getClient()
->addressbookMultiget([
'{DAV:}getetag',
$this->getAddressDataProperty(),
], $this->hrefs);
collect($datas)
->filter(function (array $contact): bool {
return isset($contact[200]);
})
->each(function (array $contact, $href) {
$this->updateVCard($contact, $href);
});
}
/**
* Update the contact.
*
* @param array $contact
* @param string $href
* @return void
*/
private function updateVCard(array $contact, $href): void
{
$card = Arr::get($contact, '200.{'.CardDAVPlugin::NS_CARDDAV.'}address-data');
if ($card !== null) {
$dto = new ContactUpdateDto($href, Arr::get($contact, '200.{DAV:}getetag'), $card);
if (($batch = $this->batch()) !== null) {
$batch->add([
new UpdateVCard($this->subscription->user, $this->subscription->addressbook->name, $dto),
]);
}
}
}
/**
* Get data for address-data property.
*
* @return array
*/
private function getAddressDataProperty(): array
{
$addressDataAttributes = Arr::get($this->subscription->capabilities, 'addressData', [
'content-type' => 'text/vcard',
'version' => '4.0',
]);
return [
'name' => '{'.CardDAVPlugin::NS_CARDDAV.'}address-data',
'value' => null,
'attributes' => $addressDataAttributes,
];
}
}

71
app/Jobs/Dav/GetVCard.php Normal file
View File

@@ -0,0 +1,71 @@
<?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;
use App\Services\DavClient\Utils\Model\ContactDto;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class GetVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var AddressBookSubscription
*/
private $subscription;
/**
* @var ContactDto
*/
private $contact;
/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param ContactDto $contact
* @return void
*/
public function __construct(AddressBookSubscription $subscription, ContactDto $contact)
{
$this->subscription = $subscription->withoutRelations();
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
Log::info(__CLASS__.' '.$this->contact->uri);
$response = $this->subscription->getClient()
->request('GET', $this->contact->uri);
$this->chainUpdateVCard($response->body());
}
private function chainUpdateVCard(string $card): void
{
$dto = new ContactUpdateDto($this->contact->uri, $this->contact->etag, $card);
if (($batch = $this->batch()) !== null) {
$batch->add([
new UpdateVCard($this->subscription->user, $this->subscription->addressbook->name, $dto),
]);
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Jobs\Dav;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Model\ContactPushDto;
class PushVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var AddressBookSubscription
*/
private $subscription;
/**
* @var ContactPushDto
*/
private $contact;
/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param ContactPushDto $contact
* @return void
*/
public function __construct(AddressBookSubscription $subscription, ContactPushDto $contact)
{
$this->subscription = $subscription->withoutRelations();
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
Log::info(__CLASS__.' '.$this->contact->uri);
$headers = [];
switch ($this->contact->mode) {
case ContactPushDto::MODE_MATCH_ETAG:
$headers['If-Match'] = $this->contact->etag;
break;
case ContactPushDto::MODE_MATCH_ANY:
$headers['If-Match'] = '*';
break;
}
$response = $this->subscription->getClient()
->request('PUT', $this->contact->uri, $this->contact->card, $headers);
$etag = $response->header('Etag');
$contact = Contact::where('account_id', $this->subscription->account_id)
->findOrFail($this->contact->contactId);
$contact->distant_etag = empty($etag) ? null : $etag;
$contact->save();
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace App\Jobs\Dav;
use App\Models\User\User;
use Illuminate\Support\Arr;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use App\Services\VCard\GetEtag;
use App\Services\VCard\ImportVCard;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Traits\Localizable;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
use App\Http\Controllers\DAV\Backend\CardDAV\CardDAVBackend;
class UpdateVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels, Localizable;
/**
* @var User
*/
private $user;
/**
* @var string
*/
private $addressBookName;
/**
* @var ContactUpdateDto
*/
private $contact;
/**
* Create a new job instance.
*
* @param User $user
* @param string $addressBookName
* @param ContactUpdateDto $contact
* @return void
*/
public function __construct(User $user, string $addressBookName, ContactUpdateDto $contact)
{
$this->user = $user->withoutRelations();
$this->addressBookName = $addressBookName;
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
$this->withLocale($this->user->preferredLocale(), function () {
$newtag = $this->updateCard($this->addressBookName, $this->contact->uri, $this->contact->card);
if (! is_null($this->contact->etag) && $newtag !== $this->contact->etag) {
Log::warning(__CLASS__.' '.__FUNCTION__.' wrong etag when updating contact. Expected '.$this->contact->etag.', get '.$newtag, [
'contacturl' => $this->contact->uri,
'carddata' => $this->contact->card,
]);
}
});
}
/**
* Update the contact with the carddata.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
private function updateCard($addressBookId, $cardUri, $cardData): ?string
{
$backend = app(CardDAVBackend::class)->init($this->user);
$contact_id = null;
if ($cardUri) {
$contactObject = $backend->getObject($addressBookId, $cardUri);
if ($contactObject) {
$contact_id = $contactObject->id;
}
}
try {
$result = app(ImportVCard::class)
->execute([
'account_id' => $this->user->account_id,
'user_id' => $this->user->id,
'contact_id' => $contact_id,
'entry' => $cardData,
'etag' => $this->contact->etag,
'behaviour' => ImportVCard::BEHAVIOUR_REPLACE,
'addressBookName' => $addressBookId === $backend->backendUri() ? null : $addressBookId,
]);
if (! Arr::has($result, 'error')) {
return app(GetEtag::class)->execute([
'account_id' => $this->user->account_id,
'contact_id' => $result['contact_id'],
]);
}
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.': '.$e->getMessage(), [
'contacturl' => $cardUri,
'contact_id' => $contact_id,
'carddata' => $cardData,
$e,
]);
throw $e;
}
return null;
}
}