refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
This commit is contained in:
69
app/Console/Commands/CalculateStatistics.php
Normal file
69
app/Console/Commands/CalculateStatistics.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Instance\Statistic;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CalculateStatistics extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:calculatestatistics';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Calculate general usage statistics';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$statistic = new Statistic;
|
||||
$statistic->number_of_users = DB::table('users')->count();
|
||||
$statistic->number_of_contacts = DB::table('contacts')->count();
|
||||
$statistic->number_of_notes = DB::table('notes')->count();
|
||||
$statistic->number_of_reminders = DB::table('reminders')->count();
|
||||
$statistic->number_of_tasks = DB::table('tasks')->count();
|
||||
$statistic->number_of_invitations_sent = DB::table('accounts')->sum('number_of_invitations_sent');
|
||||
|
||||
// number_of_accounts_with_more_than_one_user
|
||||
$number_of_accounts_with_more_than_one_user = 0;
|
||||
foreach (Account::all() as $account) {
|
||||
if ($account->users()->count() > 1) {
|
||||
$number_of_accounts_with_more_than_one_user = $number_of_accounts_with_more_than_one_user + 1;
|
||||
}
|
||||
}
|
||||
$statistic->number_of_accounts_with_more_than_one_user = $number_of_accounts_with_more_than_one_user;
|
||||
$statistic->number_of_import_jobs = DB::table('import_jobs')->count();
|
||||
$statistic->number_of_tags = DB::table('tags')->count();
|
||||
$statistic->number_of_activities = DB::table('activities')->count();
|
||||
$statistic->number_of_addresses = DB::table('addresses')->count();
|
||||
$statistic->number_of_api_calls = DB::table('api_usage')->count();
|
||||
$statistic->number_of_calls = DB::table('calls')->count();
|
||||
$statistic->number_of_contact_fields = DB::table('contact_fields')->count();
|
||||
$statistic->number_of_contact_field_types = DB::table('contact_field_types')->count();
|
||||
$statistic->number_of_debts = DB::table('debts')->count();
|
||||
$statistic->number_of_entries = DB::table('entries')->count();
|
||||
$statistic->number_of_gifts = DB::table('gifts')->count();
|
||||
$statistic->number_of_oauth_access_tokens = DB::table('oauth_access_tokens')->count();
|
||||
$statistic->number_of_oauth_clients = DB::table('oauth_clients')->count();
|
||||
$statistic->number_of_relationships = DB::table('relationships')->count();
|
||||
$statistic->number_of_subscriptions = DB::table('subscriptions')->count();
|
||||
$statistic->number_of_conversations = DB::table('conversations')->count();
|
||||
$statistic->number_of_messages = DB::table('messages')->count();
|
||||
|
||||
$statistic->save();
|
||||
}
|
||||
}
|
||||
53
app/Console/Commands/Clean.php
Normal file
53
app/Console/Commands/Clean.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\SyncToken;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Events\TokenDeleteEvent;
|
||||
use App\Services\Instance\TokenClean;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class Clean extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:clean
|
||||
{--dry-run : Do everything except actually clean monica instance.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clean monica instance';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Event::listen(TokenDeleteEvent::class, function ($event) {
|
||||
$this->handleTokenDelete($event->token);
|
||||
});
|
||||
|
||||
app(TokenClean::class)->execute([
|
||||
'dryrun' => (bool) $this->option('dry-run'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle TokenDeleteEvent event.
|
||||
*
|
||||
* @param SyncToken $token
|
||||
*/
|
||||
private function handleTokenDelete($token)
|
||||
{
|
||||
$this->info('Delete token '.$token->id.' - User '.$token->user_id.' - Type '.$token->name.' - timestamp '.$token->timestamp);
|
||||
}
|
||||
}
|
||||
70
app/Console/Commands/CreateAccount.php
Normal file
70
app/Console/Commands/CreateAccount.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
class CreateAccount extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'account:create
|
||||
{--email= : Login email for the account.}
|
||||
{--password= : Password to set for the account.}
|
||||
{--firstname= : First name for the account.}
|
||||
{--lastname= : Last name for the account.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new account';
|
||||
|
||||
/**
|
||||
* Missing argument errors. Exposed for testing.
|
||||
*/
|
||||
const ERROR_MISSING_EMAIL = '! You must specify an email';
|
||||
const ERROR_MISSING_PASSWORD = '! You must specify a password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$email = $this->option('email');
|
||||
if (empty($email)) {
|
||||
$this->error($this::ERROR_MISSING_EMAIL);
|
||||
}
|
||||
|
||||
$password = $this->option('password');
|
||||
if (empty($password)) {
|
||||
$this->error($this::ERROR_MISSING_PASSWORD);
|
||||
}
|
||||
|
||||
$firstName = $this->option('firstname') ?? 'John';
|
||||
|
||||
$lastName = $this->option('lastname') ?? 'Doe';
|
||||
|
||||
if (empty($email) || empty($password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->confirmToProceed('This will create a new user for '.$firstName.' '.$lastName.' with email '.$email)) {
|
||||
Account::createDefault($firstName, $lastName, $email, $password);
|
||||
|
||||
$this->info('| You can now sign in to your account:');
|
||||
$this->line('| username: '.$email);
|
||||
$this->line('| password: <hidden>');
|
||||
}
|
||||
}
|
||||
}
|
||||
55
app/Console/Commands/DavClientsUpdate.php
Normal file
55
app/Console/Commands/DavClientsUpdate.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\SynchronizeAddressBooks;
|
||||
use App\Models\Account\AddressBookSubscription;
|
||||
|
||||
class DavClientsUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:davclients';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update all dav subscriptions';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$subscriptions = AddressBookSubscription::active()->get();
|
||||
|
||||
$now = now();
|
||||
$subscriptions->filter(function ($subscription) use ($now) {
|
||||
return $this->isTimeToRunSync($subscription, $now);
|
||||
})->each(function ($subscription) {
|
||||
SynchronizeAddressBooks::dispatch($subscription);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the last synchronized timestamp is older than the subscription's frequency time.
|
||||
*
|
||||
* @param AddressBookSubscription $subscription
|
||||
* @param Carbon $now
|
||||
* @return bool
|
||||
*/
|
||||
private function isTimeToRunSync(AddressBookSubscription $subscription, Carbon $now): bool
|
||||
{
|
||||
return is_null($subscription->last_synchronized_at)
|
||||
|| $subscription->last_synchronized_at->addMinutes($subscription->frequency)->lessThan($now);
|
||||
}
|
||||
}
|
||||
75
app/Console/Commands/Deactivate2FA.php
Normal file
75
app/Console/Commands/Deactivate2FA.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Deactivate2FA extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = '2fa:deactivate
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--email= : The email of the user to deactivate 2FA.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Deactivate 2FA for this user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// retrieve the email from the option
|
||||
$email = $this->option('email');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = $this->ask('what is the user\'s email?');
|
||||
}
|
||||
|
||||
// retrieve the user with the specified email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (! $user) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('No user with that email.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (is_null($user->google2fa_secret)) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('2FA is currently not activated for this user.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Print a warning
|
||||
$this->info('2FA will be deactivated for '.$user->email);
|
||||
$this->info('This action can\'t be cancelled.');
|
||||
|
||||
// ask for confirmation if not forced
|
||||
if (! $this->option('force') && ! $this->confirm('Do you wish to continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove google2fa_secret key
|
||||
$user->google2fa_secret = null;
|
||||
|
||||
// save the user
|
||||
$user->save();
|
||||
|
||||
// show the new secret key
|
||||
$this->info('2FA has been deactivated for '.$user->email);
|
||||
}
|
||||
}
|
||||
34
app/Console/Commands/ExportAll.php
Normal file
34
app/Console/Commands/ExportAll.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\ExportAllAsSQL;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ExportAll extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'export:all';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Exports all data as SQL to storage/app';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$job = new ExportAllAsSQL();
|
||||
$this->info('Exported as '.$job->handle().'');
|
||||
}
|
||||
}
|
||||
32
app/Console/Commands/GetVersion.php
Normal file
32
app/Console/Commands/GetVersion.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class GetVersion extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:getversion';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Get current version of monica';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->line(config('monica.app_version'));
|
||||
}
|
||||
}
|
||||
71
app/Console/Commands/Helpers/Command.php
Normal file
71
app/Console/Commands/Helpers/Command.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Helpers;
|
||||
|
||||
use Tests\Helpers\CommandCallerFake;
|
||||
|
||||
/**
|
||||
* @method static void exec(\Illuminate\Console\Command $command, string $message, string $commandline)
|
||||
* @method static void artisan(\Illuminate\Console\Command $command, string $message, string $commandline, array $arguments)
|
||||
*/
|
||||
class Command
|
||||
{
|
||||
/**
|
||||
* Switch to a fake executor for testing purpose.
|
||||
*
|
||||
* @return CommandCallerContract
|
||||
*/
|
||||
public static function fake(): CommandCallerContract
|
||||
{
|
||||
static::setBackend($fake = app(CommandCallerFake::class));
|
||||
|
||||
return $fake;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Command Executor.
|
||||
*
|
||||
* @var CommandCallerContract|null
|
||||
*/
|
||||
private static $commandCaller;
|
||||
|
||||
/**
|
||||
* Get the current backend command.
|
||||
*
|
||||
* @return CommandCallerContract
|
||||
*/
|
||||
private static function getBackend(): CommandCallerContract
|
||||
{
|
||||
if (! static::$commandCaller) {
|
||||
static::$commandCaller = app(CommandCaller::class); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return static::$commandCaller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current backend command.
|
||||
*
|
||||
* @param CommandCallerContract $executor
|
||||
*/
|
||||
public static function setBackend(CommandCallerContract $executor): void
|
||||
{
|
||||
static::$commandCaller = $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic, static calls to the object.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
$instance = static::getBackend();
|
||||
|
||||
return $instance->$method(...$args);
|
||||
}
|
||||
}
|
||||
54
app/Console/Commands/Helpers/CommandCaller.php
Normal file
54
app/Console/Commands/Helpers/CommandCaller.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Helpers;
|
||||
|
||||
use function Safe\exec;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\Application;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CommandCaller implements CommandCallerContract
|
||||
{
|
||||
/**
|
||||
* Print a message on the console, then execute a command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Command to execute
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function exec(Command $command, string $message, string $commandline): void
|
||||
{
|
||||
$command->info($message);
|
||||
$command->line($commandline, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
exec($commandline.' 2>&1', $output);
|
||||
foreach ($output as $line) {
|
||||
$command->line($line, null, OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
}
|
||||
$command->line('', null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a message on the console, then execute an artisan command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Artisan command name to execute
|
||||
* @param array $arguments Optional arguments to pass to the artisan command
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function artisan(Command $command, string $message, string $commandline, array $arguments = []): void
|
||||
{
|
||||
$info = '';
|
||||
foreach ($arguments as $key => $value) {
|
||||
if (is_string($key)) {
|
||||
$info .= ' '.$key.'="'.$value.'"';
|
||||
} else {
|
||||
$info .= ' '.$value;
|
||||
}
|
||||
}
|
||||
$this->exec($command, $message, Application::formatCommandString($commandline.$info));
|
||||
}
|
||||
}
|
||||
27
app/Console/Commands/Helpers/CommandCallerContract.php
Normal file
27
app/Console/Commands/Helpers/CommandCallerContract.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Helpers;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
interface CommandCallerContract
|
||||
{
|
||||
/**
|
||||
* Print a message on the console, then execute a command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Command to execute
|
||||
*/
|
||||
public function exec(Command $command, string $message, string $commandline): void;
|
||||
|
||||
/**
|
||||
* Print a message on the console, then execute an artisan command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Artisan command name to execute
|
||||
* @param array $arguments Optional arguments to pass to the artisan command
|
||||
*/
|
||||
public function artisan(Command $command, string $message, string $commandline, array $arguments = []): void;
|
||||
}
|
||||
134
app/Console/Commands/ImportAccounts.php
Normal file
134
app/Console/Commands/ImportAccounts.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ImportAccounts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'account:import_ldap
|
||||
{--ldap_uri= : LDAP URI.}
|
||||
{--ldap_user= : LDAP Bind DN.}
|
||||
{--ldap_pass= : LDAP Bind Password.}
|
||||
{--ldap_base= : LDAP base DN for searching.}
|
||||
{--ldap_filter= : Filter to search for user accounts.}
|
||||
{--ldap_attr_mail= : LDAP attribute to map to email (default: mail).}
|
||||
{--ldap_attr_firstname= : LDAP attribute to map to firstname (default: gn).}
|
||||
{--ldap_attr_lastname= : LDAP attribute to map to lastname (default: sn).}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import user accounts from LDAP';
|
||||
|
||||
/**
|
||||
* Missing argument errors. Exposed for testing.
|
||||
*/
|
||||
const ERROR_MISSING_LDAP_FILTER = '! You must specify an LDAP Filter';
|
||||
const ERROR_MISSING_LDAP_BASE = '! You must specify an LDAP Base';
|
||||
const ERROR_MISSING_LDAP_USER = '! You must specify an LDAP User';
|
||||
const ERROR_MISSING_LDAP_PASS = '! You must specify an LDAP Password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$ldap_uri = $this->option('ldap_uri') ?? '127.0.0.1';
|
||||
$ldap_attr_mail = $this->option('ldap_attr_mail') ?? 'mail';
|
||||
$ldap_attr_firstname = $this->option('ldap_attr_firstname') ?? 'givenName';
|
||||
$ldap_attr_lastname = $this->option('ldap_attr_lastname') ?? 'sn';
|
||||
|
||||
$ldap_user = $this->option('ldap_user');
|
||||
if (empty($ldap_user)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_USER);
|
||||
}
|
||||
|
||||
$ldap_pass = $this->option('ldap_pass');
|
||||
if (empty($ldap_pass)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_PASS);
|
||||
}
|
||||
|
||||
$ldap_base = $this->option('ldap_base');
|
||||
if (empty($ldap_base)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_BASE);
|
||||
}
|
||||
|
||||
$ldap_filter = $this->option('ldap_filter');
|
||||
if (empty($ldap_filter)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_FILTER);
|
||||
}
|
||||
|
||||
if (empty($ldap_user) || empty($ldap_pass) || empty($ldap_base) || empty($ldap_filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ldap_conn = ldap_connect($ldap_uri);
|
||||
if (! $ldap_conn) {
|
||||
$this->error('Could not connect to LDAP URI');
|
||||
|
||||
return;
|
||||
}
|
||||
if (! ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
$this->error('Could not set LDAP protocol v3');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$bind = ldap_bind($ldap_conn, $ldap_user, $ldap_pass);
|
||||
if (! $bind) {
|
||||
$this->error('Could not bind with given LDAP credentials');
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ldap_res = [];
|
||||
try {
|
||||
$ldap_res = ldap_search($ldap_conn, $ldap_base, $ldap_filter, [$ldap_attr_mail, $ldap_attr_firstname, $ldap_attr_lastname]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ldap_data = ldap_get_entries($ldap_conn, $ldap_res);
|
||||
|
||||
for ($i = 0; $i < $ldap_data['count']; $i++) {
|
||||
if (! (isset($ldap_data[$i][$ldap_attr_mail]) && $ldap_data[$i][$ldap_attr_mail]['count'] > 0)) {
|
||||
continue;
|
||||
}
|
||||
$user_mail = $ldap_data[$i][$ldap_attr_mail][0];
|
||||
$user_firstname = 'John';
|
||||
$user_lastname = 'Doe';
|
||||
$user_password = bin2hex(random_bytes(64));
|
||||
if (isset($ldap_data[$i][$ldap_attr_firstname]) && $ldap_data[$i][$ldap_attr_firstname]['count'] > 0) {
|
||||
$user_firstname = $ldap_data[$i][$ldap_attr_firstname][0];
|
||||
}
|
||||
if (isset($ldap_data[$i][$ldap_attr_lastname]) && $ldap_data[$i][$ldap_attr_lastname]['count'] > 0) {
|
||||
$user_lastname = $ldap_data[$i][$ldap_attr_lastname][0];
|
||||
}
|
||||
$this->info('Importing user "'.$user_mail.'"');
|
||||
try {
|
||||
Account::createDefault($user_firstname, $user_lastname, $user_mail, $user_password);
|
||||
} catch (\Exception $import_error) {
|
||||
$this->warn('Could not import user "'.$user_mail.'": '.$import_error->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
249
app/Console/Commands/ImportCSV.php
Normal file
249
app/Console/Commands/ImportCSV.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\fopen;
|
||||
use App\Models\User\User;
|
||||
use function Safe\fclose;
|
||||
use App\Helpers\DateHelper;
|
||||
use App\Models\Contact\Gender;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Contact\ContactField;
|
||||
use App\Models\Contact\ContactFieldType;
|
||||
use App\Jobs\Avatars\GetAvatarsFromInternet;
|
||||
use App\Services\Contact\Address\CreateAddress;
|
||||
use App\Services\Contact\Reminder\CreateReminder;
|
||||
|
||||
class ImportCSV extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'import:csv {user : email or id of a user} {file : path to the CSV file} {--format=google}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Imports CSV in Google format to user account';
|
||||
|
||||
/**
|
||||
* The contact field email object.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $contactFieldEmailId;
|
||||
|
||||
/**
|
||||
* The contact field phone object.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $contactFieldPhoneId;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$file = $this->argument('file');
|
||||
|
||||
if (is_numeric($this->argument('user'))) {
|
||||
$user = User::find($this->argument('user'));
|
||||
} else {
|
||||
$user = User::where('email', $this->argument('user'))->first();
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
$this->error('You need to provide a valid User ID or email address!');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (! file_exists($file)) {
|
||||
$this->error('You need to provide a valid file path.');
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (is_string($file)) {
|
||||
$this->info("Importing CSV file {$file} to user {$user->id}");
|
||||
}
|
||||
|
||||
// create special gender for this import
|
||||
// we don't know which gender all the contacts are, so we need to create a special status for them, as we
|
||||
// can't guess whether they are men, women or else.
|
||||
$gender = Gender::where('name', config('dav.default_gender'))->first();
|
||||
if (! $gender) {
|
||||
$gender = new Gender;
|
||||
$gender->account_id = $user->account_id;
|
||||
$gender->name = config('dav.default_gender');
|
||||
$gender->save();
|
||||
}
|
||||
|
||||
$first = true;
|
||||
$imported = 0;
|
||||
$handle = fopen($file, 'r');
|
||||
try {
|
||||
while (($data = fgetcsv($handle)) !== false) { /** @phpstan-ignore-line */
|
||||
// don't import the columns
|
||||
if ($first) {
|
||||
$first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if first & last name do not exist skip row
|
||||
if (empty($data[1]) && empty($data[3])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->csvToContact($data, $user->account_id, $gender->id);
|
||||
|
||||
$imported++;
|
||||
}
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
$this->info("Imported {$imported} Contacts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create contact.
|
||||
*/
|
||||
private function csvToContact($data, $account_id, $gender_id)
|
||||
{
|
||||
$contact = new Contact();
|
||||
$contact->account_id = $account_id;
|
||||
$contact->gender_id = $gender_id;
|
||||
|
||||
if (! empty($data[1])) {
|
||||
$contact->first_name = $data[1]; // Given Name
|
||||
}
|
||||
|
||||
if (! empty($data[2])) {
|
||||
$contact->middle_name = $data[2]; // Additional Name
|
||||
}
|
||||
|
||||
if (! empty($data[3])) {
|
||||
$contact->last_name = $data[3]; // Family Name
|
||||
}
|
||||
|
||||
$street = null;
|
||||
if (! empty($data[49])) {
|
||||
$street = $data[49]; // address 1 street
|
||||
}
|
||||
|
||||
$city = null;
|
||||
if (! empty($data[50])) {
|
||||
$city = $data[50]; // address 1 city
|
||||
}
|
||||
|
||||
$province = null;
|
||||
if (! empty($data[52])) {
|
||||
$province = $data[52]; // address 1 region (state)
|
||||
}
|
||||
|
||||
$postalCode = null;
|
||||
if (! empty($data[53])) {
|
||||
$postalCode = $data[53]; // address 1 postal code (zip) 53
|
||||
}
|
||||
|
||||
if (! empty($data[66])) {
|
||||
$contact->job = $data[66]; // organization 1 name 66
|
||||
}
|
||||
|
||||
$contact->setAvatarColor();
|
||||
$contact->save();
|
||||
|
||||
if (! empty($data[28])) {
|
||||
// Email 1 Value
|
||||
ContactField::firstOrCreate([
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'data' => $data[28],
|
||||
'contact_field_type_id' => $this->contactFieldEmailId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($postalCode || $province || $street || $city) {
|
||||
$request = [
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'street' => $street,
|
||||
'city' => $city,
|
||||
'province' => $province,
|
||||
'postal_code' => $postalCode,
|
||||
];
|
||||
|
||||
app(CreateAddress::class)->execute($request);
|
||||
}
|
||||
|
||||
if (! empty($data[42])) {
|
||||
// Phone 1 Value
|
||||
ContactField::firstOrCreate([
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'data' => $data[42],
|
||||
'contact_field_type_id' => $this->contactFieldPhoneId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! empty($data[14])) {
|
||||
$birthdate = DateHelper::parseDate($data[14]);
|
||||
|
||||
$specialDate = $contact->setSpecialDate('birthdate', $birthdate->year, $birthdate->month, $birthdate->day);
|
||||
|
||||
app(CreateReminder::class)->execute([
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'initial_date' => DateHelper::getDate($specialDate),
|
||||
'frequency_type' => 'year',
|
||||
'frequency_number' => 1,
|
||||
'title' => trans(
|
||||
'people.people_add_birthday_reminder',
|
||||
['name' => $contact->first_name]
|
||||
),
|
||||
'delible' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
GetAvatarsFromInternet::dispatch($contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default contact field email id for the account.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function contactFieldEmailId()
|
||||
{
|
||||
if (! $this->contactFieldEmailId) {
|
||||
$contactFieldType = ContactFieldType::where('type', 'email')->first();
|
||||
$this->contactFieldEmailId = $contactFieldType->id;
|
||||
}
|
||||
|
||||
return $this->contactFieldEmailId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default contact field phone id for the account.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function contactFieldPhoneId()
|
||||
{
|
||||
if (! $this->contactFieldPhoneId) {
|
||||
$contactFieldType = ContactFieldType::where('type', 'phone')->first();
|
||||
$this->contactFieldPhoneId = $contactFieldType->id;
|
||||
}
|
||||
|
||||
return $this->contactFieldPhoneId;
|
||||
}
|
||||
}
|
||||
116
app/Console/Commands/ImportVCards.php
Normal file
116
app/Console/Commands/ImportVCards.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Http\File;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\AddContactFromVCard;
|
||||
use App\Models\Account\ImportJob;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ImportVCards extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'import:vcard
|
||||
{--user= : user to import the contacts}
|
||||
{--path= : path of the file to import}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Imports contacts from vCard files for a specific user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param Filesystem $filesystem
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Filesystem $filesystem)
|
||||
{
|
||||
$email = $this->option('user');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = $this->ask('what is the user\'s email?');
|
||||
}
|
||||
|
||||
// retrieve the user with the specified email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (! $user) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('No user with that email.');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$path = $this->option('path');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $path) {
|
||||
$path = $this->ask('what file you want to import?');
|
||||
}
|
||||
|
||||
if (! $filesystem->exists($path) || ! $this->acceptedExtensions($filesystem, $path)) {
|
||||
$this->error('The provided vcard file was not found or is not valid!');
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
$importJob = $this->import($path, $user);
|
||||
|
||||
return $this->report($importJob) ? 0 : 1;
|
||||
}
|
||||
|
||||
private function acceptedExtensions(Filesystem $filesystem, string $path): bool
|
||||
{
|
||||
switch ($filesystem->extension($path)) {
|
||||
case 'vcf':
|
||||
case 'vcard':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function import(string $path, User $user): ImportJob
|
||||
{
|
||||
$pathName = Storage::putFile('public', new File($path));
|
||||
|
||||
$importJob = $user->account->importjobs()->create([
|
||||
'user_id' => $user->id,
|
||||
'type' => 'vcard',
|
||||
'filename' => $pathName,
|
||||
]);
|
||||
|
||||
AddContactFromVCard::dispatchSync($importJob);
|
||||
|
||||
return $importJob;
|
||||
}
|
||||
|
||||
private function report(ImportJob $importJob)
|
||||
{
|
||||
$importJob->refresh();
|
||||
|
||||
if ($importJob->failed) {
|
||||
$this->warn('Error: '.$importJob->failed_reason);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->info('Contacts found: '.$importJob->contacts_found);
|
||||
$this->info('Contacts skipped: '.$importJob->contacts_skipped);
|
||||
$this->info('Contacts imported: '.$importJob->contacts_imported);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/Inspire.php
Normal file
33
app/Console/Commands/Inspire.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
|
||||
class Inspire extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'inspire';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display an inspiring quote';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->comment(PHP_EOL.Inspiring::quote().PHP_EOL);
|
||||
}
|
||||
}
|
||||
50
app/Console/Commands/LangGenerate.php
Normal file
50
app/Console/Commands/LangGenerate.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class LangGenerate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'lang:generate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate i18n json assets';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$dirs = new DirectoryIterator(resource_path('lang').'/');
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (! $dir->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lang = $dir->getFilename();
|
||||
if ($lang == '.' || $lang == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->call('lang:js', [
|
||||
'--json' => true,
|
||||
'--source' => $dir->getPathname(),
|
||||
'target' => 'public/js/langs/'.$lang.'.json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
app/Console/Commands/MigrateDatabaseCollation.php
Normal file
121
app/Console/Commands/MigrateDatabaseCollation.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\DBHelper;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
class MigrateDatabaseCollation extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'migrate:collation
|
||||
{--force : Force the operation to run when in production.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update database for utf8mb4 collation';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->confirmToProceed()) {
|
||||
try {
|
||||
$connection = DBHelper::connection();
|
||||
|
||||
if ($connection->getDriverName() != 'mysql') {
|
||||
return;
|
||||
}
|
||||
|
||||
$databasename = $connection->getDatabaseName();
|
||||
|
||||
$schemata = $connection->table('information_schema.schemata')
|
||||
->select('DEFAULT_CHARACTER_SET_NAME')
|
||||
->where('schema_name', '=', $databasename)
|
||||
->get();
|
||||
|
||||
$schema = $schemata->first()->DEFAULT_CHARACTER_SET_NAME;
|
||||
|
||||
if (config('database.use_utf8mb4') && $schema == 'utf8') {
|
||||
$this->line('Migrate to utf8mb4 schema collation');
|
||||
$this->toUtf8mb4($connection, $databasename);
|
||||
} elseif (! config('database.use_utf8mb4') && $schema == 'utf8mb4') {
|
||||
$this->line('Migrate to utf8 schema collation');
|
||||
$this->toUtf8($connection, $databasename);
|
||||
} else {
|
||||
$this->info('Nothing to migrate, everything is ok.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ');
|
||||
$this->error(' Check if the DB_USE_UTF8MB4 variable in .env file is correctly set ');
|
||||
$this->error(' ');
|
||||
$this->info('');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to utf8mb4.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string $databasename
|
||||
*/
|
||||
private function toUtf8mb4($connection, $databasename)
|
||||
{
|
||||
// Tables
|
||||
$tables = $connection->table('information_schema.tables')
|
||||
->select('table_name')
|
||||
->where('table_schema', '=', $databasename)
|
||||
->get();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
DB::statement('ALTER TABLE `'.$table->table_name.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
|
||||
}
|
||||
|
||||
// Database
|
||||
$pdo = $connection->getPdo();
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
|
||||
DB::statement('ALTER DATABASE `'.$databasename.'` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;');
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to utf8.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string $databasename
|
||||
*/
|
||||
private function toUtf8($connection, $databasename)
|
||||
{
|
||||
// Tables
|
||||
$tables = $connection->table('information_schema.tables')
|
||||
->select('table_name')
|
||||
->where('table_schema', '=', $databasename)
|
||||
->get();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
DB::statement('ALTER TABLE `'.$table->table_name.'` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
|
||||
}
|
||||
|
||||
// Database
|
||||
$pdo = $connection->getPdo();
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
|
||||
DB::statement('ALTER DATABASE `'.$databasename.'` CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;');
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
|
||||
}
|
||||
}
|
||||
62
app/Console/Commands/NewAddressBookSubscription.php
Normal file
62
app/Console/Commands/NewAddressBookSubscription.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\SynchronizeAddressBooks;
|
||||
use App\Services\DavClient\CreateAddressBookSubscription;
|
||||
|
||||
class NewAddressBookSubscription extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:newaddressbooksubscription
|
||||
{--email= : Monica account to add subscription to}
|
||||
{--url= : CardDAV url of the address book}
|
||||
{--login= : Login}
|
||||
{--password= : Password of the account}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Add a new all dav subscriptions';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$user = User::where('email', $this->option('email'))->firstOrFail();
|
||||
|
||||
$url = $this->option('url') ?? $this->ask('url', 'CardDAV url of the address book');
|
||||
$login = $this->option('login') ?? $this->ask('login', 'Login name');
|
||||
$password = $this->option('password') ?? $this->ask('password', 'User password');
|
||||
|
||||
try {
|
||||
$addressBookSubscription = app(CreateAddressBookSubscription::class)->execute([
|
||||
'account_id' => $user->account_id,
|
||||
'user_id' => $user->id,
|
||||
'base_uri' => $url,
|
||||
'username' => $login,
|
||||
'password' => $password,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if (! isset($addressBookSubscription)) {
|
||||
$this->error('Could not add subscription');
|
||||
} else {
|
||||
$this->info('Subscription added');
|
||||
SynchronizeAddressBooks::dispatch($addressBookSubscription, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
137
app/Console/Commands/OneTime/MoveAvatars.php
Normal file
137
app/Console/Commands/OneTime/MoveAvatars.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\OneTime;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
|
||||
class MoveAvatars extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:moveavatars
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--dryrun : Simulate the execution but not write anything.}
|
||||
{--storage= : new storage to move avatars to}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Move avatars from their current storage to current default storage';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Contact::where('has_avatar', true)
|
||||
->chunk(200, function ($contacts) {
|
||||
$this->handleContacts($contacts);
|
||||
});
|
||||
}
|
||||
|
||||
private function handleContacts($contacts)
|
||||
{
|
||||
foreach ($contacts as $contact) {
|
||||
if ($contact->avatar_location == $this->newStorage()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->handleOneContact($contact);
|
||||
} catch (FileNotFoundException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleOneContact($contact)
|
||||
{
|
||||
// move avatars to new location
|
||||
$this->moveContactAvatars($contact);
|
||||
|
||||
if (! $this->option('dryrun')) {
|
||||
$contact->deleteAvatars();
|
||||
$this->line(' Files deleted from old location.', null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
// Update location. The filename has not changed.
|
||||
$contact->avatar_location = $this->newStorage();
|
||||
$contact->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function moveContactAvatars($contact)
|
||||
{
|
||||
$this->line('Contact id:'.$contact->id.' | Avatar location:'.$contact->avatar_location.' | File name:'.$contact->avatar_file_name);
|
||||
|
||||
$avatarFileNames = [];
|
||||
array_push($avatarFileNames, $this->getFileName($contact));
|
||||
array_push($avatarFileNames, $this->getFileName($contact, 110));
|
||||
array_push($avatarFileNames, $this->getFileName($contact, 174));
|
||||
|
||||
$storage = Storage::disk($contact->avatar_location);
|
||||
$newStorage = Storage::disk($this->newStorage());
|
||||
|
||||
foreach ($avatarFileNames as $avatarFileName) {
|
||||
if ($newStorage->exists($avatarFileName)) {
|
||||
$this->line(' File already pushed: '.$avatarFileName, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
continue;
|
||||
}
|
||||
if (! $this->option('dryrun')) {
|
||||
$avatarFile = $storage->get($avatarFileName);
|
||||
$newStorage->put($avatarFileName, $avatarFile, config('filesystems.default_visibility'));
|
||||
}
|
||||
|
||||
$this->line(' File pushed: '.$avatarFileName, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFileName($contact, $size = null)
|
||||
{
|
||||
$filename = pathinfo($contact->avatar_file_name, PATHINFO_FILENAME);
|
||||
$extension = pathinfo($contact->avatar_file_name, PATHINFO_EXTENSION);
|
||||
|
||||
$avatarFileName = 'avatars/'.$filename.'.'.$extension;
|
||||
if (! is_null($size)) {
|
||||
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;
|
||||
}
|
||||
|
||||
if ($this->fileExists($contact->avatar_location, $avatarFileName)) {
|
||||
return $avatarFileName;
|
||||
}
|
||||
}
|
||||
|
||||
private function fileExists($storage, $avatarFileName): bool
|
||||
{
|
||||
$storage = Storage::disk($storage);
|
||||
|
||||
if (! $storage->exists($avatarFileName)) {
|
||||
$this->line(' ! File not found: '.$avatarFileName, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function newStorage()
|
||||
{
|
||||
return $this->option('storage') ?? config('filesystems.default');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\OneTime;
|
||||
|
||||
use App\Events\MoveAvatarEvent;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use App\Exceptions\FileNotFoundException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use App\Jobs\Avatars\MoveContactAvatarToPhotosDirectory;
|
||||
|
||||
/**
|
||||
* This command moves current avatars to the new Photos directory and converts
|
||||
* each avatar to a Photo object.
|
||||
*/
|
||||
class MoveAvatarsToPhotosDirectory extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:moveavatarstophotosdirectory
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--dryrun : Simulate the execution but not write anything.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Move avatars to the Photos directory, and create a photo object for each one of them';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Event::listen(MoveAvatarEvent::class, function ($event) {
|
||||
$this->handleEvent($event->contact);
|
||||
});
|
||||
|
||||
$delay = now();
|
||||
|
||||
Contact::where('has_avatar', true)
|
||||
->chunk(100, function ($contacts) use ($delay) {
|
||||
foreach ($contacts as $contact) {
|
||||
if ($contact->avatar_source === 'default') {
|
||||
$this->handleContact($contact, $delay);
|
||||
}
|
||||
}
|
||||
// add some delay, so we treat 100 contacts each minutes
|
||||
$delay = $delay->addMinutes(1);
|
||||
});
|
||||
}
|
||||
|
||||
private function handleContact($contact, $delay)
|
||||
{
|
||||
try {
|
||||
if ($this->option('dryrun')) {
|
||||
MoveContactAvatarToPhotosDirectory::dispatchNow($contact, true);
|
||||
} else {
|
||||
MoveContactAvatarToPhotosDirectory::dispatch($contact, false)
|
||||
->delay($delay);
|
||||
}
|
||||
} catch (FileNotFoundException $e) {
|
||||
$this->warn(' ! File not found: '.$e->fileName, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleEvent($contact)
|
||||
{
|
||||
$this->info('Contact id:'.$contact->id.' | Avatar location:'.$contact->avatar_location.' | File name:'.$contact->avatar_file_name);
|
||||
}
|
||||
}
|
||||
73
app/Console/Commands/Passport.php
Normal file
73
app/Console/Commands/Passport.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Laravel\Passport\PersonalAccessClient;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Passport extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:passport {--force : Force the operation to run when in production.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if encryption keys and Personal Access Client are present, and create them if not.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->confirmToProceed()) {
|
||||
$this->checkEncryptionKeys();
|
||||
$this->checkPersonalAccessClient();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkEncryptionKeys()
|
||||
{
|
||||
$this->info('Checking encryption keys...', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
if (! empty(config('passport.private_key')) && ! empty(config('passport.public_key'))) {
|
||||
$this->info('✓ PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY detected.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists(base_path('storage/oauth-private.key')) && file_exists(base_path('storage/oauth-public.key'))) {
|
||||
$this->info('✓ Files storage/oauth-private.key and storage/oauth-public.key detected.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->artisan('✓ Creating encryption keys', 'passport:keys', ['--no-interaction']);
|
||||
$this->warn('! Please be careful to backup '.base_path('storage/oauth-public.key').' and '.base_path('storage/oauth-private.key').' files !', OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
private function checkPersonalAccessClient()
|
||||
{
|
||||
$this->info('Checking Personal Access Client...', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
if (PersonalAccessClient::count() > 0) {
|
||||
$this->info('✓ Personal Access Client already created.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->artisan('✓ Creating personal access client', 'passport:client', ['--personal', '--no-interaction']);
|
||||
}
|
||||
}
|
||||
113
app/Console/Commands/PingVersionServer.php
Normal file
113
app/Console/Commands/PingVersionServer.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use PharIo\Version\Version;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Instance\Instance;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
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
|
||||
{--force : Force the operation to run when in production.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Ping version.monicahq.com to know if a new version is available';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! config('monica.check_version')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->confirmToProceed('Checking version deactivated', function () {
|
||||
return $this->getLaravel()->environment() === 'production';
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$instance = Instance::first();
|
||||
$instance->current_version = config('monica.app_version');
|
||||
|
||||
if ($instance->current_version == '') {
|
||||
Log::warning('Current instance version is not set, skipping version check.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Query version.monicahq.com
|
||||
try {
|
||||
$this->log('Call url: '.config('monica.weekly_ping_server_url'));
|
||||
$response = Http::acceptJson()
|
||||
->post(config('monica.weekly_ping_server_url'), [
|
||||
'uuid' => $instance->uuid,
|
||||
'version' => $instance->current_version,
|
||||
'contacts' => Contact::count(),
|
||||
])
|
||||
->throw();
|
||||
} catch (RequestException $e) {
|
||||
$this->error('Error calling "'.config('monica.weekly_ping_server_url').'": '.$e->getMessage());
|
||||
Log::error(__CLASS__.' Error calling "'.config('monica.weekly_ping_server_url').'": '.$e->getMessage(), [$e]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Receive the JSON
|
||||
$json = $response->json();
|
||||
|
||||
$this->log('instance version: '.$instance->current_version);
|
||||
$currentVersion = $this->getVersion($instance->current_version);
|
||||
|
||||
$this->log('current version: '.$json['latest_version']);
|
||||
$latestVersion = $this->getVersion($json['latest_version']);
|
||||
|
||||
if ($latestVersion > $currentVersion) {
|
||||
$instance->latest_version = $json['latest_version'];
|
||||
$instance->latest_release_notes = $json['notes'];
|
||||
$instance->number_of_versions_since_current_version = $json['number_of_versions_since_user_version'];
|
||||
} else {
|
||||
$instance->latest_release_notes = null;
|
||||
$instance->number_of_versions_since_current_version = null;
|
||||
}
|
||||
|
||||
$instance->save();
|
||||
}
|
||||
|
||||
public function log($string)
|
||||
{
|
||||
$this->info($string, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
private function getVersion(string $version): ?Version
|
||||
{
|
||||
try {
|
||||
return new Version($version);
|
||||
} catch (\Exception $e) {
|
||||
$this->error("Error parsing version '$version': ".$e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
56
app/Console/Commands/SendReminders.php
Normal file
56
app/Console/Commands/SendReminders.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Contact\ReminderOutbox;
|
||||
use App\Jobs\Reminder\NotifyUserAboutReminder;
|
||||
|
||||
class SendReminders extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'send:reminders';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send reminders that are scheduled for the contacts';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Grab all the reminders that are supposed to be sent in the next two days
|
||||
// Why 2? because in terms of timezone, we can have up to more than 24 hours
|
||||
// between two timezones and we need to take into accounts reminders
|
||||
// that are not in the same timezone.
|
||||
ReminderOutbox::where('planned_date', '<', now()->addDays(2))
|
||||
->orderBy('planned_date', 'asc')
|
||||
->chunk(500, function ($reminderOutboxes) {
|
||||
$this->send($reminderOutboxes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the reminder to the user and schedule the future.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function send($reminderOutboxes)
|
||||
{
|
||||
foreach ($reminderOutboxes as $reminderOutbox) {
|
||||
if ($reminderOutbox->user->isTheRightTimeToBeReminded($reminderOutbox->planned_date)) {
|
||||
NotifyUserAboutReminder::dispatch($reminderOutbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
app/Console/Commands/SendStayInTouch.php
Normal file
47
app/Console/Commands/SendStayInTouch.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\StayInTouch\ScheduleStayInTouch;
|
||||
|
||||
class SendStayInTouch extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'send:stay_in_touch';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send notifications about staying in touch with contacts';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// we add two days to make sure we cover all timezones
|
||||
Contact::where('stay_in_touch_trigger_date', '<', now()->addDays(2))
|
||||
->whereNotNull('stay_in_touch_frequency')
|
||||
->orderBy('stay_in_touch_trigger_date', 'asc')
|
||||
->chunk(500, function ($contacts) {
|
||||
$this->schedule($contacts);
|
||||
});
|
||||
}
|
||||
|
||||
private function schedule($contacts)
|
||||
{
|
||||
foreach ($contacts as $contact) {
|
||||
ScheduleStayInTouch::dispatch($contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
app/Console/Commands/SendTestEmail.php
Normal file
64
app/Console/Commands/SendTestEmail.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendTestEmail extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:test-email
|
||||
{--email= : The email address to send to}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send a test email to check that delivery is working';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** retrieve the email from the option.
|
||||
* @var string $email
|
||||
*/
|
||||
$email = $this->option('email');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = (string) $this->ask('What email address should I send the test email to?');
|
||||
}
|
||||
|
||||
// Validate user provided email address
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
$this->error("Invalid email address: \"$email\".");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$this->info("Preparing and sending email to \"$email\"");
|
||||
|
||||
// immediately deliver the test email (bypassing the queue)
|
||||
Mail::raw(
|
||||
"Hi $email, you requested a test email from Monica.",
|
||||
function ($message) use ($email) {
|
||||
$message->to($email)
|
||||
->subject('Monica email delivery test');
|
||||
}
|
||||
);
|
||||
|
||||
$this->info('Email sent!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
135
app/Console/Commands/SentryRelease.php
Normal file
135
app/Console/Commands/SentryRelease.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\exec;
|
||||
use function Safe\mkdir;
|
||||
use Illuminate\Console\Command;
|
||||
use function Safe\file_put_contents;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SentryRelease extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sentry:release
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--release= : release version for sentry.}
|
||||
{--store-release : store release version in config/.release file.}
|
||||
{--commit= : commit associated with this release.}
|
||||
{--environment= : sentry environment.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a release for sentry';
|
||||
|
||||
/**
|
||||
* Installation path of sentry cli.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $install_dir;
|
||||
|
||||
/**
|
||||
* sentry cli name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const SENTRY_CLI = 'sentry-cli';
|
||||
|
||||
/**
|
||||
* Sentry cli download url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const SENTRY_URL = 'https://sentry.io/get-cli/';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! config('monica.sentry_support') || ! $this->check()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->confirmToProceed()) {
|
||||
$this->install_dir = env('SENTRY_ROOT', getenv('HOME').'/.local/bin');
|
||||
|
||||
$release = $this->option('release') ?? config('sentry.release');
|
||||
$commit = $this->option('commit') ??
|
||||
(is_dir(__DIR__.'/../../../.git') ? trim(exec('git log --pretty="%H" -n1 HEAD')) : $release);
|
||||
|
||||
// Sentry update
|
||||
$this->exec('Update sentry', $this->getSentryCli().' update');
|
||||
|
||||
// Create a release
|
||||
$this->execSentryCli('Create a release', 'releases new '.$release.' --finalize --project '.config('sentry-release.project'));
|
||||
|
||||
// Associate commits with the release
|
||||
$this->execSentryCli('Associate commits with the release', 'releases set-commits '.$release.' --commit "'.config('sentry-release.repo').'@'.$commit.'"');
|
||||
|
||||
// Create a deploy
|
||||
$this->execSentryCli('Create a deploy', 'releases deploys '.$release.' new --env '.$this->option('environment').' --name '.config('monica.app_version'));
|
||||
|
||||
if ($this->option('store-release')) {
|
||||
// Set sentry release
|
||||
$this->line('Store release in config/.release file', null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
file_put_contents(__DIR__.'/../../../config/.release', $release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function check(): bool
|
||||
{
|
||||
$check = true;
|
||||
if (empty(config('sentry-release.auth_token'))) {
|
||||
$this->error('You must provide an auth_token (SENTRY_AUTH_TOKEN)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty(config('sentry-release.organisation'))) {
|
||||
$this->error('You must provide an organisation slug (SENTRY_ORG)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty(config('sentry-release.project'))) {
|
||||
$this->error('You must set the project (SENTRY_PROJECT)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty(config('sentry-release.repo'))) {
|
||||
$this->error('You must set the repository (SENTRY_REPO)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty($this->option('environment'))) {
|
||||
$this->error('No environment given');
|
||||
$check = false;
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
|
||||
private function getSentryCli()
|
||||
{
|
||||
if (! file_exists($this->install_dir.'/'.self::SENTRY_CLI)) {
|
||||
mkdir($this->install_dir, 0777, true);
|
||||
$this->exec('Downloading sentry-cli', 'curl -sL '.self::SENTRY_URL.' | INSTALL_DIR='.$this->install_dir.' bash');
|
||||
}
|
||||
|
||||
return $this->install_dir.'/'.self::SENTRY_CLI;
|
||||
}
|
||||
|
||||
private function execSentryCli($message, $command)
|
||||
{
|
||||
$this->exec($message, $this->getSentryCli().' '.$command);
|
||||
}
|
||||
}
|
||||
36
app/Console/Commands/SetPremiumAccount.php
Normal file
36
app/Console/Commands/SetPremiumAccount.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetPremiumAccount extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'account:setpremium {accountId}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Give a premium access to an account.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$account = Account::findOrFail($this->argument('accountId'));
|
||||
$account->update([
|
||||
'has_access_to_paid_version_for_free' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
74
app/Console/Commands/SetUserAdmin.php
Normal file
74
app/Console/Commands/SetUserAdmin.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetUserAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:admin
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--email= : The email of the user whose admin status you want to change}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Toggle administrator privileges for a user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// retrieve the email from the option
|
||||
$email = $this->option('email');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = $this->ask('What is the user’s email?');
|
||||
}
|
||||
|
||||
// retrieve the user with the specified email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (! $user) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('No user with that email.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Print a warning
|
||||
if ($user->admin) {
|
||||
$this->warn($user->email.' will be removed from the administrators of this instance');
|
||||
} else {
|
||||
$this->warn($user->email.' will be added to the administrators of this instance');
|
||||
}
|
||||
|
||||
// ask for confirmation if not forced
|
||||
if (! $this->option('force') && ! $this->confirm('Do you wish to continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// toglle admin status
|
||||
$user->admin = ! $user->admin;
|
||||
$user->save();
|
||||
|
||||
// Show new status
|
||||
if ($user->admin) {
|
||||
$this->info($user->email.' has been added to the administrators of this instance');
|
||||
} else {
|
||||
$this->info($user->email.' has been removed from the administrators of this instance');
|
||||
}
|
||||
}
|
||||
}
|
||||
82
app/Console/Commands/SetupProduction.php
Normal file
82
app/Console/Commands/SetupProduction.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\touch;
|
||||
use App\Helpers\InstanceHelper;
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetupProduction extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:production
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--email= : Login email for the first account.}
|
||||
{--password= : Password to set for the first account.}
|
||||
{--skipSeed : Skip the populate database process.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Perform setup of Monica.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ((! $this->option('force')) && (! $this->confirm('You are about to setup and configure Monica. Do you wish to continue?'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(__DIR__.'/../../../.env')) {
|
||||
touch(__DIR__.'/../../../.env');
|
||||
}
|
||||
|
||||
$this->call('monica:update', ['--force' => true]);
|
||||
|
||||
if (! $this->option('skipSeed')) {
|
||||
$this->line('✓ Filling database');
|
||||
$this->call('db:seed', ['--force' => true]);
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->line('-----------------------------');
|
||||
$this->line('|');
|
||||
$this->line('| Welcome to Monica v'.config('monica.app_version'));
|
||||
$this->line('|');
|
||||
$this->line('-----------------------------');
|
||||
|
||||
$email = $this->option('email');
|
||||
$password = $this->option('password');
|
||||
if (! empty($email) && ! empty($password)) {
|
||||
Account::createDefault('John', 'Doe', $email, $password);
|
||||
|
||||
$this->info('| You can now sign in to your account:');
|
||||
$this->line('| username: '.$email);
|
||||
$this->line('| password: <hidden>');
|
||||
} elseif (InstanceHelper::hasAtLeastOneAccount()) {
|
||||
$this->info('| You can now log in to your account');
|
||||
} else {
|
||||
$this->info('| You can now register to the first account by opening the application:');
|
||||
}
|
||||
|
||||
$this->line('| URL: '.config('app.url'));
|
||||
$this->line('-----------------------------');
|
||||
|
||||
$this->info('Setup is done. Have fun.');
|
||||
}
|
||||
}
|
||||
677
app/Console/Commands/SetupTest.php
Normal file
677
app/Console/Commands/SetupTest.php
Normal file
@@ -0,0 +1,677 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\exec;
|
||||
use App\Models\User\User;
|
||||
use App\Helpers\DateHelper;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Models\Account\Account;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Helpers\CountriesHelper;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Console\Application;
|
||||
use App\Models\Contact\LifeEventType;
|
||||
use App\Models\Contact\ContactFieldType;
|
||||
use App\Services\Contact\Gift\CreateGift;
|
||||
use App\Services\Contact\Tag\AssociateTag;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use App\Services\Contact\Address\CreateAddress;
|
||||
use App\Services\Contact\Contact\CreateContact;
|
||||
use App\Services\Contact\Reminder\CreateReminder;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use App\Services\Contact\LifeEvent\CreateLifeEvent;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use App\Services\Contact\Conversation\CreateConversation;
|
||||
use App\Services\Contact\Relationship\CreateRelationship;
|
||||
use App\Services\Account\Activity\Activity\CreateActivity;
|
||||
use App\Services\Contact\Contact\UpdateBirthdayInformation;
|
||||
use App\Services\Contact\Contact\UpdateDeceasedInformation;
|
||||
use App\Services\Contact\Conversation\AddMessageToConversation;
|
||||
use App\Services\Account\Activity\Activity\AttachContactToActivity;
|
||||
|
||||
class SetupTest extends Command
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:test
|
||||
{--skipSeed : Skip the populate database with fake data.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create the test environment with optional fake data for testing purposes.';
|
||||
|
||||
/**
|
||||
* The number of contacts that need to be generated.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $numberOfContacts;
|
||||
|
||||
/**
|
||||
* The Current contact instance.
|
||||
*
|
||||
* @var Contact
|
||||
*/
|
||||
private $contact;
|
||||
|
||||
/**
|
||||
* The current Contact instance.
|
||||
*
|
||||
* @var Account
|
||||
*/
|
||||
private $account;
|
||||
|
||||
/**
|
||||
* The list of countries.
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
private $countries = null;
|
||||
|
||||
/**
|
||||
* The logged user.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirm('Are you sure you want to proceed? This will delete ALL data in your environment.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->artisan('✓ Performing migrations', 'migrate:fresh');
|
||||
|
||||
$this->artisan('✓ Symlink the storage folder', 'storage:link');
|
||||
|
||||
if (! $this->option('skipSeed')) {
|
||||
$this->numberOfContacts = $this->ask('How many contacts would you like to have in this test account?');
|
||||
$this->info('✓ Filling database with fake data');
|
||||
$this->seed();
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->line('-----------------------------');
|
||||
$this->line('|');
|
||||
$this->line('| Welcome to Monica v'.config('monica.app_version'));
|
||||
$this->line('|');
|
||||
$this->line('-----------------------------');
|
||||
$this->info('| You can now sign in to your account:');
|
||||
$this->line('| username: admin@admin.com');
|
||||
$this->line('| password: admin0');
|
||||
$this->line('| URL: '.config('app.url'));
|
||||
$this->line('-----------------------------');
|
||||
|
||||
$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(Application::formatCommandString($command));
|
||||
$this->callSilent($command, $arguments);
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function seed()
|
||||
{
|
||||
$this->setUpFaker();
|
||||
|
||||
// Get or create the first account
|
||||
if (User::where('email', 'admin@admin.com')->exists()) {
|
||||
$this->user = User::where('email', 'admin@admin.com')->first();
|
||||
$userId = $this->user->value('id');
|
||||
$this->account = Account::where('id', $userId)->first();
|
||||
} else {
|
||||
$this->account = Account::createDefault('John', 'Doe', 'admin@admin.com', 'admin0');
|
||||
|
||||
// set default admin account to confirmed
|
||||
/** @var User */
|
||||
$adminUser = $this->account->users()->first();
|
||||
$this->confirmUser($adminUser);
|
||||
$this->user = $adminUser;
|
||||
}
|
||||
|
||||
// create a random number of contacts
|
||||
//$this->numberOfContacts = rand(60, 100);
|
||||
echo 'Generating '.$this->numberOfContacts.' fake contacts'.PHP_EOL;
|
||||
|
||||
$output = new ConsoleOutput();
|
||||
$progress = new ProgressBar($output, $this->numberOfContacts);
|
||||
$progress->start();
|
||||
|
||||
for ($i = 0; $i < $this->numberOfContacts; $i++) {
|
||||
$gender = (rand(1, 2) == 1) ? 'male' : 'female';
|
||||
|
||||
$this->contact = app(CreateContact::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'author_id' => $this->user->id,
|
||||
'first_name' => $this->faker->firstName($gender),
|
||||
'last_name' => (rand(1, 2) == 1) ? $this->faker->lastName : null,
|
||||
'nickname' => (rand(1, 2) == 1) ? $this->faker->name : null,
|
||||
'gender_id' => $this->getRandomGender()->id,
|
||||
'is_partial' => false,
|
||||
'is_birthdate_known' => false,
|
||||
'is_deceased' => false,
|
||||
'is_deceased_date_known' => false,
|
||||
]);
|
||||
|
||||
$this->populateTags();
|
||||
$this->populateFoodPreferences();
|
||||
$this->populateDeceasedDate();
|
||||
$this->populateBirthday();
|
||||
$this->populateFirstMetInformation();
|
||||
$this->populateRelationships();
|
||||
$this->populateNotes();
|
||||
$this->populateActivities();
|
||||
$this->populateTasks();
|
||||
$this->populateDebts();
|
||||
$this->populateCalls();
|
||||
$this->populateConversations();
|
||||
$this->populateLifeEvents();
|
||||
$this->populateGifts();
|
||||
$this->populateAddresses();
|
||||
$this->populateContactFields();
|
||||
$this->populatePets();
|
||||
$this->changeUpdatedAt();
|
||||
|
||||
$progress->advance();
|
||||
}
|
||||
|
||||
$this->populateDayRatings();
|
||||
$this->populateEntries();
|
||||
|
||||
$progress->finish();
|
||||
|
||||
// create the second test, blank account
|
||||
if (! User::where('email', 'blank@blank.com')->exists()) {
|
||||
$blankAccount = Account::createDefault('Blank', 'State', 'blank@blank.com', 'blank0');
|
||||
$blankUser = $blankAccount->users()->first();
|
||||
$this->confirmUser($blankUser);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateTags()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
$i = 0;
|
||||
do {
|
||||
app(AssociateTag::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'name' => $this->faker->word,
|
||||
]);
|
||||
$i++;
|
||||
} while ($i < 10);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateFoodPreferences()
|
||||
{
|
||||
// add food preferences
|
||||
if (rand(1, 2) == 1) {
|
||||
$this->contact->food_preferences = $this->faker->realText();
|
||||
$this->contact->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function populateDeceasedDate()
|
||||
{
|
||||
// deceased?
|
||||
if (rand(1, 7) == 1) {
|
||||
$birthdate = $this->faker->dateTimeThisCentury();
|
||||
|
||||
app(UpdateDeceasedInformation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'is_deceased' => rand(1, 2) == 1,
|
||||
'is_date_known' => rand(1, 2) == 1,
|
||||
'day' => (int) $birthdate->format('d'),
|
||||
'month' => (int) $birthdate->format('m'),
|
||||
'year' => (int) $birthdate->format('Y'),
|
||||
'add_reminder' => rand(1, 2) == 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateBirthday()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
$birthdate = $this->faker->dateTimeThisCentury();
|
||||
|
||||
app(UpdateBirthdayInformation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'is_date_known' => rand(1, 2) == 1,
|
||||
'day' => (int) $birthdate->format('d'),
|
||||
'month' => (int) $birthdate->format('m'),
|
||||
'year' => (int) $birthdate->format('Y'),
|
||||
'is_age_based' => rand(1, 2) == 1,
|
||||
'age' => rand(1, 99),
|
||||
'add_reminder' => rand(1, 2) == 1,
|
||||
'is_deceased' => $this->contact->is_dead,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateFirstMetInformation()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
$this->contact->first_met_where = $this->faker->realText(20);
|
||||
}
|
||||
|
||||
if (rand(1, 2) == 1) {
|
||||
$this->contact->first_met_additional_info = $this->faker->realText(20);
|
||||
$firstMetDate = $this->faker->dateTimeThisCentury();
|
||||
|
||||
if (rand(1, 2) == 1) {
|
||||
// add a date where we don't know the year
|
||||
$specialDate = $this->contact->setSpecialDate('first_met', 0, intval($firstMetDate->format('m')), intval($firstMetDate->format('d')));
|
||||
} else {
|
||||
// add a date where we know the year
|
||||
$specialDate = $this->contact->setSpecialDate('first_met', intval($firstMetDate->format('Y')), intval($firstMetDate->format('m')), intval($firstMetDate->format('d')));
|
||||
}
|
||||
app(CreateReminder::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'initial_date' => $specialDate->date->toDateString(),
|
||||
'frequency_type' => 'year',
|
||||
'frequency_number' => 1,
|
||||
'title' => trans(
|
||||
'people.introductions_reminder_title',
|
||||
['name' => $this->contact->first_name]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (rand(1, 2) == 1) {
|
||||
do {
|
||||
$rand = rand(1, $this->numberOfContacts);
|
||||
} while (in_array($rand, [$this->contact->id]));
|
||||
|
||||
$this->contact->first_met_through_contact_id = $rand;
|
||||
}
|
||||
|
||||
$this->contact->save();
|
||||
}
|
||||
|
||||
public function populateRelationships()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
foreach (range(1, rand(2, 6)) as $index) {
|
||||
$gender = (rand(1, 2) == 1) ? 'male' : 'female';
|
||||
|
||||
$relatedContact = app(CreateContact::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'author_id' => $this->user->id,
|
||||
'first_name' => $this->faker->firstName($gender),
|
||||
'last_name' => (rand(1, 2) == 1) ? $this->faker->lastName : null,
|
||||
'nickname' => (rand(1, 2) == 1) ? $this->faker->name : null,
|
||||
'gender_id' => $this->getRandomGender()->id,
|
||||
'is_partial' => rand(1, 2) == 1,
|
||||
'is_birthdate_known' => false,
|
||||
'is_deceased' => false,
|
||||
'is_deceased_date_known' => false,
|
||||
]);
|
||||
|
||||
$relatedContactBirthDate = $this->faker->dateTimeThisCentury();
|
||||
app(UpdateBirthdayInformation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $relatedContact->id,
|
||||
'is_date_known' => rand(1, 2) == 1,
|
||||
'day' => (int) $relatedContactBirthDate->format('d'),
|
||||
'month' => (int) $relatedContactBirthDate->format('m'),
|
||||
'year' => (int) $relatedContactBirthDate->format('Y'),
|
||||
'is_age_based' => rand(1, 2) == 1,
|
||||
'age' => rand(1, 99),
|
||||
'add_reminder' => rand(1, 2) == 1,
|
||||
'is_deceased' => $relatedContact->is_dead,
|
||||
]);
|
||||
|
||||
// set relationship
|
||||
$relationshipId = $this->contact->account->relationshipTypes->random()->id;
|
||||
$relationship = app(CreateRelationship::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_is' => $this->contact->id,
|
||||
'of_contact' => $relatedContact->id,
|
||||
'relationship_type_id' => $relationshipId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateNotes()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 13); $j++) {
|
||||
$note = $this->contact->notes()->create([
|
||||
'body' => $this->faker->realText(rand(40, 500)),
|
||||
'account_id' => $this->account->id,
|
||||
'is_favorited' => rand(1, 3) == 1,
|
||||
'favorited_at' => $this->faker->dateTimeThisCentury(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateActivities()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 13); $j++) {
|
||||
$date = DateHelper::getDate(Carbon::instance($this->faker->dateTimeThisYear($max = 'now')));
|
||||
|
||||
$request = [
|
||||
'account_id' => $this->account->id,
|
||||
'activity_type_id' => rand(1, 13),
|
||||
'summary' => $this->faker->realText(rand(40, 100)),
|
||||
'description' => (rand(1, 2) == 1 ? $this->faker->realText(rand(100, 1000)) : null),
|
||||
'happened_at' => $date,
|
||||
'contacts' => [$this->contact->id],
|
||||
];
|
||||
|
||||
$activity = app(CreateActivity::class)->execute($request);
|
||||
|
||||
$request = [
|
||||
'account_id' => $this->account->id,
|
||||
'activity_id' => $activity->id,
|
||||
'contacts' => [$this->contact->id],
|
||||
];
|
||||
|
||||
app(AttachContactToActivity::class)->execute($request);
|
||||
|
||||
DB::table('journal_entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'date' => $date,
|
||||
'journalable_id' => $activity->id,
|
||||
'journalable_type' => 'App\Models\Account\Activity',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateTasks()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 10); $j++) {
|
||||
$task = $this->contact->tasks()->create([
|
||||
'title' => $this->faker->realText(rand(40, 100)),
|
||||
'description' => $this->faker->realText(rand(100, 1000)),
|
||||
'completed' => (rand(1, 2) == 1 ? 0 : 1),
|
||||
'completed_at' => (rand(1, 2) == 1 ? $this->faker->dateTimeThisCentury() : null),
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateDebts()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 6); $j++) {
|
||||
$this->contact->debts()->create([
|
||||
'in_debt' => (rand(1, 2) == 1 ? 'yes' : 'no'),
|
||||
'amount' => rand(321, 39391),
|
||||
'reason' => $this->faker->realText(rand(100, 1000)),
|
||||
'status' => 'inprogress',
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateGifts()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 31); $j++) {
|
||||
app(CreateGift::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'status' => (rand(1, 3) == 1 ? 'offered' : 'idea'),
|
||||
'name' => $this->faker->realText(rand(10, 100)),
|
||||
'comment' => $this->faker->realText(rand(1000, 5000)),
|
||||
'url' => $this->faker->url,
|
||||
'amount' => rand(12, 120),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateAddresses()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
$request = [
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'country' => $this->getRandomCountry(),
|
||||
'name' => $this->faker->word,
|
||||
'street' => (rand(1, 3) == 1) ? $this->faker->streetAddress : null,
|
||||
'city' => (rand(1, 3) == 1) ? $this->faker->city : null,
|
||||
'province' => (rand(1, 3) == 1) ? $this->faker->state : null,
|
||||
'postal_code' => (rand(1, 3) == 1) ? $this->faker->postcode : null,
|
||||
];
|
||||
|
||||
app(CreateAddress::class)->execute($request);
|
||||
}
|
||||
}
|
||||
|
||||
private function getRandomCountry()
|
||||
{
|
||||
if ($this->countries == null) {
|
||||
$this->countries = CountriesHelper::getAll();
|
||||
}
|
||||
|
||||
return $this->countries->random()['id'];
|
||||
}
|
||||
|
||||
public function populateContactFields()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
|
||||
// Fetch number of types
|
||||
$numberOfTypes = ContactFieldType::where('account_id', $this->account->id)->count();
|
||||
|
||||
for ($j = 0; $j < rand(1, $numberOfTypes); $j++) {
|
||||
// Retrieve random ContactFieldType
|
||||
$contactFieldType = ContactFieldType::where('account_id', $this->account->id)->orderBy(DB::raw('RAND()'))->firstOrFail();
|
||||
|
||||
// Fake data according to type
|
||||
$data = null;
|
||||
switch ($contactFieldType->name) {
|
||||
case 'Email':
|
||||
$data = $this->faker->email;
|
||||
break;
|
||||
case 'Phone':
|
||||
$data = $this->faker->phoneNumber;
|
||||
break;
|
||||
case 'Facebook':
|
||||
$data = 'https://facebook.com/'.$this->faker->userName;
|
||||
break;
|
||||
case 'Twitter':
|
||||
$data = 'https://twitter.com/'.$this->faker->userName;
|
||||
break;
|
||||
case 'Whatsapp':
|
||||
$data = $this->faker->phoneNumber;
|
||||
break;
|
||||
case 'Telegram':
|
||||
$data = $this->faker->phoneNumber;
|
||||
break;
|
||||
default:
|
||||
$data = $this->faker->url;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->contact->contactFields()->create([
|
||||
'contact_field_type_id' => $contactFieldType->id,
|
||||
'data' => $data,
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateEntries()
|
||||
{
|
||||
for ($j = 0; $j < rand(10, 100); $j++) {
|
||||
$date = $this->faker->dateTimeThisYear();
|
||||
|
||||
$entryId = DB::table('entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'title' => $this->faker->realText(rand(12, 20)),
|
||||
'post' => $this->faker->realText(rand(400, 500)),
|
||||
'created_at' => $date,
|
||||
]);
|
||||
|
||||
DB::table('journal_entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'date' => $date,
|
||||
'journalable_id' => $entryId,
|
||||
'journalable_type' => 'App\Models\Journal\Entry',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populatePets()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
for ($j = 0; $j < rand(1, 3); $j++) {
|
||||
$date = $this->faker->dateTimeThisYear();
|
||||
|
||||
DB::table('pets')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'pet_category_id' => rand(1, 11),
|
||||
'name' => (rand(1, 3) == 1) ? $this->faker->firstName : null,
|
||||
'created_at' => $date,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateDayRatings()
|
||||
{
|
||||
for ($j = 0; $j < rand(10, 100); $j++) {
|
||||
$date = $this->faker->dateTimeThisYear();
|
||||
|
||||
$dayId = DB::table('days')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'rate' => rand(1, 3),
|
||||
'date' => $date,
|
||||
'created_at' => $date,
|
||||
]);
|
||||
|
||||
DB::table('journal_entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'date' => $date,
|
||||
'journalable_id' => $dayId,
|
||||
'journalable_type' => 'App\Models\Journal\Day',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function changeUpdatedAt()
|
||||
{
|
||||
$this->contact->last_consulted_at = Carbon::instance($this->faker->dateTimeThisYear());
|
||||
$this->contact->save();
|
||||
}
|
||||
|
||||
public function populateCalls()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
$this->contact->calls()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'called_at' => $this->faker->dateTimeThisYear(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateConversations()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
for ($j = 0; $j < rand(1, 20); $j++) {
|
||||
$contactFieldType = ContactFieldType::where('account_id', $this->account->id)->orderBy(DB::raw('RAND()'))->firstOrFail();
|
||||
|
||||
$conversation = app(CreateConversation::class)->execute([
|
||||
'happened_at' => $this->faker->dateTimeThisCentury(),
|
||||
'contact_id' => $this->contact->id,
|
||||
'contact_field_type_id' => $contactFieldType->id,
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
|
||||
for ($k = 0; $k < rand(1, 20); $k++) {
|
||||
app(AddMessageToConversation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'conversation_id' => $conversation->id,
|
||||
'written_at' => $this->faker->dateTimeThisCentury(),
|
||||
'written_by_me' => (rand(1, 2) == 1),
|
||||
'content' => $this->faker->realText(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateLifeEvents()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
for ($j = 0; $j < rand(1, 20); $j++) {
|
||||
$lifeEventType = LifeEventType::where('account_id', $this->account->id)->orderBy(DB::raw('RAND()'))->firstOrFail();
|
||||
|
||||
app(CreateLifeEvent::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'life_event_type_id' => $lifeEventType->id,
|
||||
'happened_at' => $this->faker->dateTimeThisCentury(),
|
||||
'name' => $this->faker->realText(),
|
||||
'note' => $this->faker->realText(),
|
||||
'has_reminder' => false,
|
||||
'happened_at_month_unknown' => false,
|
||||
'happened_at_day_unknown' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getRandomGender()
|
||||
{
|
||||
return $this->account->genders->random();
|
||||
}
|
||||
|
||||
public function confirmUser($user)
|
||||
{
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
}
|
||||
45
app/Console/Commands/Tests/SetupFrontEndTestUser.php
Normal file
45
app/Console/Commands/Tests/SetupFrontEndTestUser.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Tests;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\User\AcceptPolicy;
|
||||
|
||||
class SetupFrontEndTestUser extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:frontendtestuser {--database= : The database connection to use}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a user exclusively for front-end testing with Cypress.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
app('migrator')->setConnection($this->option('database'));
|
||||
|
||||
$user = factory(User::class)->create();
|
||||
$user->account->populateDefaultFields();
|
||||
|
||||
app(AcceptPolicy::class)->execute([
|
||||
'account_id' => $user->account->id,
|
||||
'user_id' => $user->id,
|
||||
'ip_address' => null,
|
||||
]);
|
||||
|
||||
$this->info($user->getKey());
|
||||
}
|
||||
}
|
||||
64
app/Console/Commands/Tests/UpdateLangs.php
Normal file
64
app/Console/Commands/Tests/UpdateLangs.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Tests;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\json_encode;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class UpdateLangs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:updatelang';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update langage files.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (! File::exists(base_path('vendor/laravel-lang/lang'))) {
|
||||
throw new \Exception("This command requires laravel-lang/lang package. Run 'composer require --dev laravel-lang/lang' to install it.");
|
||||
}
|
||||
|
||||
$en = json_decode(File::get(lang_path('en.json')), true);
|
||||
|
||||
foreach (File::directories(lang_path()) as $lang) {
|
||||
$lang = basename($lang);
|
||||
$locale = Str::of($lang)->replace('-', '_');
|
||||
switch ($lang) {
|
||||
case 'zh':
|
||||
$locale = 'zh_CN';
|
||||
break;
|
||||
case 'en':
|
||||
continue 2;
|
||||
}
|
||||
if (File::exists($orig_path = lang_path("$lang.json"))
|
||||
&& File::exists($trans_path = base_path("vendor/laravel-lang/lang/locales/$locale/$locale.json"))) {
|
||||
$lang_orig = json_decode(File::get($orig_path), true);
|
||||
$lang_trans = json_decode(File::get($trans_path), true);
|
||||
foreach ($en as $key) {
|
||||
$lang_orig[$key] = $lang_trans[$key];
|
||||
}
|
||||
File::put($orig_path, json_encode($lang_orig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
app/Console/Commands/Update.php
Normal file
116
app/Console/Commands/Update.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\DBHelper;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
class Update extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:update
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--composer-install : Updating composer dependencies.}
|
||||
{--skip-storage-link : Skip storage link create.}
|
||||
{--dev : Install dev dependencies too.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update monica dependencies and migrations after a new release';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->confirmToProceed()) {
|
||||
try {
|
||||
$this->artisan('✓ Maintenance mode: on', 'down', [
|
||||
'--retry' => '10',
|
||||
]);
|
||||
|
||||
// Clear or rebuild all cache
|
||||
if (config('cache.default') != 'database' || Schema::hasTable(config('cache.stores.database.table'))) {
|
||||
$this->artisan('✓ Resetting application cache', 'cache:clear');
|
||||
}
|
||||
|
||||
if ($this->getLaravel()->environment() == 'production') {
|
||||
$this->artisan('✓ Clear config cache', 'config:clear');
|
||||
$this->artisan('✓ Resetting route cache', 'route:cache');
|
||||
if ($this->getLaravel()->version() > '5.6') {
|
||||
$this->artisan('✓ Resetting view cache', 'view:cache');
|
||||
} else {
|
||||
$this->artisan('✓ Resetting view cache', 'view:clear');
|
||||
}
|
||||
} else {
|
||||
$this->artisan('✓ Clear config cache', 'config:clear');
|
||||
$this->artisan('✓ Clear route cache', 'route:clear');
|
||||
$this->artisan('✓ Clear view cache', 'view:clear');
|
||||
}
|
||||
|
||||
if ($this->option('composer-install') === true) {
|
||||
$this->exec('✓ Updating composer dependencies', 'composer install --no-interaction'.($this->option('dev') === false ? ' --no-dev' : ''));
|
||||
}
|
||||
|
||||
if ($this->option('skip-storage-link') !== true && $this->getLaravel()->environment() != 'testing' && ! file_exists(public_path('storage'))) {
|
||||
$this->artisan('✓ Symlink the storage folder', 'storage:link');
|
||||
}
|
||||
|
||||
if ($this->migrateCollationTest()) {
|
||||
$this->artisan('✓ Performing collation migrations', 'migrate:collation', ['--force']);
|
||||
}
|
||||
|
||||
$this->artisan('✓ Performing migrations', 'migrate', ['--force']);
|
||||
|
||||
$this->artisan('✓ Check for encryption keys', 'monica:passport', ['--force']);
|
||||
|
||||
$this->artisan('✓ Ping for new version', 'monica:ping', ['--force']);
|
||||
|
||||
// Cache config
|
||||
if ($this->getLaravel()->environment() == 'production'
|
||||
&& (config('cache.default') != 'database' || Schema::hasTable(config('cache.stores.database.table')))) {
|
||||
$this->artisan('✓ Cache configuraton', 'config:cache');
|
||||
}
|
||||
} finally {
|
||||
$this->artisan('✓ Maintenance mode: off', 'up');
|
||||
}
|
||||
|
||||
$this->line('Monica v'.config('monica.app_version').' is set up, enjoy.');
|
||||
}
|
||||
}
|
||||
|
||||
private function migrateCollationTest()
|
||||
{
|
||||
$connection = DBHelper::connection();
|
||||
|
||||
if ($connection->getDriverName() != 'mysql') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$databasename = $connection->getDatabaseName();
|
||||
|
||||
$schemata = DB::select(
|
||||
'select DEFAULT_CHARACTER_SET_NAME from information_schema.schemata where schema_name = ?',
|
||||
[$databasename]
|
||||
);
|
||||
|
||||
$schema = $schemata[0]->DEFAULT_CHARACTER_SET_NAME;
|
||||
|
||||
return config('database.use_utf8mb4') && $schema == 'utf8'
|
||||
|| ! config('database.use_utf8mb4') && $schema == 'utf8mb4';
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/UpdateGravatars.php
Normal file
33
app/Console/Commands/UpdateGravatars.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\Avatars\UpdateAllGravatars;
|
||||
|
||||
class UpdateGravatars extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:updategravatars';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update all gravatars';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
UpdateAllGravatars::dispatch();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user