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,48 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use App\Models\Account\ImportJob;
use App\Services\VCard\ImportVCard;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class AddContactFromVCard implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var ImportJob
*/
protected $importJob;
/**
* @var string
*/
protected $behaviour;
/**
* Create a new job instance.
*
* @param ImportJob $importJob
* @param string $behaviour
* @return void
*/
public function __construct(ImportJob $importJob, string $behaviour = ImportVCard::BEHAVIOUR_ADD)
{
$this->importJob = $importJob;
$this->behaviour = $behaviour;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->importJob->process($this->behaviour);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Jobs\AuditLog;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Instance\AuditLog\LogAccountAction;
class LogAccountAudit implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The audit log instance.
*
* @var array
*/
public $auditLog;
/**
* Create a new job instance.
*
* @param array $auditLog
*/
public function __construct(array $auditLog)
{
$this->auditLog = $auditLog;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
app(LogAccountAction::class)->execute(
$this->auditLog
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Jobs\Avatars;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
/**
* This creates all the avatars (default, adorable and gravatars) for existing
* contacts.
*/
class CreateAvatarsForExistingContacts implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 5;
/**
* Determine the time at which this job should timeout.
*
* @return \Carbon\Carbon
*/
public function retryUntil()
{
$totalContact = Contact::whereNull('avatar_adorable_url')
->orWhere('avatar_default_url', 'not like', 'avatars/%')
->count();
return now()->addSeconds($totalContact / 500);
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$delay = $this->retryUntil();
Contact::without(['account', 'avatarPhoto', 'gender'])
->whereNull('avatar_adorable_url')
->orWhere('avatar_default_url', 'not like', 'avatars/%')
->chunk(1000, function ($contacts) use ($delay) {
foreach ($contacts as $contact) {
GetAvatarsFromInternet::dispatch($contact)
->delay($delay);
GenerateDefaultAvatar::dispatch($contact)
->delay($delay);
}
$delay = $delay->addMinutes(1);
});
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Jobs\Avatars;
use App\Helpers\StringHelper;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Contact\Avatar\GenerateDefaultAvatar as GenerateDefaultAvatarService;
class GenerateDefaultAvatar implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 1;
/**
* Contact to treat.
*
* @var Contact
*/
public $contact;
/**
* Create a new job instance.
*
* @param Contact $contact
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (StringHelper::isNullOrWhitespace($this->contact->default_avatar_color)) {
$this->contact->setAvatarColor();
$this->contact->save();
}
// generate the default avatar
app(GenerateDefaultAvatarService::class)->execute([
'contact_id' => $this->contact->id,
]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Jobs\Avatars;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Contact\Avatar\GetAvatarsFromInternet as GetAvatarsFromInternetService;
class GetAvatarsFromInternet implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 1;
/**
* Contact to treat.
*
* @var Contact
*/
public $contact;
/**
* Create a new job instance.
*
* @param Contact $contact
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// generate the default avatar
app(GetAvatarsFromInternetService::class)->execute([
'contact_id' => $this->contact->id,
]);
}
}

View File

@@ -0,0 +1,196 @@
<?php
namespace App\Jobs\Avatars;
use App\Models\Account\Photo;
use Illuminate\Bus\Queueable;
use App\Events\MoveAvatarEvent;
use App\Models\Contact\Contact;
use Illuminate\Support\Facades\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\InteractsWithQueue;
use App\Exceptions\FileNotFoundException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Contact\Avatar\UpdateAvatar;
class MoveContactAvatarToPhotosDirectory implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 1;
/**
* @var Contact
*/
private $contact;
/**
* @var bool
*/
private $dryrun;
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem
*/
private $storage;
/**
* Create a new job instance.
*
* @param Contact $contact
* @param bool $dryrun
* @return void
*/
public function __construct(Contact $contact, $dryrun)
{
$this->contact = $contact;
$this->dryrun = $dryrun;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->storage = Storage::disk($this->contact->avatar_location);
// move avatar to new location
$avatarFileName = $this->moveContactAvatars();
if ($this->dryrun) {
return;
}
// create a Photo object for this avatar
$photo = $this->createPhotoObject($avatarFileName);
// associate the Photo object to the contact
$this->associatePhotoAsAvatar($photo);
// delete original avatar
$this->deleteOriginalAvatar($avatarFileName);
// delete thumbnails of avatars
$this->deleteThumbnails();
}
/**
* @return string|null
*/
private function moveContactAvatars(): ?string
{
Event::dispatch(new MoveAvatarEvent($this->contact));
$newStorage = Storage::disk(config('filesystems.default'));
$avatarFileName = $this->getAvatarFileName();
// $avatarFileName has the format `avatars/XXX.jpg`. We need to remove
// the `avatars/` string to store the new file.
$newAvatarFilename = str_replace('avatars/', 'photos/', $avatarFileName);
if ($newStorage->exists($newAvatarFilename)) {
return null;
}
if (! $this->dryrun) {
$avatarFile = $this->storage->get($avatarFileName);
$newStorage->put($newAvatarFilename, $avatarFile, config('filesystems.default_visibility'));
$this->contact->avatar_location = config('filesystems.default');
$this->contact->save();
}
return $avatarFileName;
}
/**
* @param string|null $avatarFileName
* @return Photo|null
*/
private function createPhotoObject($avatarFileName): ?Photo
{
if (is_null($avatarFileName)) {
return null;
}
$newAvatarFilename = str_replace('avatars/', '', $avatarFileName);
$photo = new Photo;
$photo->account_id = $this->contact->account_id;
$photo->original_filename = $newAvatarFilename;
$photo->new_filename = 'photos/'.$newAvatarFilename;
$photo->filesize = Storage::disk($this->contact->avatar_location)->size('/photos/'.$newAvatarFilename);
$photo->mime_type = 'adfad';
$photo->save();
return $photo;
}
private function associatePhotoAsAvatar($photo)
{
if (is_null($photo)) {
return;
}
$data = [
'account_id' => $this->contact->account_id,
'contact_id' => $this->contact->id,
'source' => 'photo',
'photo_id' => $photo->id,
];
app(UpdateAvatar::class)->execute($data);
}
private function deleteThumbnails()
{
try {
$smallThumbnail = $this->getAvatarFileName(110);
$this->storage->delete($smallThumbnail);
} catch (FileNotFoundException $e) {
// ignore
}
try {
$bigThumbnail = $this->getAvatarFileName(174);
$this->storage->delete($bigThumbnail);
} catch (FileNotFoundException $e) {
// ignore
}
}
private function deleteOriginalAvatar($avatarFileName)
{
$this->storage->delete($avatarFileName);
}
private function getAvatarFileName($size = null)
{
$filename = pathinfo($this->contact->avatar_file_name, PATHINFO_FILENAME);
$extension = pathinfo($this->contact->avatar_file_name, PATHINFO_EXTENSION);
$avatarFileName = 'avatars/'.$filename.'.'.$extension;
if (! is_null($size)) {
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;
}
if (! $this->fileExists($avatarFileName)) {
throw new FileNotFoundException($avatarFileName);
}
return $avatarFileName;
}
private function fileExists($avatarFileName): bool
{
return $this->storage->exists($avatarFileName);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Jobs\Avatars;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateAllGravatars implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$contacts = Contact::where('avatar_source', 'gravatar')
->orWhere('avatar_gravatar_url', '<>', '')
->active()
->get();
foreach ($contacts as $contact) {
UpdateGravatar::dispatch($contact);
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Jobs\Avatars;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Contact\Avatar\GetGravatar;
class UpdateGravatar implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 1;
/**
* Contact to treat.
*
* @var Contact
*/
public $contact;
/**
* Create a new job instance.
*
* @param Contact $contact
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// generate the default avatar
app(GetGravatar::class)->execute([
'contact_id' => $this->contact->id,
]);
}
}

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

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Jobs;
use Throwable;
use Illuminate\Http\File;
use Illuminate\Bus\Queueable;
use App\Helpers\StorageHelper;
use App\Models\Account\ExportJob;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Account\Settings\SqlExportAccount;
use App\Services\Account\Settings\JsonExportAccount;
class ExportAccount implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var string
*/
protected $path = '';
/**
* Export job.
*
* @var ExportJob
*/
protected $exportJob;
/**
* Create a new job instance.
*
* @param ExportJob $exportJob
* @param string|null $path
*/
public function __construct(ExportJob $exportJob, string $path = null)
{
$exportJob->status = ExportJob::EXPORT_TODO;
$exportJob->save();
$this->exportJob = $exportJob->withoutRelations();
$this->path = $path ?? 'exports';
}
/**
* Execute the job.
*/
public function handle()
{
$this->exportJob->start();
$tempFileName = '';
$handler = $this->exportJob->type === ExportJob::JSON ?
app(JsonExportAccount::class) :
app(SqlExportAccount::class);
try {
$tempFileName = $handler->execute([
'account_id' => $this->exportJob->account_id,
'user_id' => $this->exportJob->user_id,
]);
// get the temp file that we just created
$tempFilePath = StorageHelper::disk('local')->path($tempFileName);
// move the file to the public storage
$file = StorageHelper::disk(config('filesystems.default'))
->putFileAs($this->path, new File($tempFilePath), basename($tempFileName));
$this->exportJob->location = config('filesystems.default');
$this->exportJob->filename = $file;
$this->exportJob->end();
} catch (Throwable $e) {
$this->fail($e);
} finally {
// delete old file from temp folder
$storage = Storage::disk('local');
if ($storage->exists($tempFileName)) {
$storage->delete($tempFileName);
}
}
}
/**
* Handle a job failure.
*
* @param \Throwable $exception
*/
public function failed(Throwable $exception): void
{
$this->exportJob->status = ExportJob::EXPORT_FAILED;
$this->exportJob->save();
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Jobs;
use App\Helpers\DBHelper;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Bus\Dispatchable;
class ExportAllAsSQL
{
use Dispatchable;
/**
* Execute the job.
*
* @return string
*/
public function handle()
{
$sql = '# ************************************************************
# Monica data export.
# Export date: '.now().'
# ************************************************************
'.PHP_EOL;
$tables = DBHelper::getTables();
// Looping over the tables
foreach ($tables as $table) {
$tableName = $table->table_name;
$tableData = DB::table($tableName)->get();
// Looping over the rows
foreach ($tableData as $data) {
$newSQLLine = 'INSERT INTO '.$tableName.' (';
$tableValues = [];
// Looping over the column names
$tableColumnNames = [];
foreach ($data as $columnName => $value) {
array_push($tableColumnNames, $columnName);
}
$newSQLLine .= implode(',', $tableColumnNames).') VALUES (';
// Looping over the values
foreach ($data as $columnName => $value) {
if (is_null($value)) {
$value = 'NULL';
} elseif (! is_numeric($value)) {
$value = "'".addslashes($value)."'";
}
array_push($tableValues, $value);
}
$newSQLLine .= implode(',', $tableValues).');'.PHP_EOL;
$sql .= $newSQLLine;
}
}
$filename = 'export-all-'.time().'.sql';
Storage::disk('local')->put($filename, $sql);
return $filename;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace App\Jobs;
use App\Models\Account\Place;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\Middleware\RateLimited;
use App\Exceptions\RateLimitedSecondException;
use App\Services\Instance\Geolocalization\GetGPSCoordinate as GetGPSCoordinateService;
class GetGPSCoordinate implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var Place
*/
protected $place;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 10;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* @var int
*/
public $maxExceptions = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Place $place)
{
$this->place = $place->withoutRelations();
}
/**
* Get the middleware the job should pass through.
*
* @return array
*/
public function middleware()
{
return [
new RateLimited('GPSCoordinate'),
];
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (($batch = $this->batch()) !== null && $batch->cancelled()) {
return;
}
try {
app(GetGPSCoordinateService::class)->execute([
'account_id' => $this->place->account_id,
'place_id' => $this->place->id,
]);
} catch (RateLimitedSecondException $e) {
$this->release(15);
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Jobs;
use App\Models\Account\Place;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use App\Exceptions\NoCoordinatesException;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\Instance\Weather\GetWeatherInformation as GetWeatherInformationService;
class GetWeatherInformation implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var Place
*/
protected $place;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 10;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* @var int
*/
public $maxExceptions = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Place $place)
{
$this->place = $place->withoutRelations();
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (! $this->batching()) {
return;
}
if (is_null($this->place->latitude)) {
$this->fail(new NoCoordinatesException());
} else {
app(GetWeatherInformationService::class)->execute([
'account_id' => $this->place->account_id,
'place_id' => $this->place->id,
]);
}
}
}

21
app/Jobs/Job.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
abstract class Job
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "onQueue" and "delay" queue helper methods.
|
*/
use Queueable;
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Jobs\Reminder;
use Illuminate\Bus\Queueable;
use App\Helpers\AccountHelper;
use App\Notifications\UserNotified;
use App\Notifications\UserReminded;
use App\Interfaces\MailNotification;
use App\Models\Contact\ReminderOutbox;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Notification;
class NotifyUserAboutReminder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var ReminderOutbox
*/
protected $reminderOutbox;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ReminderOutbox $reminderOutbox)
{
$this->reminderOutbox = $reminderOutbox;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// prepare the notification to be sent
$message = $this->getMessage();
if (! is_null($message)) {
$this->sendNotification($message);
$this->scheduleNextReminder();
}
// delete the reminder outbox
$this->reminderOutbox->delete();
}
/**
* Send the notification to this user.
*
* @param MailNotification $message
* @return void
*/
private function sendNotification(MailNotification $message): void
{
if ($this->reminderOutbox->reminder->contact !== null) {
$account = $this->reminderOutbox->user->account;
$hasLimitations = AccountHelper::hasLimitations($account);
if (! $hasLimitations) {
Notification::send($this->reminderOutbox->user, $message);
}
}
}
/**
* Schedule the next reminder for this user.
*
* @return void
*/
private function scheduleNextReminder(): void
{
/** @var \App\Models\Contact\Reminder */
$reminder = $this->reminderOutbox->reminder;
if ($reminder->frequency_type == 'one_time') {
$reminder->inactive = true;
$reminder->save();
} else {
$reminder->schedule($this->reminderOutbox->user);
}
}
/**
* Get message to send.
*
* @return MailNotification|null
*/
private function getMessage(): ?MailNotification
{
switch ($this->reminderOutbox->nature) {
case 'reminder':
return new UserReminded($this->reminderOutbox->reminder);
case 'notification':
return new UserNotified($this->reminderOutbox->reminder, $this->reminderOutbox->notification_number_days_before);
default:
return null;
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Intervention\Image\Facades\Image;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class ResizeAvatars implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $contact;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (! $this->contact->has_avatar) {
return;
}
$storage = Storage::disk($this->contact->avatar_location);
if (! $storage->exists($this->contact->avatar_file_name)) {
return;
}
try {
$avatarFile = $storage->get($this->contact->avatar_file_name);
$filename = pathinfo($this->contact->avatar_file_name, PATHINFO_FILENAME);
$extension = pathinfo($this->contact->avatar_file_name, PATHINFO_EXTENSION);
} catch (FileNotFoundException $e) {
return;
}
$this->resize($avatarFile, $filename, $extension, $storage, 110);
$this->resize($avatarFile, $filename, $extension, $storage, 174);
}
private function resize($avatarFile, $filename, $extension, $storage, $size)
{
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;
$avatar = Image::make($avatarFile);
$avatar->fit($size);
$storage->put($avatarFileName, (string) $avatar->stream(), config('filesystems.default_visibility'));
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Jobs;
use App\Models\User\User;
use Illuminate\Bus\Queueable;
use App\Notifications\NewUserAlert;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Notification;
class SendNewUserAlert implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$email = config('monica.email_new_user_notification');
if (! empty($email)) {
Notification::route('mail', $email)
->notify(new NewUserAlert($this->user));
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Jobs;
use App\Models\User\User;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Auth\Notifications\VerifyEmail;
class SendVerifyEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->user->notify(new VerifyEmail());
}
}

70
app/Jobs/ServiceQueue.php Normal file
View File

@@ -0,0 +1,70 @@
<?php
namespace App\Jobs;
use Throwable;
use Illuminate\Bus\Queueable;
use App\Services\QueuableService;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class ServiceQueue implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The service to queue.
*
* @var QueuableService
*/
public $service;
/**
* The data to run service.
*
* @var array
*/
public $data;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 1;
/**
* Create a new job instance.
*
* @param QueuableService $service
* @param array|null $data
*/
public function __construct(QueuableService $service, array $data = null)
{
$this->service = $service;
$this->data = $data;
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
$this->service->handle($this->data);
}
/**
* Handle a job failure.
*
* @param \Throwable $exception
* @return void
*/
public function failed(Throwable $exception): void
{
$this->service->failed($exception);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Jobs\StayInTouch;
use Illuminate\Bus\Queueable;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use Illuminate\Queue\SerializesModels;
use App\Notifications\StayInTouchEmail;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Notification as NotificationFacade;
class ScheduleStayInTouch implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $contact;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$account = $this->contact->account;
$users = [];
foreach ($account->users as $user) {
if ($user->isTheRightTimeToBeReminded($this->contact->stay_in_touch_trigger_date)
&& ! AccountHelper::hasLimitations($account)) {
array_push($users, $user);
}
}
if (count($users) > 0) {
NotificationFacade::send($users, new StayInTouchEmail($this->contact));
$this->contact->setStayInTouchTriggerDate($this->contact->stay_in_touch_frequency, $this->contact->stay_in_touch_trigger_date);
return;
}
$now = now();
while ($this->contact->stay_in_touch_trigger_date < $now) {
// If stay in touch was missed, we reschedule it.
$this->contact->setStayInTouchTriggerDate($this->contact->stay_in_touch_frequency, $this->contact->stay_in_touch_trigger_date);
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\SynchronizeAddressBook;
class SynchronizeAddressBooks implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var AddressBookSubscription
*/
public $subscription;
/**
* @var bool
*/
private $force;
/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @return void
*/
public function __construct(AddressBookSubscription $subscription, bool $force = false)
{
$this->subscription = $subscription;
$this->force = $force;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
try {
app(SynchronizeAddressBook::class)->execute([
'account_id' => $this->subscription->account_id,
'addressbook_subscription_id' => $this->subscription->id,
'force' => $this->force,
]);
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.':'.$e->getMessage(), [$e]);
}
$this->subscription->last_synchronized_at = now();
$this->subscription->save();
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateLastConsultedDate implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $contact;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
$timestamps = $this->contact->timestamps;
$this->contact->timestamps = false;
$this->contact->last_consulted_at = now();
$this->contact->number_of_views = $this->contact->number_of_views + 1;
$this->contact->save();
$this->contact->timestamps = $timestamps;
}
}