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,92 @@
<?php
namespace App\Services\Contact\Address;
use App\Models\Account\Place;
use App\Services\BaseService;
use App\Models\Contact\Address;
use App\Models\Contact\Contact;
use App\Services\Account\Place\CreatePlace;
use App\Services\Contact\Label\UpdateAddressLabels;
class CreateAddress extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'name' => 'nullable|string|max:255',
'street' => 'nullable|string|max:255',
'city' => 'nullable|string|max:255',
'province' => 'nullable|string|max:255',
'postal_code' => 'nullable|string|max:255',
'country' => 'nullable|string|max:3',
'latitude' => 'nullable|numeric',
'longitude' => 'nullable|numeric',
'labels' => 'nullable|array',
];
}
/**
* Create an address.
*
* @param array $data
* @return Address
*/
public function execute(array $data): Address
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$place = $this->createPlace($data);
$address = Address::create([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'place_id' => $place->id,
'name' => $this->nullOrValue($data, 'name'),
]);
if ($labels = $this->nullOrValue($data, 'labels')) {
app(UpdateAddressLabels::class)->execute([
'account_id' => $data['account_id'],
'address_id' => $address->id,
'labels' => $labels,
]);
}
return $address;
}
/**
* Create a place for the given address.
*
* @param array $data
* @return Place
*/
private function createPlace(array $data)
{
$request = [
'account_id' => $data['account_id'],
'street' => $this->nullOrValue($data, 'street'),
'city' => $this->nullOrValue($data, 'city'),
'province' => $this->nullOrValue($data, 'province'),
'postal_code' => $this->nullOrValue($data, 'postal_code'),
'country' => $this->nullOrValue($data, 'country'),
'latitude' => $this->nullOrValue($data, 'latitude'),
'longitude' => $this->nullOrValue($data, 'longitude'),
];
return app(CreatePlace::class)->execute($request);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Services\Contact\Address;
use App\Services\BaseService;
use App\Models\Contact\Address;
use App\Services\Account\Place\DestroyPlace;
class DestroyAddress extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'address_id' => 'required|integer|exists:addresses,id',
];
}
/**
* Destroy an address.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$address = Address::where('account_id', $data['account_id'])
->findOrFail($data['address_id']);
$address->contact->throwInactive();
app(DestroyPlace::class)->execute([
'account_id' => $data['account_id'],
'place_id' => $address->place_id,
]);
$address->delete();
return true;
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Services\Contact\Address;
use App\Models\Account\Place;
use App\Services\BaseService;
use App\Models\Contact\Address;
use App\Models\Contact\Contact;
use App\Services\Account\Place\UpdatePlace;
use App\Services\Contact\Label\UpdateAddressLabels;
class UpdateAddress extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'address_id' => 'required|integer|exists:addresses,id',
'name' => 'nullable|string|max:255',
'street' => 'nullable|string|max:255',
'city' => 'nullable|string|max:255',
'province' => 'nullable|string|max:255',
'postal_code' => 'nullable|string|max:255',
'country' => 'nullable|string|max:3',
'latitude' => 'nullable|numeric',
'longitude' => 'nullable|numeric',
'labels' => 'nullable|array',
];
}
/**
* Update an address.
*
* @param array $data
* @return Address
*/
public function execute(array $data): Address
{
$this->validate($data);
/** @var Address */
$address = Address::where('account_id', $data['account_id'])
->where('contact_id', $data['contact_id'])
->findOrFail($data['address_id']);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$this->updatePlace($data, $address);
$address->update([
'name' => $this->nullOrValue($data, 'name'),
]);
if ($labels = $this->nullOrValue($data, 'labels')) {
app(UpdateAddressLabels::class)->execute([
'account_id' => $data['account_id'],
'address_id' => $address->id,
'labels' => $labels,
]);
}
return $address;
}
/**
* Create a place for the given address.
*
* @param array $data
* @param Address $address
* @return Place
*/
private function updatePlace(array $data, Address $address)
{
$request = [
'account_id' => $data['account_id'],
'place_id' => $address->place_id,
'street' => $this->nullOrValue($data, 'street'),
'city' => $this->nullOrValue($data, 'city'),
'province' => $this->nullOrValue($data, 'province'),
'postal_code' => $this->nullOrValue($data, 'postal_code'),
'country' => $this->nullOrValue($data, 'country'),
'latitude' => $this->nullOrValue($data, 'latitude'),
'longitude' => $this->nullOrValue($data, 'longitude'),
];
return app(UpdatePlace::class)->execute($request);
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Services\Contact\Avatar;
use Illuminate\Support\Str;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Support\Facades\Cache;
use Laravolt\Avatar\Facade as Avatar;
use Illuminate\Support\Facades\Storage;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class GenerateDefaultAvatar extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'contact_id' => 'required|integer|exists:contacts,id',
];
}
/**
* Generate the default image for the avatar, based on the initals of the
* contact and returns the filename.
*
* @param array $data
* @return Contact
*/
public function execute(array $data)
{
$this->validate($data);
$contact = Contact::find($data['contact_id']);
$contact = $this->generateContactUUID($contact);
// delete existing default avatar
$contact = $this->deleteExistingDefaultAvatar($contact);
// create new avatar
$filename = $this->createNewAvatar($contact);
$contact->avatar_default_url = $filename;
$contact->save();
Cache::forget('etag'.Str::before('?', $filename));
return $contact;
}
/**
* Create an uuid for the contact if it does not exist.
*
* @param Contact $contact
* @return Contact
*/
private function generateContactUUID(Contact $contact)
{
if (! $contact->uuid) {
$contact->uuid = Str::uuid()->toString();
$contact->save();
}
return $contact;
}
/**
* Create a new avatar for the contact based on the name of the contact.
*
* @param Contact $contact
* @return string
*/
private function createNewAvatar(Contact $contact)
{
$img = null;
try {
$img = Avatar::create($contact->name)
->setBackground($contact->default_avatar_color)
->getImageObject()
->encode('jpg');
$filename = 'avatars/'.$contact->uuid.'.jpg';
Storage::disk(config('filesystems.default'))
->put($filename, $img, config('filesystems.default_visibility'));
// This will force the browser to reload the new avatar
return $filename.'?'.now()->format('U');
} finally {
if ($img) {
$img->destroy();
}
}
}
/**
* Delete the existing default avatar.
*
* @param Contact $contact
* @return Contact
*/
private function deleteExistingDefaultAvatar(Contact $contact)
{
if ($contact->avatar_default_url !== null) {
try {
Storage::disk(config('filesystems.default'))
->delete($contact->avatar_default_url);
$contact->avatar_default_url = null;
} catch (FileNotFoundException $e) {
// ignore
}
}
return $contact;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Services\Contact\Avatar;
use App\Services\BaseService;
class GetAdorableAvatarURL extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'uuid' => 'required|string',
'size' => 'nullable|integer|between:1,2000',
];
}
/**
* Get an url for an adorable avatar.
* - http://avatars.adorable.io/ gives avatars based on a random string.
*
* @param array $data
* @return string|null
*/
public function execute(array $data)
{
$this->validate($data);
$size = $this->size($data);
return $size.'/'.$data['uuid'].'.png';
}
/**
* Get the size for the avatar, based on a given parameter. Provides a
* default otherwise.
*
* @param array $data
* @return int
*/
private function size(array $data)
{
if (isset($data['size'])) {
return $data['size'];
}
return (int) config('monica.avatar_size');
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Services\Contact\Avatar;
use Illuminate\Support\Str;
use App\Services\BaseService;
use App\Models\Contact\Contact;
class GetAvatarsFromInternet extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'contact_id' => 'required|integer|exists:contacts,id',
];
}
/**
* Query both Gravatar and Adorable Avatars based on the email address of
* the contact.
*
* - http://avatars.adorable.io/ gives avatars based on a random string.
* This random string comes from the `avatar_adorable_uuid` field in the
* Contact object.
* - Gravatar only gives an avatar only if it's set.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
$contact = Contact::findOrFail($data['contact_id']);
$contact = $this->getAdorable($contact);
$contact = $this->getGravatar($contact);
return $contact;
}
/**
* Generate the UUID used to identify the contact in the Adorable service.
*
* @param Contact $contact
* @return Contact
*/
private function generateUUID(Contact $contact)
{
if (empty($contact->avatar_adorable_uuid)) {
$contact->avatar_adorable_uuid = Str::uuid()->toString();
$contact->save();
}
return $contact;
}
/**
* Get the adorable avatar.
*
* @param Contact $contact
* @return Contact
*/
private function getAdorable(Contact $contact)
{
// prevent timestamp update
$timestamps = $contact->timestamps;
$contact->timestamps = false;
$contact = $this->generateUUID($contact);
$contact->avatar_adorable_url = app(GetAdorableAvatarURL::class)->execute([
'uuid' => $contact->avatar_adorable_uuid,
'size' => 200,
]);
$contact->save();
$contact->timestamps = $timestamps;
return $contact;
}
/**
* Query Gravatar (if it exists) for the contact's email address.
*
* @param Contact $contact
* @return Contact
*/
private function getGravatar(Contact $contact)
{
return app(GetGravatar::class)->execute([
'contact_id' => $contact->id,
]);
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Services\Contact\Avatar;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class GetGravatar extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'contact_id' => 'required|integer|exists:contacts,id',
];
}
public function execute(array $data): Contact
{
$this->validate($data);
/** @var Contact */
$contact = Contact::findOrFail($data['contact_id']);
// prevent timestamp update
$timestamps = $contact->timestamps;
$contact->timestamps = false;
$contact = $this->getGravatar($contact);
$contact->save();
$contact->timestamps = $timestamps;
return $contact;
}
/**
* Get the emails of the contact, based on the contact fields.
*
* @param Contact $contact
* @return Collection
*/
private function getEmails(Contact $contact)
{
$emails = collect();
$contactFields = $contact->contactFields()
->email()
->get();
foreach ($contactFields as $contactField) {
try {
$email = $contactField->data;
Validator::make(['email' => $email], ['email' => 'email'])
->validate();
$emails->push($email);
} catch (ModelNotFoundException $e) {
// Not found
} catch (ValidationException $e) {
// Not an email
}
}
return $emails;
}
/**
* Query Gravatar (if it exists) for the contact's email address.
*
* @param Contact $contact
* @return Contact
*/
private function getGravatar(Contact $contact)
{
$emails = $this->getEmails($contact);
$gravatarUrl = null;
foreach ($emails as $email) {
$gravatarUrl = app(GetGravatarURL::class)->execute([
'email' => $email,
'size' => config('monica.avatar_size'),
]);
if ($gravatarUrl) {
break;
}
}
if ($gravatarUrl) {
$contact->avatar_gravatar_url = $gravatarUrl;
} else {
// in this case we need to make sure that we reset the gravatar URL
$contact->avatar_gravatar_url = null;
if ($contact->avatar_source == 'gravatar') {
$contact->avatar_source = 'adorable';
}
}
return $contact;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Services\Contact\Avatar;
use App\Services\BaseService;
use Illuminate\Support\Facades\App;
use Creativeorange\Gravatar\Facades\Gravatar;
class GetGravatarURL extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
'size' => 'nullable|integer|between:1,2000',
];
}
/**
* Get Gravatar, if it exists.
*
* @param array $data
* @return string|null
*/
public function execute(array $data): ?string
{
$this->validate($data);
if ($this->exists($data)) {
$size = $this->size($data);
return Gravatar::get($data['email'], [
'size' => $size,
'secure' => App::environment('production'),
]);
}
return null;
}
/**
* Test given email.
*
* @param array $data
* @return bool
*/
private function exists(array $data)
{
try {
return Gravatar::exists($data['email']);
} catch (\Exception $e) {
// catch invalid email
return false;
}
}
/**
* Get the size for the gravatar, based on a given parameter. Provides a
* default otherwise.
*
* @param array $data
* @return int
*/
private function size(array $data)
{
if (isset($data['size'])) {
return $data['size'];
}
return (int) config('monica.avatar_size');
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Services\Contact\Avatar;
use App\Models\Account\Photo;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
/**
* Update the avatar of the contact.
*/
class UpdateAvatar extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'source' => [
'required',
Rule::in([
'default',
'adorable',
'gravatar',
'photo',
]),
],
'photo_id' => 'required_if:source,photo|integer|exists:photos,id',
];
}
/**
* Update message in a conversation.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
if (isset($data['photo_id'])) {
Photo::where('account_id', $data['account_id'])
->findOrFail($data['photo_id']);
}
$contact->avatar_source = $data['source'];
switch ($contact->avatar_source) {
case 'photo':
// in case of a photo, set the photo as the avatar
$contact->avatar_photo_id = $this->nullOrValue($data, 'photo_id');
$contact->photos()->syncWithoutDetaching([$this->nullOrValue($data, 'photo_id')]);
break;
default:
$contact->avatar_photo_id = null;
break;
}
$contact->save();
return $contact;
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Services\Contact\Call;
use Illuminate\Support\Arr;
use App\Models\Contact\Call;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Instance\Emotion\Emotion;
class CreateCall extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer',
'called_at' => 'required|date',
'content' => 'nullable|string',
'contact_called' => 'nullable|boolean',
'emotions' => 'nullable|array',
];
}
/**
* Create a call.
*
* @param array $data
* @return Call
*/
public function execute(array $data): Call
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
// emotions array is left out as they are not attached during this call
$call = Call::create(Arr::except($data, ['emotions']));
$this->updateLastCallInfo($contact, $call);
if (! empty($data['emotions'])) {
if ($data['emotions'] != '') {
$this->addEmotions($data['emotions'], $call);
}
}
return $call;
}
/**
* Add emotions to the call.
*
* @param array $emotions
* @param Call $call
* @return void
*/
private function addEmotions(array $emotions, Call $call)
{
foreach ($emotions as $emotionId) {
$emotion = Emotion::findOrFail($emotionId);
$call->emotions()->syncWithoutDetaching([$emotion->id => [
'account_id' => $call->account_id,
'contact_id' => $call->contact_id,
]]);
}
}
/**
* Update last call information of the contact.
*
* @param Contact $contact
* @param Call $call
* @return void
*/
private function updateLastCallInfo(Contact $contact, Call $call)
{
if (is_null($contact->last_talked_to)) {
$contact->last_talked_to = $call->called_at;
} else {
$contact->last_talked_to = $contact->last_talked_to->max($call->called_at);
}
$contact->save();
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Services\Contact\Call;
use App\Models\Contact\Call;
use App\Services\BaseService;
use App\Models\Contact\Contact;
class DestroyCall extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'call_id' => 'required|integer',
];
}
/**
* Destroy a call.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$call = Call::where('account_id', $data['account_id'])
->findOrFail($data['call_id']);
$contact = $call->contact;
$contact->throwInactive();
// delete all associations with emotions
$call->emotions()->sync([]);
$call->delete();
$this->updateLastCallInfo($contact);
return true;
}
/**
* Update last call information of the contact.
*
* @param Contact $contact
* @return void
*/
private function updateLastCallInfo(Contact $contact)
{
// look for all the calls of the contact and take the most recent call
// as the one we just deleted could have been the most recent call
$contact->last_talked_to = optional($contact->calls->first())->called_at;
$contact->save();
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Services\Contact\Call;
use App\Models\Contact\Call;
use App\Services\BaseService;
use App\Models\Instance\Emotion\Emotion;
class UpdateCall extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'call_id' => 'required|integer|exists:calls,id',
'called_at' => 'required|date',
'content' => 'nullable|string',
'contact_called' => 'nullable|boolean',
'emotions' => 'nullable|array',
];
}
/**
* Update a call.
*
* @param array $data
* @return Call
*/
public function execute(array $data): Call
{
$this->validate($data);
/** @var Call */
$call = Call::where('account_id', $data['account_id'])
->findOrFail($data['call_id']);
$call->contact->throwInactive();
$call->update([
'called_at' => $data['called_at'],
'content' => (empty($data['content']) ? null : $data['content']),
'contact_called' => (empty($data['contact_called']) ? null : $data['contact_called']),
]);
// emotions array is left out as they are not attached during this call
if (! empty($data['emotions'])) {
if ($data['emotions'] != '') {
$this->addEmotions($data['emotions'], $call);
}
}
$this->updateLastCallInfo($call);
return $call;
}
/**
* Add emotions to the call.
*
* @param array $emotions
* @param Call $call
* @return void
*/
private function addEmotions(array $emotions, Call $call)
{
// reset current emotions
$call->emotions()->sync([]);
// saving new emotions
foreach ($emotions as $emotionId) {
$emotion = Emotion::findOrFail($emotionId);
$call->emotions()->syncWithoutDetaching([$emotion->id => [
'account_id' => $call->account_id,
'contact_id' => $call->contact_id,
]]);
}
}
/**
* Update last call information of the contact.
*
* @param Call $call
* @return void
*/
private function updateLastCallInfo(Call $call)
{
/** @var \App\Models\Contact\Contact */
$contact = $call->contact;
if (is_null($contact->last_talked_to)) {
$contact->last_talked_to = $call->called_at;
} else {
$contact->last_talked_to = $contact->last_talked_to->max($call->called_at);
}
$contact->save();
}
}

View File

@@ -0,0 +1,262 @@
<?php
namespace App\Services\Contact\Contact;
use Ramsey\Uuid\Uuid;
use App\Models\User\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use App\Services\BaseService;
use App\Helpers\AccountHelper;
use function Safe\json_encode;
use App\Models\Account\Account;
use App\Models\Contact\Contact;
use App\Models\Account\AddressBook;
use App\Jobs\AuditLog\LogAccountAudit;
use App\Models\Contact\ContactFieldType;
use App\Jobs\Avatars\GenerateDefaultAvatar;
use App\Jobs\Avatars\GetAvatarsFromInternet;
use App\Services\Contact\ContactField\CreateContactField;
class CreateContact extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'author_id' => 'required|integer|exists:users,id',
'uuid' => 'nullable|string',
'address_book_id' => 'nullable|integer|exists:addressbooks,id',
'first_name' => 'required|string|max:255',
'middle_name' => 'nullable|string|max:255',
'last_name' => 'nullable|string|max:255',
'nickname' => 'nullable|string|max:255',
'email' => 'nullable|string|max:255',
'gender_id' => 'nullable|integer|exists:genders,id',
'description' => 'nullable|string|max:255',
'is_partial' => 'nullable|boolean',
'is_birthdate_known' => 'required|boolean',
'birthdate_day' => 'nullable|integer',
'birthdate_month' => 'nullable|integer',
'birthdate_year' => 'nullable|integer',
'birthdate_is_age_based' => 'nullable|boolean',
'birthdate_age' => 'nullable|integer',
'birthdate_add_reminder' => 'nullable|boolean',
'is_deceased' => 'required|boolean',
'is_deceased_date_known' => 'required|boolean',
'deceased_date_day' => 'nullable|integer',
'deceased_date_month' => 'nullable|integer',
'deceased_date_year' => 'nullable|integer',
'deceased_date_add_reminder' => 'nullable|boolean',
];
}
/**
* Create a contact.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
$account = Account::find($data['account_id']);
if (AccountHelper::hasReachedContactLimit($account)
&& AccountHelper::hasLimitations($account)
&& ! $account->legacy_free_plan_unlimited_contacts) {
abort(402);
}
if (Arr::get($data, 'address_book_id')) {
AddressBook::where('account_id', $data['account_id'])
->findOrFail($data['address_book_id']);
}
$contact = $this->create($data);
$this->updateBirthDayInformation($data, $contact);
$this->updateDeceasedInformation($data, $contact);
$this->updateEmail($data, $contact);
$this->generateUUID($contact);
$this->addAvatars($contact);
$this->log($data, $contact);
// we query the DB again to fill the object with all the new properties
$contact->refresh();
return $contact;
}
/**
* Create the contact.
*
* @param array $data
* @return Contact
*/
private function create(array $data): Contact
{
// filter out the data that shall not be updated here
$dataOnly = Arr::except(
$data,
[
'author_id',
'email',
'is_birthdate_known',
'birthdate_day',
'birthdate_month',
'birthdate_year',
'birthdate_is_age_based',
'birthdate_age',
'birthdate_add_reminder',
'is_deceased',
'is_deceased_date_known',
'deceased_date_day',
'deceased_date_month',
'deceased_date_year',
'deceased_date_add_reminder',
]
);
if (! empty($uuid = Arr::get($data, 'uuid')) && Uuid::isValid($uuid)) {
$dataOnly['uuid'] = $uuid;
}
return Contact::create($dataOnly);
}
/**
* Generates a UUID for this contact.
*
* @param Contact $contact
* @return void
*/
private function generateUUID(Contact $contact)
{
if (empty($contact->uuid)) {
$contact->uuid = Str::uuid()->toString();
$contact->save();
}
}
/**
* Add the different default avatars.
*
* @param Contact $contact
* @return void
*/
private function addAvatars(Contact $contact)
{
// set the default avatar color
$contact->setAvatarColor();
$contact->save();
// populate the avatar from Adorable and grab the Gravatar
GetAvatarsFromInternet::dispatch($contact);
// also generate the default avatar
GenerateDefaultAvatar::dispatch($contact);
}
/**
* Update the information about the birthday.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function updateBirthDayInformation(array $data, Contact $contact)
{
app(UpdateBirthdayInformation::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $contact->id,
'is_date_known' => $data['is_birthdate_known'],
'day' => $this->nullOrvalue($data, 'birthdate_day'),
'month' => $this->nullOrvalue($data, 'birthdate_month'),
'year' => $this->nullOrvalue($data, 'birthdate_year'),
'is_age_based' => $this->nullOrvalue($data, 'birthdate_is_age_based'),
'age' => $this->nullOrvalue($data, 'birthdate_age'),
'add_reminder' => $this->nullOrvalue($data, 'birthdate_add_reminder'),
'is_deceased' => $data['is_deceased'],
]);
}
/**
* Adds a contact field containing the email address.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function updateEmail(array $data, Contact $contact)
{
$contactFieldType = ContactFieldType::where([
'account_id' => $data['account_id'],
'type' => ContactFieldType::EMAIL,
])->first();
if (is_null($contactFieldType) || is_null($this->nullOrvalue($data, 'email'))) {
return;
}
app(CreateContactField::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $contact->id,
'contact_field_type_id' => $contactFieldType->id,
'data' => $data['email'],
]);
}
/**
* Update the information about the date of death.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function updateDeceasedInformation(array $data, Contact $contact)
{
app(UpdateDeceasedInformation::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $contact->id,
'is_deceased' => $data['is_deceased'],
'is_date_known' => $data['is_deceased_date_known'],
'day' => $this->nullOrValue($data, 'deceased_date_day'),
'month' => $this->nullOrValue($data, 'deceased_date_month'),
'year' => $this->nullOrValue($data, 'deceased_date_year'),
'add_reminder' => $this->nullOrValue($data, 'deceased_date_add_reminder'),
]);
}
/**
* Add an audit log.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function log(array $data, Contact $contact): void
{
$author = User::find($data['author_id']);
LogAccountAudit::dispatch([
'action' => 'contact_created',
'account_id' => $author->account_id,
'about_contact_id' => $contact->id,
'author_id' => $author->id,
'author_name' => $author->name,
'audited_at' => now(),
'should_appear_on_dashboard' => true,
'objects' => json_encode([
'contact_name' => $contact->name,
'contact_id' => $contact->id,
]),
]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Services\Contact\Contact;
use App\Models\User\User;
use App\Services\BaseService;
use App\Models\Contact\Contact;
class DeleteMeContact extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'user_id' => 'required|integer|exists:users,id',
];
}
/**
* Set a contact as 'me' contact.
*
* @param array $data
* @return User
*/
public function execute(array $data): User
{
$this->validate($data);
/** @var User */
$user = User::where('account_id', $data['account_id'])
->findOrFail($data['user_id']);
$user->me_contact_id = null;
$user->save();
return $user;
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Services\Contact\Contact;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Services\QueuableService;
use App\Services\DispatchableService;
use App\Models\Relationship\Relationship;
use App\Services\Contact\Relationship\DestroyRelationship;
class DestroyContact extends BaseService implements QueuableService
{
use DispatchableService;
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'force_delete' => 'nullable|boolean',
];
}
/**
* Destroy a contact.
*
* @param array $data
* @return void
*/
public function handle(array $data): void
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$this->destroyRelationships($data, $contact);
$contact->deleteAvatars();
if ($this->valueOrFalse($data, 'force_delete') === true) {
$contact->forceDelete();
} else {
$contact->delete();
}
}
/**
* Destroy all associated relationships.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function destroyRelationships(array $data, Contact $contact)
{
$relationships = Relationship::where('contact_is', $contact->id)->get();
$this->destroySpecificRelationships($data, $relationships);
$relationships = Relationship::where('of_contact', $contact->id)->get();
$this->destroySpecificRelationships($data, $relationships);
}
/**
* Delete specific relationships.
*
* @param array $data
* @param \Illuminate\Support\Collection $relationships
* @return void
*/
private function destroySpecificRelationships(array $data, $relationships)
{
foreach ($relationships as $relationship) {
app(DestroyRelationship::class)
->execute([
'account_id' => $data['account_id'],
'relationship_id' => $relationship->id,
]);
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Services\Contact\Contact;
use App\Models\User\User;
use App\Services\BaseService;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
class SetMeContact extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'user_id' => 'required|integer|exists:users,id',
'contact_id' => 'required|integer|exists:contacts,id',
];
}
/**
* Set a contact as 'me' contact.
*
* @param array $data
* @return User
*/
public function execute(array $data): User
{
$this->validate($data);
/** @var User */
$user = User::where('account_id', $data['account_id'])
->findOrFail($data['user_id']);
if (AccountHelper::hasLimitations($user->account)) {
abort(402);
}
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$user->me_contact_id = $contact->id;
$user->save();
return $user;
}
}

View File

@@ -0,0 +1,201 @@
<?php
namespace App\Services\Contact\Contact;
use App\Helpers\DateHelper;
use Illuminate\Support\Arr;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use App\Models\Contact\Reminder;
use App\Models\Instance\SpecialDate;
use App\Services\Contact\Reminder\CreateReminder;
use App\Services\Contact\Reminder\DestroyReminder;
class UpdateBirthdayInformation extends BaseService
{
/**
* @var array
*/
public $data;
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'is_date_known' => 'required|boolean',
'is_age_based' => 'nullable|boolean',
'day' => [
'integer',
'nullable',
Rule::requiredIf(function () {
return Arr::get($this->data, 'is_date_known', false) && ! Arr::get($this->data, 'is_age_based', false);
}),
],
'month' => [
'integer',
'nullable',
Rule::requiredIf(function () {
return Arr::get($this->data, 'is_date_known', false) && ! Arr::get($this->data, 'is_age_based', false);
}),
],
'year' => 'nullable|integer',
'age' => [
'integer',
'nullable',
Rule::requiredIf(function () {
return Arr::get($this->data, 'is_date_known', false) && Arr::get($this->data, 'is_age_based', false);
}),
],
'add_reminder' => 'nullable|boolean',
];
}
/**
* Update the information about the birthday.
*
* @param array $data
* @return Contact
*/
public function execute(array $data)
{
$this->data = $data;
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$this->clearRelatedReminder($contact);
$this->clearRelatedSpecialDate($contact);
$this->manageBirthday($data, $contact);
return $contact;
}
/**
* Delete related reminder.
*
* @param Contact $contact
* @return void
*/
private function clearRelatedReminder(Contact $contact)
{
if (is_null($contact->birthday_reminder_id)) {
return;
}
app(DestroyReminder::class)->execute([
'account_id' => $contact->account_id,
'reminder_id' => $contact->birthday_reminder_id,
]);
}
/**
* Delete related special date.
*
* @param Contact $contact
* @return void
*/
private function clearRelatedSpecialDate(Contact $contact)
{
$specialDate = SpecialDate::find($contact->birthday_special_date_id);
if (! is_null($specialDate)) {
$specialDate->delete();
}
}
/**
* Update birthday information depending on the type of information.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function manageBirthday(array $data, Contact $contact): void
{
if (! $data['is_date_known']) {
return;
}
if ($data['is_age_based']) {
$this->approximate($data, $contact);
} else {
$this->exact($data, $contact);
}
}
/**
* Case where the birthday is approximate. That means the birthdate is based
* on the estimated age of the contact.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function approximate(array $data, Contact $contact)
{
$contact->setSpecialDateFromAge('birthdate', $data['age']);
}
/**
* Case where we have a year, month and day for the birthday.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function exact(array $data, Contact $contact)
{
$specialDate = $contact->setSpecialDate(
'birthdate',
(is_null($data['year']) ? 0 : $data['year']),
$data['month'],
$data['day']
);
$this->setReminder($data, $contact, $specialDate);
}
/**
* Set a reminder for the given special date, if required.
*
* @param array $data
* @param Contact $contact
* @param SpecialDate $specialDate
* @return void
*/
private function setReminder(array $data, Contact $contact, SpecialDate $specialDate)
{
if (empty($data['add_reminder'])) {
return;
}
$reminder = app(CreateReminder::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
($data['is_deceased'] ?
'people.people_add_birthday_reminder_deceased' : 'people.people_add_birthday_reminder'),
['name' => $contact->first_name]
),
'delible' => false,
]);
$contact->birthday_reminder_id = $reminder->id;
$contact->save();
}
}

View File

@@ -0,0 +1,177 @@
<?php
namespace App\Services\Contact\Contact;
use Ramsey\Uuid\Uuid;
use Illuminate\Support\Arr;
use App\Services\BaseService;
use App\Helpers\AccountHelper;
use App\Models\Account\Account;
use App\Models\Contact\Contact;
use App\Jobs\Avatars\GenerateDefaultAvatar;
use App\Services\Contact\Description\SetPersonalDescription;
use App\Services\Contact\Description\ClearPersonalDescription;
class UpdateContact extends BaseService
{
private array $data;
private Contact $contact;
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'author_id' => 'required|integer|exists:users,id',
'contact_id' => 'required|integer',
'uuid' => 'nullable|string',
'first_name' => 'required|string|max:255',
'middle_name' => 'nullable|string|max:255',
'last_name' => 'nullable|string|max:255',
'nickname' => 'nullable|string|max:255',
'gender_id' => 'nullable|integer|exists:genders,id',
'description' => 'nullable|string|max:255',
'is_partial' => 'nullable|boolean',
'is_birthdate_known' => 'required|boolean',
'birthdate_day' => 'nullable|integer',
'birthdate_month' => 'nullable|integer',
'birthdate_year' => 'nullable|integer',
'birthdate_is_age_based' => 'nullable|boolean',
'birthdate_age' => 'nullable|integer',
'birthdate_add_reminder' => 'nullable|boolean',
'is_deceased' => 'nullable|boolean',
'is_deceased_date_known' => 'required|boolean',
'deceased_date_day' => 'nullable|integer',
'deceased_date_month' => 'nullable|integer',
'deceased_date_year' => 'nullable|integer',
'deceased_date_add_reminder' => 'nullable|boolean',
];
}
/**
* Update a contact.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->data = $data;
$this->validate($this->data);
/* @var Contact */
$this->contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$this->contact->throwInactive();
// Test is the account is limited and the contact should be updated as real contact
$account = Account::find($data['account_id']);
if ($this->contact->is_partial
&& ! $this->valueOrFalse($this->data, 'is_partial')
&& AccountHelper::hasReachedContactLimit($account)
&& AccountHelper::hasLimitations($account)
&& ! $account->legacy_free_plan_unlimited_contacts) {
abort(402);
}
$this->updateGeneralInformation();
$this->updateDescription();
$this->updateBirthDayInformation();
$this->updateDeceasedInformation();
return $this->contact->refresh();
}
private function updateGeneralInformation(): void
{
// filter out the data that shall not be updated here
$dataOnly = Arr::except(
$this->data,
[
'author_id',
'uuid',
'is_birthdate_known',
'birthdate_day',
'birthdate_month',
'birthdate_year',
'birthdate_is_age_based',
'birthdate_age',
'birthdate_add_reminder',
'is_deceased',
'is_deceased_date_known',
'deceased_date_day',
'deceased_date_month',
'deceased_date_year',
'deceased_date_add_reminder',
'description',
]
);
if (! empty($uuid = Arr::get($this->data, 'uuid')) && Uuid::isValid($uuid)) {
$dataOnly['uuid'] = $uuid;
}
$oldName = $this->contact->name;
$this->contact->update($dataOnly);
// only update the avatar if the name has changed
if ($oldName != $this->contact->name) {
GenerateDefaultAvatar::dispatch($this->contact);
}
}
private function updateDescription(): void
{
if (is_null($this->nullOrValue($this->data, 'description'))) {
app(ClearPersonalDescription::class)->execute([
'account_id' => $this->data['account_id'],
'contact_id' => $this->data['contact_id'],
'author_id' => $this->data['author_id'],
]);
} else {
if ($this->contact->description != $this->data['description']) {
app(SetPersonalDescription::class)->execute([
'account_id' => $this->data['account_id'],
'contact_id' => $this->data['contact_id'],
'author_id' => $this->data['author_id'],
'description' => $this->data['description'],
]);
}
}
}
private function updateBirthDayInformation(): void
{
app(UpdateBirthdayInformation::class)->execute([
'account_id' => $this->data['account_id'],
'contact_id' => $this->contact->id,
'is_date_known' => $this->data['is_birthdate_known'],
'day' => $this->nullOrvalue($this->data, 'birthdate_day'),
'month' => $this->nullOrvalue($this->data, 'birthdate_month'),
'year' => $this->nullOrvalue($this->data, 'birthdate_year'),
'is_age_based' => $this->nullOrvalue($this->data, 'birthdate_is_age_based'),
'age' => $this->nullOrvalue($this->data, 'birthdate_age'),
'add_reminder' => $this->nullOrvalue($this->data, 'birthdate_add_reminder'),
'is_deceased' => $this->data['is_deceased'],
]);
}
private function updateDeceasedInformation(): void
{
app(UpdateDeceasedInformation::class)->execute([
'account_id' => $this->data['account_id'],
'contact_id' => $this->contact->id,
'is_deceased' => $this->data['is_deceased'],
'is_date_known' => $this->data['is_deceased_date_known'],
'day' => $this->nullOrvalue($this->data, 'deceased_date_day'),
'month' => $this->nullOrvalue($this->data, 'deceased_date_month'),
'year' => $this->nullOrvalue($this->data, 'deceased_date_year'),
'add_reminder' => $this->nullOrvalue($this->data, 'deceased_date_add_reminder'),
]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Services\Contact\Contact;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\ValidationException;
class UpdateContactFoodPreferences extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'food_preferences' => 'nullable|string|max:65535',
];
}
/**
* Update the food preferences of the given contact.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
if ($contact->is_partial) {
throw ValidationException::withMessages([
'contact_id' => 'The contact can\'t be a partial contact',
]);
}
$contact->food_preferences = ! empty($data['food_preferences']) ? $data['food_preferences'] : null;
$contact->save();
// we query the DB again to fill the object with all the new properties
$contact->refresh();
return $contact;
}
}

View File

@@ -0,0 +1,214 @@
<?php
namespace App\Services\Contact\Contact;
use App\Helpers\DateHelper;
use Illuminate\Support\Arr;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use App\Models\Contact\Reminder;
use App\Models\Instance\SpecialDate;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Reminder\CreateReminder;
use App\Services\Contact\Reminder\DestroyReminder;
class UpdateContactIntroduction extends BaseService
{
/**
* @var array
*/
public $data;
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'met_through_contact_id' => 'nullable|integer|exists:contacts,id',
'general_information' => 'nullable|string|max:65535',
'where' => 'nullable|string|max:255',
'is_date_known' => 'required|boolean',
'is_age_based' => 'nullable|boolean',
'day' => [
'integer',
'nullable',
Rule::requiredIf(function () {
return Arr::get($this->data, 'is_date_known', false) && ! Arr::get($this->data, 'is_age_based', false);
}),
],
'month' => [
'integer',
'nullable',
Rule::requiredIf(function () {
return Arr::get($this->data, 'is_date_known', false) && ! Arr::get($this->data, 'is_age_based', false);
}),
],
'year' => 'nullable|integer',
'age' => [
'integer',
'nullable',
Rule::requiredIf(function () {
return Arr::get($this->data, 'is_date_known', false) && Arr::get($this->data, 'is_age_based', false);
}),
],
'add_reminder' => 'nullable|boolean',
];
}
/**
* Update the information about how a contact was introduced.
*
* @param array $data
* @return Contact
*
* @throws ValidationException
*/
public function execute(array $data): Contact
{
$this->data = $data;
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
if ($contact->is_partial) {
throw ValidationException::withMessages([
'contact_id' => 'The contact can\'t be a partial contact',
]);
}
if ($metContactId = Arr::get($data, 'met_through_contact_id')) {
Contact::where('account_id', $data['account_id'])
->findOrFail($metContactId);
}
$this->setMetThroughContact($data, $contact);
$this->clearRelatedReminder($contact);
$this->manageDate($data, $contact);
$this->setInformation($data, $contact);
// we query the DB again to fill the object with all the new properties
$contact->refresh();
return $contact;
}
private function setMetThroughContact(array $data, Contact $contact): void
{
$contact->first_met_through_contact_id = Arr::get($data, 'met_through_contact_id');
$contact->save();
}
private function setInformation(array $data, Contact $contact): void
{
$contact->first_met_additional_info = Arr::get($data, 'general_information');
$contact->first_met_where = Arr::get($data, 'where');
$contact->save();
}
private function clearRelatedReminder(Contact $contact): void
{
try {
app(DestroyReminder::class)->execute([
'account_id' => $contact->account_id,
'reminder_id' => $contact->first_met_reminder_id,
]);
} catch (\Exception $e) {
// Ignore this error
}
}
/**
* Update date information depending on the type of information.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function manageDate(array $data, Contact $contact): void
{
if (! $data['is_date_known']) {
$contact->firstMetDate()->delete();
return;
}
if (Arr::get($data, 'is_age_based')) {
$this->approximate($data, $contact);
} else {
$this->exact($data, $contact);
}
}
/**
* Case where the date is approximate. That means the date is based
* on the estimated age of the contact.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function approximate(array $data, Contact $contact): void
{
$contact->setSpecialDateFromAge('first_met', $data['age']);
}
/**
* Case where we have a year, month and day for the date.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function exact(array $data, Contact $contact): void
{
$specialDate = $contact->setSpecialDate(
'first_met',
(is_null($data['year']) ? 0 : $data['year']),
$data['month'],
$data['day']
);
$this->setReminder($data, $contact, $specialDate);
}
/**
* Set a reminder for the given special date, if required.
*
* @param array $data
* @param Contact $contact
* @param SpecialDate $specialDate
* @return void
*/
private function setReminder(array $data, Contact $contact, SpecialDate $specialDate): void
{
if (empty($data['add_reminder'])) {
return;
}
$reminder = app(CreateReminder::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
'people.introductions_reminder_title',
['name' => $contact->first_name]
),
'delible' => false,
]);
$contact->first_met_reminder_id = $reminder->id;
$contact->save();
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace App\Services\Contact\Contact;
use App\Helpers\DateHelper;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Instance\SpecialDate;
use App\Services\Contact\Reminder\CreateReminder;
use App\Services\Contact\Reminder\DestroyReminder;
class UpdateDeceasedInformation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer',
'is_deceased' => 'required|boolean',
'is_date_known' => 'required|boolean',
'day' => 'nullable|integer',
'month' => 'nullable|integer',
'year' => 'nullable|integer',
'add_reminder' => 'nullable|boolean',
];
}
/**
* Update the information about the deceased date.
*
* @param array $data
* @return Contact
*/
public function execute(array $data)
{
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$this->clearRelatedReminder($contact);
$this->clearRelatedSpecialDate($contact);
$this->manageDeceasedDate($data, $contact);
return $contact;
}
/**
* Delete related reminder.
*
* @param Contact $contact
* @return void
*/
private function clearRelatedReminder(Contact $contact)
{
if (is_null($contact->deceased_reminder_id)) {
return;
}
app(DestroyReminder::class)->execute([
'account_id' => $contact->account_id,
'reminder_id' => $contact->deceased_reminder_id,
]);
}
/**
* Delete related special date.
*
* @param Contact $contact
* @return void
*/
private function clearRelatedSpecialDate(Contact $contact)
{
$specialDate = SpecialDate::find($contact->deceased_special_date_id);
if (! is_null($specialDate)) {
$specialDate->delete();
}
}
/**
* Update deceased date information depending on the type of information.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function manageDeceasedDate(array $data, Contact $contact): void
{
if (! $data['is_deceased']) {
// remove all information about deceased date in the DB
$contact->is_dead = false;
$contact->deceased_special_date_id = null;
$contact->save();
return;
}
$contact->is_dead = true;
$contact->save();
if ($data['is_date_known']) {
$this->exact($data, $contact);
}
}
/**
* Case where we have a year, month and day for the date.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function exact(array $data, Contact $contact)
{
$specialDate = $contact->setSpecialDate(
'deceased_date',
(is_null($data['year']) ? 0 : $data['year']),
$data['month'],
$data['day']
);
$this->setReminder($data, $contact, $specialDate);
}
/**
* Set a reminder for the given special date, if required.
*
* @param array $data
* @param Contact $contact
* @param SpecialDate $specialDate
* @return void
*/
private function setReminder(array $data, Contact $contact, SpecialDate $specialDate)
{
if (empty($data['add_reminder'])) {
return;
}
$reminder = app(CreateReminder::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
'people.deceased_reminder_title',
['name' => $contact->first_name]
),
]);
$contact->deceased_reminder_id = $reminder->id;
$contact->save();
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Services\Contact\Contact;
use App\Models\User\User;
use App\Services\BaseService;
use function Safe\json_encode;
use App\Models\Contact\Contact;
use App\Jobs\AuditLog\LogAccountAudit;
use Illuminate\Validation\ValidationException;
class UpdateWorkInformation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'author_id' => 'required|integer|exists:users,id',
'contact_id' => 'required|integer|exists:contacts,id',
'job' => 'nullable|string|max:255',
'company' => 'nullable|string|max:255',
];
}
/**
* Update a contact.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
if ($contact->is_partial) {
throw ValidationException::withMessages([
'contact_id' => 'The contact can\'t be a partial contact',
]);
}
$contact->job = empty($data['job']) ? null : $data['job'];
$contact->company = empty($data['company']) ? null : $data['company'];
$contact->save();
$this->log($data, $contact);
$contact->refresh();
return $contact;
}
/**
* Add an audit log.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function log(array $data, Contact $contact): void
{
$author = User::find($data['author_id']);
LogAccountAudit::dispatch([
'action' => 'contact_work_updated',
'account_id' => $author->account_id,
'about_contact_id' => $contact->id,
'author_id' => $author->id,
'author_name' => $author->name,
'audited_at' => now(),
'should_appear_on_dashboard' => true,
'objects' => json_encode([
'contact_name' => $contact->name,
'contact_id' => $contact->id,
]),
]);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Services\Contact\ContactField;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Contact\ContactField;
use App\Models\Contact\ContactFieldType;
use App\Services\Contact\Label\UpdateContactFieldLabels;
class CreateContactField extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'contact_field_type_id' => 'required|integer|exists:contact_field_types,id',
'data' => 'required|string|max:255',
'labels' => 'nullable|array',
];
}
/**
* Create a contact field.
*
* @param array $data
* @return ContactField
*/
public function execute(array $data): ContactField
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
ContactFieldType::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_type_id']);
$contactField = ContactField::create([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'contact_field_type_id' => $data['contact_field_type_id'],
'data' => $this->nullOrValue($data, 'data'),
]);
if ($labels = $this->nullOrValue($data, 'labels')) {
app(UpdateContactFieldLabels::class)->execute([
'account_id' => $data['account_id'],
'contact_field_id' => $contactField->id,
'labels' => $labels,
]);
}
return $contactField;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Services\Contact\ContactField;
use App\Services\BaseService;
use App\Models\Contact\ContactField;
class DestroyContactField extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_field_id' => 'required|integer|exists:contact_fields,id',
];
}
/**
* Destroy an address.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$contactField = ContactField::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_id']);
$contactField->contact->throwInactive();
$contactField->delete();
return true;
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Services\Contact\ContactField;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Contact\ContactField;
use App\Models\Contact\ContactFieldType;
use App\Services\Contact\Label\UpdateContactFieldLabels;
class UpdateContactField extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_field_id' => 'required|integer|exists:contact_fields,id',
'contact_id' => 'required|integer|exists:contacts,id',
'contact_field_type_id' => 'required|integer|exists:contact_field_types,id',
'data' => 'required|string|max:255',
'labels' => 'nullable|array',
];
}
/**
* Update a contact field.
*
* @param array $data
* @return ContactField
*/
public function execute(array $data): ContactField
{
$this->validate($data);
/** @var ContactField */
$contactField = ContactField::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_id']);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
ContactFieldType::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_type_id']);
$contactField->update([
'contact_id' => $data['contact_id'],
'contact_field_type_id' => $data['contact_field_type_id'],
'data' => $this->nullOrValue($data, 'data'),
]);
if ($labels = $this->nullOrValue($data, 'labels')) {
app(UpdateContactFieldLabels::class)->execute([
'account_id' => $data['account_id'],
'contact_field_id' => $data['contact_field_id'],
'labels' => $labels,
]);
}
return $contactField;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* This is a single action class, totally inspired by
* https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d.
*/
namespace App\Services\Contact\Conversation;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Contact\Message;
use App\Models\Contact\Conversation;
class AddMessageToConversation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'conversation_id' => 'required|integer|exists:conversations,id',
'written_at' => 'required|date',
'written_by_me' => 'required|boolean',
'content' => 'required|string',
];
}
/**
* Add message to a conversation.
*
* @param array $data
* @return Message
*/
public function execute(array $data): Message
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
Conversation::where('contact_id', $data['contact_id'])
->where('account_id', $data['account_id'])
->findOrFail($data['conversation_id']);
return Message::create($data);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* This is a single action class, totally inspired by
* https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d.
*/
namespace App\Services\Contact\Conversation;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Contact\Conversation;
use App\Models\Contact\ContactFieldType;
class CreateConversation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'happened_at' => 'required|date',
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'contact_field_type_id' => 'required|integer|exists:contact_field_types,id',
];
}
/**
* Create a conversation.
*
* @param array $data
* @return Conversation
*/
public function execute(array $data): Conversation
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
ContactFieldType::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_type_id']);
return Conversation::create($data);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* This is a single action class, totally inspired by
* https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d.
*/
namespace App\Services\Contact\Conversation;
use App\Services\BaseService;
use App\Models\Contact\Conversation;
class DestroyConversation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'conversation_id' => 'required|integer|exists:conversations,id',
];
}
/**
* Destroy a conversation.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$conversation = Conversation::where('account_id', $data['account_id'])
->findOrFail($data['conversation_id']);
$conversation->contact->throwInactive();
$conversation->delete();
return true;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* This is a single action class, totally inspired by
* https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d.
*/
namespace App\Services\Contact\Conversation;
use App\Services\BaseService;
use App\Models\Contact\Message;
use App\Models\Contact\Conversation;
class DestroyMessage extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'conversation_id' => 'required|integer|exists:conversations,id',
'message_id' => 'required|integer|exists:messages,id',
];
}
/**
* Destroy a message.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
Conversation::where('account_id', $data['account_id'])
->findOrFail($data['conversation_id']);
$message = Message::where('account_id', $data['account_id'])
->where('conversation_id', $data['conversation_id'])
->findOrFail($data['message_id']);
$message->contact->throwInactive();
$message->delete();
return true;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* This is a single action class, totally inspired by
* https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d.
*/
namespace App\Services\Contact\Conversation;
use App\Services\BaseService;
use App\Models\Contact\Conversation;
use App\Models\Contact\ContactFieldType;
class UpdateConversation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'happened_at' => 'required|date',
'contact_field_type_id' => 'required|integer',
'conversation_id' => 'required|integer|exists:conversations,id',
];
}
/**
* Update a conversation.
*
* @param array $data
* @return Conversation
*/
public function execute(array $data): Conversation
{
$this->validate($data);
/** @var Conversation */
$conversation = Conversation::where('account_id', $data['account_id'])
->findOrFail($data['conversation_id']);
$conversation->contact->throwInactive();
ContactFieldType::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_type_id']);
$conversation->update([
'happened_at' => $data['happened_at'],
'contact_field_type_id' => $data['contact_field_type_id'],
]);
return $conversation;
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* This is a single action class, totally inspired by
* https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d.
*/
namespace App\Services\Contact\Conversation;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Contact\Message;
use App\Models\Contact\Conversation;
class UpdateMessage extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'conversation_id' => 'required|integer|exists:conversations,id',
'message_id' => 'required|integer|exists:messages,id',
'written_at' => 'required|date',
'written_by_me' => 'required|boolean',
'content' => 'required|string',
];
}
/**
* Update message in a conversation.
*
* @param array $data
* @return Message
*/
public function execute(array $data): Message
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
Conversation::where('contact_id', $data['contact_id'])
->where('account_id', $data['account_id'])
->findOrFail($data['conversation_id']);
/** @var Message */
$message = Message::where('contact_id', $data['contact_id'])
->where('conversation_id', $data['conversation_id'])
->where('account_id', $data['account_id'])
->findOrFail($data['message_id']);
$message->update([
'written_at' => $data['written_at'],
'written_by_me' => $data['written_by_me'],
'content' => $data['content'],
]);
return $message;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Services\Contact\Description;
use App\Models\User\User;
use App\Services\BaseService;
use function Safe\json_encode;
use App\Models\Contact\Contact;
use App\Jobs\AuditLog\LogAccountAudit;
class ClearPersonalDescription extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules(): array
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'author_id' => 'required|integer|exists:users,id',
];
}
/**
* Clear a contact's description.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
/** @var Contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$contact->description = null;
$contact->save();
$this->log($data, $contact);
return $contact;
}
/**
* Add an audit log.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function log(array $data, Contact $contact): void
{
$author = User::find($data['author_id']);
LogAccountAudit::dispatch([
'action' => 'contact_description_cleared',
'account_id' => $author->account_id,
'about_contact_id' => $contact->id,
'author_id' => $author->id,
'author_name' => $author->name,
'audited_at' => now(),
'should_appear_on_dashboard' => true,
'objects' => json_encode([
'contact_name' => $contact->name,
'contact_id' => $contact->id,
]),
]);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Services\Contact\Description;
use App\Models\User\User;
use App\Services\BaseService;
use function Safe\json_encode;
use App\Models\Contact\Contact;
use App\Jobs\AuditLog\LogAccountAudit;
class SetPersonalDescription extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules(): array
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'author_id' => 'required|integer|exists:users,id',
'description' => 'nullable|string|max:255',
];
}
/**
* Set a contact's description.
* The description should be saved as unparsed markdown content, and fetched
* as unparsed markdown content. The UI is responsible for parsing and
* displaying the proper content.
*
* @param array $data
* @return Contact
*/
public function execute(array $data): Contact
{
$this->validate($data);
/** @var Contact $contact */
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$contact->description = $data['description'];
$contact->save();
$this->log($data, $contact);
return $contact->refresh();
}
/**
* Add an audit log.
*
* @param array $data
* @param Contact $contact
* @return void
*/
private function log(array $data, Contact $contact): void
{
$author = User::find($data['author_id']);
LogAccountAudit::dispatch([
'action' => 'contact_description_updated',
'account_id' => $author->account_id,
'about_contact_id' => $contact->id,
'author_id' => $author->id,
'author_name' => $author->name,
'audited_at' => now(),
'should_appear_on_dashboard' => true,
'objects' => json_encode([
'contact_name' => $contact->name,
'contact_id' => $contact->id,
]),
]);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Services\Contact\Document;
use App\Services\BaseService;
use App\Models\Contact\Document;
use Illuminate\Support\Facades\Storage;
class DestroyDocument extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'document_id' => 'required|integer',
];
}
/**
* Destroy a document.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$document = Document::where('account_id', $data['account_id'])
->findOrFail($data['document_id']);
// Delete the physical document
// Throws FileNotFoundException
Storage::delete($document->new_filename);
// Delete the object in the DB
$document->delete();
return true;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Services\Contact\Document;
use App\Services\BaseService;
use App\Helpers\AccountHelper;
use App\Models\Account\Account;
use App\Models\Contact\Contact;
use App\Models\Contact\Document;
class UploadDocument extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer',
'document' => 'required|file',
];
}
/**
* Upload a document.
*
* @param array $data
* @return Document
*/
public function execute(array $data): Document
{
$this->validate($data);
$account = Account::find($data['account_id']);
if (AccountHelper::hasLimitations($account)) {
abort(402);
}
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$array = $this->populateData($data);
return Document::create($array);
}
/**
* Create an array with the necessary fields to create the document object.
*
* @return array
*/
private function populateData($data)
{
$document = $data['document'];
$data = [
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'original_filename' => $document->getClientOriginalName(),
'filesize' => $document->getSize(),
'type' => $document->guessClientExtension(),
'mime_type' => (new \Mimey\MimeTypes)->getMimeType($document->guessClientExtension()),
];
$filename = $document->store('documents', [
'disk' => config('filesystems.default'),
'visibility' => config('filesystems.default_visibility'),
]);
return array_merge($data, [
'new_filename' => $filename,
]);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Services\Contact\Gift;
use App\Models\Contact\Gift;
use App\Models\Account\Photo;
use App\Services\BaseService;
class AssociatePhotoToGift extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'photo_id' => 'required|integer|exists:photos,id',
'gift_id' => 'required|integer|exists:gifts,id',
];
}
/**
* Link a photo to a gift.
*
* @param array $data
*/
public function execute(array $data)
{
$this->validate($data);
$photo = Photo::where('account_id', $data['account_id'])
->findOrFail($data['photo_id']);
$gift = Gift::where('account_id', $data['account_id'])
->findOrFail($data['gift_id']);
$gift->contact->throwInactive();
$gift->photos()->syncWithoutDetaching([$photo->id]);
return $gift;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Services\Contact\Gift;
use App\Models\Contact\Gift;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;
class CreateGift extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'name' => 'required|string|max:255',
'status' => [
'required',
Rule::in([
'idea',
'offered',
'received',
]),
],
'comment' => 'string|max:1000000|nullable',
'url' => 'string|max:1000000|nullable',
'amount' => 'numeric|nullable',
'date' => 'date|nullable',
'recipient_id' => 'integer|nullable|exists:contacts,id',
];
}
/**
* Create a tag.
*
* @param array $data
* @return Gift
*/
public function execute(array $data): Gift
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
if (isset($data['recipient_id'])) {
Contact::where('account_id', $data['account_id'])
->findOrFail($data['recipient_id']);
}
$array = [
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'name' => $data['name'],
'status' => $data['status'],
'comment' => $this->nullOrvalue($data, 'comment'),
'url' => $this->nullOrvalue($data, 'url'),
'amount' => $this->nullOrvalue($data, 'amount'),
'date' => $this->nullOrvalue($data, 'date'),
];
if (Auth::check()) {
$array['currency_id'] = Auth::user()->currency->id;
}
return tap(Gift::create($array), function ($gift) use ($data): void {
$gift->recipient = $this->nullOrvalue($data, 'recipient_id');
$gift->save();
});
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Services\Contact\Gift;
use App\Models\Contact\Gift;
use App\Services\BaseService;
class DestroyGift extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'gift_id' => 'required|integer|exists:gifts,id',
];
}
/**
* Destroy a gift.
*
* @param array $data
* @return bool
*/
public function execute(array $data)
{
$this->validate($data);
$gift = Gift::where('account_id', $data['account_id'])
->findOrFail($data['gift_id']);
$gift->contact->throwInactive();
$gift->photos()->detach();
$gift->delete();
return true;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Services\Contact\Gift;
use App\Models\Contact\Gift;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;
class UpdateGift extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'gift_id' => 'required|integer|exists:gifts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'name' => 'required|string|max:255',
'status' => [
'required',
Rule::in([
'idea',
'offered',
'received',
]),
],
'comment' => 'string|max:1000000|nullable',
'url' => 'string|max:1000000|nullable',
'amount' => 'numeric|nullable',
'date' => 'date|nullable',
'recipient_id' => 'integer|nullable|exists:contacts,id',
];
}
/**
* Update a gift.
*
* @param array $data
* @return Gift
*/
public function execute(array $data): Gift
{
$this->validate($data);
$gift = Gift::where('account_id', $data['account_id'])
->findOrFail((int) $data['gift_id']);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
if (isset($data['recipient_id'])) {
Contact::where('account_id', $data['account_id'])
->findOrFail($data['recipient_id']);
}
$array = [
'contact_id' => $data['contact_id'],
'name' => $data['name'],
'status' => $data['status'],
'comment' => $this->nullOrvalue($data, 'comment'),
'url' => $this->nullOrvalue($data, 'url'),
'amount' => $this->nullOrvalue($data, 'amount'),
'date' => $this->nullOrvalue($data, 'date'),
];
if (Auth::check()) {
$array['currency_id'] = Auth::user()->currency->id;
}
$gift->update($array);
return tap($gift, function ($gift) use ($data): void {
$gift->recipient = $this->nullOrvalue($data, 'recipient_id');
$gift->save();
});
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Services\Contact\Label;
use App\Services\BaseService;
use App\Models\Contact\Address;
use App\Models\Contact\ContactFieldLabel;
class UpdateAddressLabels extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'address_id' => 'required|integer|exists:addresses,id',
'labels' => 'required|array',
];
}
/**
* Update address' labels.
*
* @param array $data
* @return void
*/
public function execute(array $data)
{
$this->validate($data);
$address = Address::where('account_id', $data['account_id'])
->findOrFail($data['address_id']);
$address->contact->throwInactive();
$labelsId = $this->getLabelsId($data);
$this->updateLabels($labelsId, $address);
}
/**
* Get ContactFieldLabel ids.
*
* @param array $data
* @return array
*/
private function getLabelsId(array $data): array
{
$labelsId = [];
foreach ($data['labels'] as $label) {
$label2 = mb_strtolower($label);
if (in_array($label2, ContactFieldLabel::$standardLabels)) {
$labelsId[] = (ContactFieldLabel::firstOrCreate([
'account_id' => $data['account_id'],
'label_i18n' => $label2,
]))->id;
} else {
$labelsId[] = (ContactFieldLabel::firstOrCreate([
'account_id' => $data['account_id'],
'label' => $label,
]))->id;
}
}
return $labelsId;
}
/**
* Update contactField's labels.
*
* @param array $labelsId
* @param Address $address
* @return void
*/
private function updateLabels(array $labelsId, Address $address)
{
$labelsSync = [];
foreach ($labelsId as $labelId) {
$labelsSync[$labelId] = ['account_id' => $address->account_id];
}
$address->labels()->sync($labelsSync);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Services\Contact\Label;
use App\Services\BaseService;
use App\Models\Contact\ContactField;
use App\Models\Contact\ContactFieldLabel;
class UpdateContactFieldLabels extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_field_id' => 'required|integer|exists:contact_fields,id',
'labels' => 'required|array',
];
}
/**
* Update contact field's labels.
*
* @param array $data
* @return void
*/
public function execute(array $data)
{
$this->validate($data);
$contactField = ContactField::where('account_id', $data['account_id'])
->findOrFail($data['contact_field_id']);
$contactField->contact->throwInactive();
$labelsId = $this->getLabelsId($data);
$this->updateLabels($labelsId, $contactField);
}
/**
* Get ContactFieldLabel ids.
*
* @param array $data
* @return array
*/
private function getLabelsId(array $data): array
{
$labelsId = [];
foreach ($data['labels'] as $label) {
$label2 = mb_strtolower($label);
$s = ContactFieldLabel::$standardLabels;
if (in_array($label2, ContactFieldLabel::$standardLabels)) {
$labelsId[] = (ContactFieldLabel::firstOrCreate([
'account_id' => $data['account_id'],
'label_i18n' => $label2,
]))->id;
} else {
$labelsId[] = (ContactFieldLabel::firstOrCreate([
'account_id' => $data['account_id'],
'label' => $label,
]))->id;
}
}
return $labelsId;
}
/**
* Update contactField's labels.
*
* @param array $labelsId
* @param ContactField $contactField
* @return void
*/
private function updateLabels(array $labelsId, ContactField $contactField)
{
$labelsSync = [];
foreach ($labelsId as $labelId) {
$labelsSync[$labelId] = ['account_id' => $contactField->account_id];
}
$contactField->labels()->sync($labelsSync);
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Services\Contact\LifeEvent;
use Carbon\Carbon;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Contact\LifeEvent;
use App\Models\Contact\LifeEventType;
use App\Services\Contact\Reminder\CreateReminder;
class CreateLifeEvent extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer',
'life_event_type_id' => 'required|integer',
'happened_at' => 'required|date',
'name' => 'nullable|string',
'note' => 'nullable|string',
'has_reminder' => 'required|boolean',
'happened_at_month_unknown' => 'required|boolean',
'happened_at_day_unknown' => 'required|boolean',
];
}
/**
* Create a life event.
*
* @param array $data
* @return LifeEvent
*/
public function execute(array $data): LifeEvent
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
LifeEventType::where('account_id', $data['account_id'])
->findOrFail($data['life_event_type_id']);
$lifeEvent = new LifeEvent;
$lifeEvent->account_id = $data['account_id'];
$lifeEvent->contact_id = $data['contact_id'];
$lifeEvent->life_event_type_id = $data['life_event_type_id'];
$lifeEvent->happened_at = $data['happened_at'];
$lifeEvent->name = $data['name'];
$lifeEvent->note = $data['note'];
$lifeEvent->happened_at_month_unknown = $data['happened_at_month_unknown'];
$lifeEvent->happened_at_day_unknown = $data['happened_at_day_unknown'];
$lifeEvent->save();
$this->addYearlyReminder($data, $lifeEvent);
// Get the newly created object as the Create method doesn't return all
// fields by default
return LifeEvent::find($lifeEvent->id);
}
/**
* Add yearly reminder if necessary.
*
* @param array $data
* @param LifeEvent $lifeEvent
*/
private function addYearlyReminder($data, $lifeEvent)
{
if ($data['has_reminder']) {
$date = Carbon::parse($data['happened_at']);
$data = [
'contact_id' => $data['contact_id'],
'account_id' => $data['account_id'],
'initial_date' => $date->toDateString(),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => $lifeEvent->lifeEventType->name,
'description' => null,
];
$reminder = app(CreateReminder::class)->execute($data);
$lifeEvent->reminder_id = $reminder->id;
$lifeEvent->save();
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Services\Contact\LifeEvent;
use App\Services\BaseService;
use App\Models\Contact\Reminder;
use App\Models\Contact\LifeEvent;
class DestroyLifeEvent extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'life_event_id' => 'required|integer',
];
}
/**
* Destroy a life event.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$lifeEvent = LifeEvent::where('account_id', $data['account_id'])
->findOrFail($data['life_event_id']);
$lifeEvent->contact->throwInactive();
$this->deleteAssociatedReminder($lifeEvent);
$lifeEvent->delete();
return true;
}
/**
* Delete the associated reminder, if it's set.
*/
private function deleteAssociatedReminder($lifeEvent)
{
if ($lifeEvent->reminder_id) {
Reminder::where('id', $lifeEvent->reminder_id)->delete();
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Services\Contact\LifeEvent;
use App\Services\BaseService;
use App\Models\Contact\LifeEvent;
use App\Models\Contact\LifeEventType;
class UpdateLifeEvent extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'life_event_id' => 'required|integer',
'life_event_type_id' => 'required|integer',
'happened_at' => 'required|date',
'name' => 'nullable|string',
'note' => 'nullable|string',
];
}
/**
* Update a life event.
*
* @param array $data
* @return LifeEvent
*/
public function execute(array $data): LifeEvent
{
$this->validate($data);
/** @var LifeEvent */
$lifeEvent = LifeEvent::where('account_id', $data['account_id'])
->findOrFail($data['life_event_id']);
$lifeEvent->contact->throwInactive();
LifeEventType::where('account_id', $data['account_id'])
->findOrFail($data['life_event_type_id']);
$lifeEvent->update([
'happened_at' => $data['happened_at'],
'life_event_type_id' => $data['life_event_type_id'],
'name' => $data['name'],
'note' => $data['note'],
]);
return $lifeEvent;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Services\Contact\Occupation;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use App\Models\Contact\Occupation;
class CreateOccupation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'company_id' => 'required|integer|exists:companies,id',
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:1000',
'salary' => 'nullable|integer',
'salary_unit' => [
'nullable',
Rule::in(Occupation::$salaryUnits),
],
'currently_works_here' => 'nullable|boolean',
'start_date' => 'nullable|date_format:Y-m-d',
'end_date' => 'nullable|date_format:Y-m-d',
];
}
/**
* Create a occupation.
*
* @param array $data
* @return Occupation
*/
public function execute(array $data): Occupation
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
return Occupation::create([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'company_id' => $data['company_id'],
'title' => $data['title'],
'description' => $this->nullOrValue($data, 'description'),
'salary' => $this->nullOrValue($data, 'salary'),
'salary_unit' => $this->nullOrValue($data, 'salary_unit'),
'currently_works_here' => $this->nullOrValue($data, 'currently_works_here'),
'start_date' => $this->nullOrDate($data, 'start_date'),
'end_date' => $this->nullOrDate($data, 'end_date'),
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Services\Contact\Occupation;
use App\Services\BaseService;
use App\Models\Contact\Occupation;
class DestroyOccupation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'occupation_id' => 'required|integer|exists:occupations,id',
];
}
/**
* Destroy an occupation.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$occupation = Occupation::where('account_id', $data['account_id'])
->findOrFail($data['occupation_id']);
$occupation->delete();
return true;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Services\Contact\Occupation;
use App\Services\BaseService;
use Illuminate\Validation\Rule;
use App\Models\Contact\Occupation;
class UpdateOccupation extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'company_id' => 'required|integer|exists:companies,id',
'occupation_id' => 'required|integer|exists:occupations,id',
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:1000',
'salary' => 'nullable|integer',
'salary_unit' => [
'nullable',
Rule::in(Occupation::$salaryUnits),
],
'currently_works_here' => 'nullable|boolean',
'start_date' => 'nullable|date_format:Y-m-d',
'end_date' => 'nullable|date_format:Y-m-d',
];
}
/**
* Update a occupation.
*
* @param array $data
* @return Occupation
*/
public function execute(array $data): Occupation
{
$this->validate($data);
/** @var Occupation */
$occupation = Occupation::where('account_id', $data['account_id'])
->where('contact_id', $data['contact_id'])
->where('company_id', $data['company_id'])
->findOrFail($data['occupation_id']);
$occupation->contact->throwInactive();
$occupation->update([
'title' => $data['title'],
'description' => $this->nullOrValue($data, 'description'),
'salary' => $this->nullOrValue($data, 'salary'),
'salary_unit' => $this->nullOrValue($data, 'salary_unit'),
'currently_works_here' => $this->nullOrValue($data, 'currently_works_here'),
'start_date' => $this->nullOrDate($data, 'start_date'),
'end_date' => $this->nullOrDate($data, 'end_date'),
]);
return $occupation;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Services\Contact\Relationship;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Relationship\Relationship;
use App\Models\Relationship\RelationshipType;
class CreateRelationship extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_is' => 'required|integer|exists:contacts,id',
'of_contact' => 'required|integer|exists:contacts,id',
'relationship_type_id' => 'required|integer|exists:relationship_types,id',
];
}
/**
* Set a relationship between two contacts.
*
* @param array $data
* @return Relationship
*/
public function execute(array $data): Relationship
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_is']);
$contact->throwInactive();
$otherContact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['of_contact']);
$relationshipType = RelationshipType::where('account_id', $data['account_id'])
->findOrFail($data['relationship_type_id']);
// create the relationship
$relationship = $this->setRelationship($contact, $otherContact, $relationshipType);
$reverseRelationshipType = $relationshipType->reverseRelationshipType();
if ($reverseRelationshipType) {
// create the reverse relationship
$this->setRelationship($otherContact, $contact, $reverseRelationshipType);
}
return $relationship;
}
/**
* Set a relationship between two contacts.
*
* @param Contact $contact
* @param Contact $otherContact
* @param RelationshipType $relationshipType
* @return Relationship
*/
public function setRelationship(Contact $contact, Contact $otherContact, RelationshipType $relationshipType): Relationship
{
return Relationship::create([
'account_id' => $relationshipType->account_id,
'relationship_type_id' => $relationshipType->id,
'contact_is' => $contact->id,
'of_contact' => $otherContact->id,
]);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Services\Contact\Relationship;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Relationship\Relationship;
use Illuminate\Database\Eloquent\Builder;
use App\Services\Contact\Contact\DestroyContact;
class DestroyRelationship extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'relationship_id' => 'required|integer|exists:relationships,id',
];
}
/**
* Destroy a relationship.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$relationship = Relationship::where('account_id', $data['account_id'])
->findOrFail($data['relationship_id']);
$relationship->contactIs->throwInactive();
$otherContact = $relationship->ofContact;
$this->deleteRelationship($relationship);
$this->deletePartialContact($otherContact);
return true;
}
/**
* Delete relationship.
*
* @param Relationship $relationship
*/
private function deleteRelationship(Relationship $relationship)
{
$reverseRelationship = $relationship->reverseRelationship();
if ($reverseRelationship) {
$reverseRelationship->delete();
}
$relationship->delete();
}
/**
* Delete partial contact.
*
* @param Contact $contact
*/
private function deletePartialContact(Contact $contact)
{
// the contact is partial - if the relationship is deleted, the partial
// contact has no reason to exist anymore
if ($contact->is_partial) {
$otherRelations = Relationship::where('account_id', $contact->account_id)
->where(function (Builder $query) use ($contact) {
return $query->where('of_contact', $contact->id)
->orWhere('contact_is', $contact->id);
})
->count();
if ($otherRelations == 0) {
DestroyContact::dispatch([
'account_id' => $contact->account_id,
'contact_id' => $contact->id,
]);
}
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Services\Contact\Relationship;
use App\Services\BaseService;
use App\Models\Relationship\Relationship;
use App\Models\Relationship\RelationshipType;
class UpdateRelationship extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'relationship_id' => 'required|integer|exists:relationships,id',
'relationship_type_id' => 'required|integer|exists:relationship_types,id',
];
}
/**
* Update a relationship.
*
* @param array $data
* @return Relationship
*/
public function execute(array $data): Relationship
{
$this->validate($data);
$relationship = Relationship::where('account_id', $data['account_id'])
->findOrFail($data['relationship_id']);
$relationship->contactIs->throwInactive();
$newRelationshipType = RelationshipType::where('account_id', $data['account_id'])
->findOrFail($data['relationship_type_id']);
$reverseRelationship = $relationship->reverseRelationship();
if ($reverseRelationship) {
$newReverseRelationshipType = $newRelationshipType->reverseRelationshipType();
if ($newReverseRelationshipType) {
$this->updateRelationship($reverseRelationship, $newReverseRelationshipType);
}
}
return $this->updateRelationship($relationship, $newRelationshipType);
}
/**
* Update one relationship.
*
* @param Relationship $relationship
* @param RelationshipType $relationshipType
* @return Relationship
*/
private function updateRelationship(Relationship $relationship, RelationshipType $relationshipType): Relationship
{
$relationship->update([
'relationship_type_id' => $relationshipType->id,
]);
return $relationship;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Services\Contact\Reminder;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use App\Models\Contact\Reminder;
class CreateReminder extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'initial_date' => 'required|date_format:Y-m-d',
'frequency_type' => [
'required',
Rule::in(Reminder::$frequencyTypes),
],
'frequency_number' => 'required|integer',
'title' => 'required|string|max:100000',
'description' => 'nullable|max:1000000',
'delible' => 'nullable|boolean',
];
}
/**
* Create a reminder.
*
* @param array $data
* @return Reminder
*/
public function execute(array $data): Reminder
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$reminder = Reminder::create([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'title' => $data['title'],
'description' => $this->nullOrValue($data, 'description'),
'initial_date' => $data['initial_date'],
'frequency_type' => $data['frequency_type'],
'frequency_number' => $data['frequency_number'],
'delible' => (isset($data['delible']) ? $data['delible'] : true),
]);
foreach ($contact->account->users as $user) {
$reminder->schedule($user);
}
return $reminder;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Services\Contact\Reminder;
use App\Services\BaseService;
use App\Models\Contact\Reminder;
class DestroyReminder extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'reminder_id' => 'required|integer|exists:reminders,id',
];
}
/**
* Destroy a reminder and all scheduled reminders that are associated with
* it (in ReminderOutbox table) thanks to foreign keys.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);
$reminder = Reminder::where('account_id', $data['account_id'])
->findOrFail($data['reminder_id']);
$reminder->contact->throwInactive();
$reminder->delete();
return true;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Services\Contact\Reminder;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use App\Models\Contact\Reminder;
class UpdateReminder extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer|exists:contacts,id',
'reminder_id' => 'required|integer|exists:reminders,id',
'initial_date' => 'required|date_format:Y-m-d',
'frequency_type' => [
'required',
Rule::in(Reminder::$frequencyTypes),
],
'frequency_number' => 'nullable|integer',
'title' => 'required|string|max:100000',
'description' => 'nullable|max:1000000',
'delible' => 'nullable|boolean',
];
}
/**
* Update a reminder.
*
* @param array $data
* @return Reminder
*/
public function execute(array $data): Reminder
{
$this->validate($data);
/** @var Reminder */
$reminder = Reminder::where('account_id', $data['account_id'])
->where('contact_id', $data['contact_id'])
->findOrFail($data['reminder_id']);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
$reminder->update([
'title' => $data['title'],
'description' => $this->nullOrValue($data, 'description'),
'initial_date' => $data['initial_date'],
'frequency_type' => $data['frequency_type'],
'frequency_number' => $this->nullOrValue($data, 'frequency_number'),
'delible' => (isset($data['delible']) ? $data['delible'] : true),
]);
foreach ($reminder->account->users as $user) {
$reminder->schedule($user);
}
return $reminder;
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace App\Services\Contact\Tag;
use App\Models\Contact\Tag;
use App\Services\BaseService;
use App\Models\Contact\Contact;
class AssociateTag extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer',
'name' => 'required|string',
];
}
/**
* Associate a tag to a contact.
*
* @param array $data
* @return Tag
*/
public function execute(array $data): Tag
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
// check if the tag already exists in the account
$tag = $this->tagExistOrCreate($data);
// associate the tag to the contact
$this->associateToContact($tag, $contact);
return $tag;
}
/**
* Check if the tag already exists in the account.
* If it does, returns it.
* If it doesn't, create it.
*
* @return Tag
*/
private function tagExistOrCreate(array $data): Tag
{
$tag = Tag::where([
'account_id' => $data['account_id'],
'name' => $data['name'],
])
->first();
if (! $tag) {
return $this->createTag($data);
}
return $tag;
}
/**
* Creates the tag.
*
* @return Tag
*/
private function createTag(array $data): Tag
{
return app(CreateTag::class)->execute([
'account_id' => $data['account_id'],
'name' => $data['name'],
]);
}
/**
* Associate the tag to the contact.
*
* @return void
*/
private function associateToContact(Tag $tag, Contact $contact)
{
// make sure the tag is not associated with the contact already
$contact->tags()->detach($tag->id);
$contact->tags()->syncWithoutDetaching([
$tag->id => [
'account_id' => $contact->account_id,
],
]);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Services\Contact\Tag;
use App\Models\Contact\Tag;
use Illuminate\Support\Str;
use App\Helpers\LocaleHelper;
use App\Services\BaseService;
class CreateTag extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'name' => 'required|string',
];
}
/**
* Create a tag.
*
* @param array $data
* @return Tag
*/
public function execute(array $data): Tag
{
$this->validate($data);
$array = [
'account_id' => $data['account_id'],
'name' => $data['name'],
'name_slug' => Str::slug($data['name'], '-', LocaleHelper::getLang()),
];
if (empty($array['name_slug'])) {
$array['name_slug'] = htmlentities($data['name']);
}
return Tag::create($array);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Services\Contact\Tag;
use App\Models\Contact\Tag;
use App\Services\BaseService;
class DestroyTag extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'tag_id' => 'required|integer',
];
}
/**
* Destroy a tag.
*
* @param array $data
* @return bool
*/
public function execute(array $data)
{
$this->validate($data);
$tag = Tag::where('account_id', $data['account_id'])
->findOrFail($data['tag_id']);
$tag->contacts()->detach();
$tag->delete();
return true;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Services\Contact\Tag;
use App\Models\Contact\Tag;
use App\Services\BaseService;
use App\Models\Contact\Contact;
class DetachTag extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'contact_id' => 'required|integer',
'tag_id' => 'required|integer',
];
}
/**
* Detach the tag associated with a contact.
*
* @param array $data
* @return void
*/
public function execute(array $data)
{
$this->validate($data);
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($data['contact_id']);
$contact->throwInactive();
Tag::where('account_id', $data['account_id'])
->findOrFail($data['tag_id']);
$contact->tags()->detach($data['tag_id']);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Services\Contact\Tag;
use App\Models\Contact\Tag;
use Illuminate\Support\Str;
use App\Helpers\LocaleHelper;
use App\Services\BaseService;
class UpdateTag extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'tag_id' => 'required|integer',
'name' => 'required|string',
];
}
/**
* Update a tag.
*
* @param array $data
* @return Tag
*/
public function execute(array $data): Tag
{
$this->validate($data);
/** @var Tag */
$tag = Tag::where('account_id', $data['account_id'])
->findOrFail($data['tag_id']);
$tag->name = $data['name'];
$tag->name_slug = Str::slug($data['name'], '-', LocaleHelper::getLang());
$tag->save();
return $tag;
}
}