Add an update command (#997)

This commit is contained in:
Alexis Saettler
2018-04-07 10:35:39 +02:00
committed by GitHub
parent bb87ef81a6
commit 2513137d12
19 changed files with 364 additions and 97 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -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:
+1 -1
View File
@@ -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
@@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands\Helpers;
use Illuminate\Console\Command;
use Symfony\Component\Console\Output\OutputInterface;
class CommandExecutor implements CommandExecutorInterface
{
/**
* @var Command
*/
protected $command;
/**
* Create a new CommandExecutor.
*
* @param Command
*/
public function __construct(Command $command)
{
$this->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('');
}
}
@@ -0,0 +1,10 @@
<?php
namespace App\Console\Commands\Helpers;
interface CommandExecutorInterface
{
public function exec($message, $command);
public function artisan($message, $command, array $arguments = []);
}
-10
View File
@@ -21,16 +21,6 @@ class LangGenerate extends Command
*/
protected $description = 'Generate i18n json assets';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
+24 -6
View File
@@ -6,15 +6,19 @@ use App\Contact;
use App\Instance;
use GuzzleHttp\Client;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Output\OutputInterface;
class PingVersionServer extends Command
{
use ConfirmableTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'monica:ping';
protected $signature = 'monica:ping {--force}';
/**
* The console command description.
@@ -30,11 +34,9 @@ class PingVersionServer extends Command
*/
public function handle()
{
if (! config('monica.check_version')) {
return false;
}
if (! \App::environment('production')) {
if (! $this->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);
}
}
}
+3 -13
View File
@@ -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('-----------------------------');
+22 -13
View File
@@ -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('');
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\ConfirmableTrait;
use App\Console\Commands\Helpers\CommandExecutor;
use App\Console\Commands\Helpers\CommandExecutorInterface;
class Update extends Command
{
use ConfirmableTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'monica:update {--force} {--composer-install} {--dev}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update monica dependencies and migrations after a new release';
/**
* The Command Executor.
*
* @var CommandExecutorInterface
*/
protected $commandExecutor;
/**
* Create a new command.
*
* @param CommandExecutorInterface
*/
public function __construct(CommandExecutorInterface $commandExecutor = null)
{
$this->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.');
}
}
}
+8 -8
View File
@@ -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',
];
/**
+4 -2
View File
@@ -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;
+35 -34
View File
@@ -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,
]);
}
}
+4
View File
@@ -25,6 +25,10 @@
<testsuite name="Api Tests">
<directory suffix="Test.php">./tests/Api</directory>
</testsuite>
<testsuite name="Commands Tests">
<directory suffix="Test.php">./tests/Commands</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
+4
View File
@@ -25,6 +25,10 @@
<testsuite name="Api Tests">
<directory suffix="Test.php">./tests/Api</directory>
</testsuite>
<testsuite name="Commands Tests">
<directory suffix="Test.php">./tests/Commands</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
+3 -1
View File
@@ -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 &
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Tests\Commands;
use App\Console\Commands\Helpers\CommandExecutorInterface;
class CommandExecutorTester implements CommandExecutorInterface
{
/**
* @var Collection
*/
public $buffer;
/**
* Create a new CommandExecutorTester.
*/
public function __construct()
{
$this->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]);
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Tests\Commands;
use Tests\TestCase;
use App\Console\Commands\Update;
class UpdateCommandTest extends TestCase
{
public function test_update_command_default()
{
$commandExecutor = new CommandExecutorTester();
$command = new Update($commandExecutor);
$command->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']);
}
}
+12 -9
View File
@@ -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);