diff --git a/.travis.yml.sig b/.travis.yml.sig
index 8d0777867..c2300c5a2 100644
Binary files a/.travis.yml.sig and b/.travis.yml.sig differ
diff --git a/CHANGELOG b/CHANGELOG
index a65592cfc..4388e2cd1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@ UNRELEASED CHANGES:
* Add Linkedin URL in the Contact object returned by the API
* Improve localization: add plural forms, localize every needed messages
* Split app.js in 3 files, and load translations files for Vue in separate files
+* Add a new monica:update command
RELEASED VERSIONS:
diff --git a/Procfile b/Procfile
index 08cfcef95..a6161ab11 100644
--- a/Procfile
+++ b/Procfile
@@ -1,3 +1,3 @@
web: vendor/bin/heroku-php-nginx -C nginx_app.conf /public
queue: php artisan queue:work --sleep=3 --tries=3
-release: php artisan migrate --force
+release: php artisan monica:update --force -vvv
diff --git a/app/Console/Commands/Helpers/CommandExecutor.php b/app/Console/Commands/Helpers/CommandExecutor.php
new file mode 100644
index 000000000..0208762db
--- /dev/null
+++ b/app/Console/Commands/Helpers/CommandExecutor.php
@@ -0,0 +1,53 @@
+command = $command;
+ }
+
+ public function exec($message, $commandline)
+ {
+ $this->command->info($message);
+ $this->command->line($commandline);
+ exec($commandline.' 2>&1', $output);
+ if ($this->command->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
+ foreach ($output as $line) {
+ $this->command->line($line);
+ }
+ }
+ $this->command->line('');
+ }
+
+ public function artisan($message, $commandline, array $arguments = [])
+ {
+ $this->command->info($message);
+ $info = '';
+ foreach ($arguments as $key => $value) {
+ $info = $info.' '.$key.'='.$value;
+ }
+ $this->command->line('php artisan '.$commandline.$info);
+ if ($this->command->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
+ $this->command->call($commandline, $arguments);
+ } else {
+ $this->command->callSilent($commandline, $arguments);
+ }
+ $this->command->line('');
+ }
+}
diff --git a/app/Console/Commands/Helpers/CommandExecutorInterface.php b/app/Console/Commands/Helpers/CommandExecutorInterface.php
new file mode 100644
index 000000000..63233074d
--- /dev/null
+++ b/app/Console/Commands/Helpers/CommandExecutorInterface.php
@@ -0,0 +1,10 @@
+confirmToProceed('Checking version deactivated', function () {
+ return ! config('monica.check_version') && $this->getLaravel()->environment() == 'production';
+ })) {
return false;
}
@@ -55,13 +57,18 @@ class PingVersionServer extends Command
// Send the JSON
try {
+ $this->log('Call url:'.config('monica.weekly_ping_server_url'));
$client = new Client();
$response = $client->post(config('monica.weekly_ping_server_url'), [
'json' => $data,
]);
} catch (\GuzzleHttp\Exception\ConnectException $e) {
+ $this->log('ConnectException...');
+
return;
} catch (\GuzzleHttp\Exception\TransferException $e) {
+ $this->log('TransferException...');
+
return;
}
@@ -71,6 +78,8 @@ class PingVersionServer extends Command
if (json_last_error() !== JSON_ERROR_NONE) {
// JSON is invalid
// The function json_last_error returns the last error occurred during the JSON encoding and decoding
+ $this->log('json error...');
+
return;
}
@@ -79,6 +88,8 @@ class PingVersionServer extends Command
return;
}
+ $this->log('instance version:'.$instance->current_version);
+ $this->log('current version:'.$json['latest_version']);
if ($json['latest_version'] != $instance->current_version) {
$instance->latest_version = $json['latest_version'];
$instance->latest_release_notes = $json['notes'];
@@ -90,4 +101,11 @@ class PingVersionServer extends Command
$instance->save();
}
}
+
+ public function log($string)
+ {
+ if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
+ $this->info($string);
+ }
+ }
}
diff --git a/app/Console/Commands/SetupProduction.php b/app/Console/Commands/SetupProduction.php
index d26536ebc..9bb0710ce 100644
--- a/app/Console/Commands/SetupProduction.php
+++ b/app/Console/Commands/SetupProduction.php
@@ -38,21 +38,11 @@ class SetupProduction extends Command
* If the .env file does not exist, then key generation
* will fail. So we create one if it does not already exist.
*/
- if (! file_exists('.env')) {
- touch('.env');
+ if (! file_exists(__DIR__.'/../../../.env')) {
+ touch(__DIR__.'/../../../.env');
}
- $this->callSilent('migrate', ['--force' => true]);
- $this->info('✓ Performed migrations');
-
- $this->call('db:seed', ['--class' => 'ActivityTypesTableSeeder', '--force' => true]);
- $this->info('✓ Filled the Activity Types table');
-
- $this->call('db:seed', ['--class' => 'CountriesSeederTable', '--force' => true]);
- $this->info('✓ Filled the Countries table');
-
- $this->callSilent('storage:link');
- $this->info('✓ Symlinked the storage folder for the avatars');
+ $this->callSilent('monica:update', ['--force' => true]);
$this->line('');
$this->line('-----------------------------');
diff --git a/app/Console/Commands/SetupTest.php b/app/Console/Commands/SetupTest.php
index 5c9a91144..5f4b7a976 100644
--- a/app/Console/Commands/SetupTest.php
+++ b/app/Console/Commands/SetupTest.php
@@ -32,22 +32,14 @@ class SetupTest extends Command
return;
}
- $this->callSilent('migrate:fresh');
- $this->info('✓ Performed migrations');
+ $this->artisan('✓ Performing migrations', 'migrate:fresh');
- $this->call('db:seed', ['--class' => 'ActivityTypesTableSeeder']);
- $this->info('✓ Filled the Activity Types table');
-
- $this->call('db:seed', ['--class' => 'CountriesSeederTable']);
- $this->info('✓ Filled the Countries table');
-
- $this->callSilent('storage:link');
- $this->info('✓ Symlinked the storage folder for the avatars');
+ $this->artisan('✓ Filling the Activity Types table', 'db:seed', ['--class' => 'ActivityTypesTableSeeder']);
+ $this->artisan('✓ Filling the Countries table', 'db:seed', ['--class' => 'CountriesSeederTable']);
+ $this->artisan('✓ Symlink the storage folder', 'storage:link');
if (! $this->option('skipSeed')) {
- $this->call('db:seed', ['--class' => 'FakeContentTableSeeder']);
- $this->info('');
- $this->info('✓ Filled database with fake data');
+ $this->artisan('✓ Filling database with fake data', 'db:seed', ['--class' => 'FakeContentTableSeeder']);
}
$this->line('');
@@ -64,4 +56,21 @@ class SetupTest extends Command
$this->info('Setup is done. Have fun.');
}
+
+ public function exec($message, $command)
+ {
+ $this->info($message);
+ $this->line($command);
+ exec($command, $output);
+ $this->line(implode('\n', $output));
+ $this->line('');
+ }
+
+ public function artisan($message, $command, array $arguments = [])
+ {
+ $this->info($message);
+ $this->line('php artisan '.$command);
+ $this->callSilent($command, $arguments);
+ $this->line('');
+ }
}
diff --git a/app/Console/Commands/Update.php b/app/Console/Commands/Update.php
new file mode 100644
index 000000000..b52595324
--- /dev/null
+++ b/app/Console/Commands/Update.php
@@ -0,0 +1,99 @@
+commandExecutor = $commandExecutor ?: new CommandExecutor($this);
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ if ($this->confirmToProceed()) {
+ try {
+ $this->commandExecutor->artisan('✓ Maintenance mode: on', 'down', [
+ '--message' => 'Upgrading Monica v'.config('monica.app_version'),
+ '--retry' => '10',
+ ]);
+
+ // Clear or rebuild all cache
+ $this->commandExecutor->artisan('✓ Resetting application cache', 'cache:clear');
+ if ($this->getLaravel()->environment() == 'production') {
+ $this->commandExecutor->artisan('✓ Resetting config cache', 'config:cache');
+ $this->commandExecutor->artisan('✓ Resetting route cache', 'route:cache');
+ if ($this->getLaravel()->version() > '5.6') {
+ $this->commandExecutor->artisan('✓ Resetting view cache', 'view:cache');
+ } else {
+ $this->commandExecutor->artisan('✓ Resetting view cache', 'view:clear');
+ }
+ } else {
+ $this->commandExecutor->artisan('✓ Clear config cache', 'config:clear');
+ $this->commandExecutor->artisan('✓ Clear route cache', 'route:clear');
+ $this->commandExecutor->artisan('✓ Clear view cache', 'view:clear');
+ }
+
+ if ($this->option('composer-install') === true) {
+ $this->commandExecutor->exec('✓ Updating composer dependencies', 'composer install --no-interaction --no-suggest --ignore-platform-reqs'.($this->option('composer-install') === false ? '--no-dev' : ''));
+ }
+
+ $this->commandExecutor->artisan('✓ Performing migrations', 'migrate', ['--force' => 'true']);
+
+ if (DB::table('activity_types')->count() == 0) {
+ $this->commandExecutor->artisan('✓ Filling the Activity Types table', 'db:seed', ['--class' => 'ActivityTypesTableSeeder', '--force' => 'true']);
+ }
+ if (DB::table('countries')->count() == 0) {
+ $this->commandExecutor->artisan('✓ Filling the Countries table', 'db:seed', ['--class' => 'CountriesSeederTable', '--force' => 'true']);
+ }
+ if ($this->getLaravel()->environment() != 'testing' && ! file_exists(public_path('storage'))) {
+ $this->commandExecutor->artisan('✓ Symlink the storage folder', 'storage:link');
+ }
+ } finally {
+ $this->commandExecutor->artisan('✓ Maintenance mode: off', 'up');
+ }
+
+ $this->line('Monica v'.config('monica.app_version').' is set up, enjoy.');
+ }
+ }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 0e69addfe..948afb73b 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -13,18 +13,18 @@ class Kernel extends ConsoleKernel
* @var array
*/
protected $commands = [
- // Commands\Inspire::class,
- 'App\Console\Commands\SendNotifications',
- 'App\Console\Commands\SendReminders',
'App\Console\Commands\CalculateStatistics',
- 'App\Console\Commands\ImportCSV',
- 'App\Console\Commands\SetupProduction',
- 'App\Console\Commands\ImportVCards',
- 'App\Console\Commands\PingVersionServer',
- 'App\Console\Commands\SetupTest',
'App\Console\Commands\Deactivate2FA',
'App\Console\Commands\GetVersion',
+ 'App\Console\Commands\ImportCSV',
+ 'App\Console\Commands\ImportVCards',
'App\Console\Commands\LangGenerate',
+ 'App\Console\Commands\PingVersionServer',
+ 'App\Console\Commands\SendNotifications',
+ 'App\Console\Commands\SendReminders',
+ 'App\Console\Commands\SetupProduction',
+ 'App\Console\Commands\SetupTest',
+ 'App\Console\Commands\Update',
];
/**
diff --git a/config/database.php b/config/database.php
index 2eb23c71c..62f3143a0 100644
--- a/config/database.php
+++ b/config/database.php
@@ -157,8 +157,7 @@ if (env('HEROKU')) {
$db['connections']['heroku'] = [
'driver' => 'mysql',
'host' => $url['host'],
- 'port' => $url['port'],
- 'database' => substr($url['path'], 1),
+ 'database' => starts_with($url['path'], '/') ? str_after($url['path'], '/') : $url['path'],
'username' => $url['user'],
'password' => $url['pass'],
'charset' => 'utf8',
@@ -166,6 +165,9 @@ if (env('HEROKU')) {
'strict' => false,
'schema' => 'public',
];
+ if (array_key_exists('port', $url)) {
+ $db['connections']['heroku']['port'] = $url['port'];
+ }
}
return $db;
diff --git a/database/seeds/ActivityTypesTableSeeder.php b/database/seeds/ActivityTypesTableSeeder.php
index e410e15fe..bfa3d2433 100644
--- a/database/seeds/ActivityTypesTableSeeder.php
+++ b/database/seeds/ActivityTypesTableSeeder.php
@@ -11,14 +11,15 @@ class ActivityTypesTableSeeder extends Seeder
*/
public function run()
{
+ $now = now();
DB::table('activity_types')->truncate();
DB::table('activity_type_groups')->truncate();
// SIMPLE ACTIVITIES
DB::table('activity_type_groups')->insert([
'key' => 'simple_activities',
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -26,8 +27,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'hang_out',
'activity_type_group_id' => 1,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -35,8 +36,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'my_place',
'icon' => 'movie_home',
'activity_type_group_id' => 1,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -44,15 +45,15 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'my_place',
'icon' => 'talk_home',
'activity_type_group_id' => 1,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
// SPORT
DB::table('activity_type_groups')->insert([
'key' => 'sport',
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -60,15 +61,15 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'sport',
'activity_type_group_id' => 2,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
// FOOD
DB::table('activity_type_groups')->insert([
'key' => 'food',
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -76,8 +77,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'his_place',
'icon' => 'ate_his_place',
'activity_type_group_id' => 3,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -85,8 +86,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'bar',
'activity_type_group_id' => 3,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -94,8 +95,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'my_place',
'icon' => 'ate_home',
'activity_type_group_id' => 3,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -103,8 +104,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'picknicked',
'activity_type_group_id' => 3,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -112,15 +113,15 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'restaurant',
'activity_type_group_id' => 3,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
// CULTURAL
DB::table('activity_type_groups')->insert([
'key' => 'cultural_activities',
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -128,8 +129,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'theater',
'activity_type_group_id' => 4,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -137,8 +138,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'concert',
'activity_type_group_id' => 4,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -146,8 +147,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'play',
'activity_type_group_id' => 4,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
DB::table('activity_types')->insert([
@@ -155,8 +156,8 @@ class ActivityTypesTableSeeder extends Seeder
'location_type' => 'outside',
'icon' => 'museum',
'activity_type_group_id' => 4,
- 'created_at' => now(),
- 'updated_at' => now(),
+ 'created_at' => $now,
+ 'updated_at' => $now,
]);
}
}
diff --git a/phpunit.xml b/phpunit.xml
index 36510a4ef..d656e5c5f 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -25,6 +25,10 @@
./tests/Api
+
+
+ ./tests/Commands
+
diff --git a/phpunitpostgres.xml b/phpunitpostgres.xml
index 9f84e3ba3..094e8bd30 100644
--- a/phpunitpostgres.xml
+++ b/phpunitpostgres.xml
@@ -25,6 +25,10 @@
./tests/Api
+
+
+ ./tests/Commands
+
diff --git a/scripts/docker/test-server.sh b/scripts/docker/test-server.sh
index 9a4b7d909..9b9b1c2fe 100755
--- a/scripts/docker/test-server.sh
+++ b/scripts/docker/test-server.sh
@@ -18,7 +18,9 @@ if [[ -z ${APP_KEY:-} || "$APP_KEY" == "ChangeMeBy32KeyLengthOrGenerated" ]]; th
else
echo "APP_KEY already set"
fi
-${ARTISAN} setup:production --force
+
+# Run migrations
+${ARTISAN} monica:update --force
# Run cron
crond -b &
diff --git a/tests/Commands/CommandExecutorTester.php b/tests/Commands/CommandExecutorTester.php
new file mode 100644
index 000000000..a37e5ae88
--- /dev/null
+++ b/tests/Commands/CommandExecutorTester.php
@@ -0,0 +1,35 @@
+buffer = collect([]);
+ }
+
+ public function exec($message, $commandline)
+ {
+ $this->buffer->push(['message' => $message, 'command' => $commandline]);
+ }
+
+ public function artisan($message, $commandline, array $arguments = [])
+ {
+ $info = '';
+ foreach ($arguments as $key => $value) {
+ $info = $info.' '.$key.'='.$value;
+ }
+ $this->buffer->push(['message' =>$message, 'command' => 'php artisan '.$commandline.$info]);
+ }
+}
diff --git a/tests/Commands/UpdateCommandTest.php b/tests/Commands/UpdateCommandTest.php
new file mode 100644
index 000000000..4f02dc12c
--- /dev/null
+++ b/tests/Commands/UpdateCommandTest.php
@@ -0,0 +1,46 @@
+setLaravel($this->createApplication());
+
+ $command->run(new \Symfony\Component\Console\Input\ArrayInput([]), new \Symfony\Component\Console\Output\NullOutput());
+
+ $this->assertCount(7, $commandExecutor->buffer);
+ $this->assertCommandContains($commandExecutor->buffer[0], 'Maintenance mode: on', 'php artisan down');
+ $this->assertCommandContains($commandExecutor->buffer[1], 'Resetting application cache', 'php artisan cache:clear');
+ $this->assertCommandContains($commandExecutor->buffer[5], 'Performing migrations', 'php artisan migrate');
+ $this->assertCommandContains($commandExecutor->buffer[6], 'Maintenance mode: off', 'php artisan up');
+ }
+
+ public function test_update_command_composer()
+ {
+ $commandExecutor = new CommandExecutorTester();
+ $command = new Update($commandExecutor);
+ $command->setLaravel($this->createApplication());
+
+ $command->run(new \Symfony\Component\Console\Input\ArrayInput(['--composer-install' => true]), new \Symfony\Component\Console\Output\NullOutput());
+
+ $this->assertCount(8, $commandExecutor->buffer);
+ $this->assertCommandContains($commandExecutor->buffer[0], 'Maintenance mode: on', 'php artisan down');
+ $this->assertCommandContains($commandExecutor->buffer[1], 'Resetting application cache', 'php artisan cache:clear');
+ $this->assertCommandContains($commandExecutor->buffer[5], 'Updating composer dependencies', 'composer install');
+ $this->assertCommandContains($commandExecutor->buffer[6], 'Performing migrations', 'php artisan migrate');
+ $this->assertCommandContains($commandExecutor->buffer[7], 'Maintenance mode: off', 'php artisan up');
+ }
+
+ public function assertCommandContains($array, $message, $command)
+ {
+ $this->assertContains($message, $array['message']);
+ $this->assertContains($command, $array['command']);
+ }
+}
diff --git a/tests/Unit/ContactTest.php b/tests/Unit/ContactTest.php
index a5d307abe..e94482a7d 100644
--- a/tests/Unit/ContactTest.php
+++ b/tests/Unit/ContactTest.php
@@ -842,9 +842,10 @@ class ContactTest extends FeatureTestCase
public function test_it_sets_a_relationship_between_two_contacts()
{
- $contact = factory(Contact::class)->create(['account_id' => 1]);
- $partner = factory(Contact::class)->create(['account_id' => 1]);
- $relationshipType = factory('App\RelationshipType')->create(['account_id' => 1]);
+ $account = factory('App\Account')->create([]);
+ $contact = factory(Contact::class)->create(['account_id' => $account->id]);
+ $partner = factory(Contact::class)->create(['account_id' => $account->id]);
+ $relationshipType = factory('App\RelationshipType')->create(['account_id' => $account->id]);
$contact->setRelationship($partner, $relationshipType->id);
@@ -921,12 +922,13 @@ class ContactTest extends FeatureTestCase
public function test_it_deletes_relationship_between_two_contacts_and_deletes_the_contact()
{
- $contact = factory(Contact::class)->create(['account_id' => 1]);
+ $account = factory('App\Account')->create([]);
+ $contact = factory(Contact::class)->create(['account_id' => $account->id]);
$partner = factory(Contact::class)->create([
- 'account_id' => 1,
+ 'account_id' => $account->id,
'is_partial' => true,
]);
- $relationshipType = factory('App\RelationshipType')->create(['account_id' => 1]);
+ $relationshipType = factory('App\RelationshipType')->create(['account_id' => $account->id]);
$contact->setRelationship($partner, $relationshipType->id);
@@ -944,12 +946,13 @@ class ContactTest extends FeatureTestCase
public function test_it_deletes_relationship_between_two_contacts_and_doesnt_delete_the_contact()
{
- $contact = factory(Contact::class)->create(['account_id' => 1]);
+ $account = factory('App\Account')->create([]);
+ $contact = factory(Contact::class)->create(['account_id' => $account->id]);
$partner = factory(Contact::class)->create([
- 'account_id' => 1,
+ 'account_id' => $account->id,
'is_partial' => false,
]);
- $relationshipType = factory('App\RelationshipType')->create(['account_id' => 1]);
+ $relationshipType = factory('App\RelationshipType')->create(['account_id' => $account->id]);
$contact->setRelationship($partner, $relationshipType->id);