Page MenuHomePhorge

No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None
diff --git a/src/app/Console/Commands/User/ResyncCommand.php b/src/app/Console/Commands/User/ResyncCommand.php
new file mode 100644
index 00000000..0acd0fd6
--- /dev/null
+++ b/src/app/Console/Commands/User/ResyncCommand.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace App\Console\Commands\User;
+
+use App\Console\Command;
+use App\User;
+
+class ResyncCommand extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'user:resync {user?} {--deleted-only} {--dry-run}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = "Re-Synchronize users with the imap/ldap backend(s)";
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $user = $this->argument('user');
+ $deleted_only = $this->option('deleted-only');
+ $dry_run = $this->option('dry-run');
+ $with_ldap = \config('app.with_ldap');
+
+ if (!empty($user)) {
+ if ($req_user = $this->getUser($user, true)) {
+ $users = [$req_user];
+ } else {
+ $this->error("User not found.");
+ return 1;
+ }
+ } else {
+ $users = User::withTrashed();
+
+ if ($deleted_only) {
+ $users->whereNotNull('deleted_at')
+ ->where(function($query) {
+ $query->where('status', '&', User::STATUS_IMAP_READY)->orWhere('status', '&', User::STATUS_LDAP_READY);
+ });
+ }
+
+ $users = $users->orderBy('id')->cursor();
+ }
+
+ // TODO: Maybe we should also have account:resync, domain:resync, resource:resync and so on.
+
+ foreach ($users as $user) {
+ if ($user->trashed()) {
+ if (($with_ldap && $user->isLdapReady()) || $user->isImapReady()) {
+ if ($dry_run) {
+ $this->info("{$user->email}: will be pushed");
+ continue;
+ }
+
+ if ($user->isDeleted()) {
+ // Remove the DELETED flag so the DeleteJob can do the work
+ $user->timestamps = false;
+ $user->update(['status' => $user->status ^ User::STATUS_DELETED]);
+ }
+
+ // TODO: Do this not asyncronously as an option or when a signle user is requested?
+ \App\Jobs\User\DeleteJob::dispatch($user->id);
+
+ $this->info("{$user->email}: pushed");
+ } else {
+ // User properly deleted, no need to push.
+ // Here potentially we could connect to ldap/imap backend and check to be sure
+ // that the user is really deleted no matter what status it has in the database.
+
+ $this->info("{$user->email}: in-sync");
+ }
+ } else {
+ if (!$user->isActive() || ($with_ldap && !$user->isLdapReady()) || !$user->isImapReady()) {
+ if ($dry_run) {
+ $this->info("{$user->email}: will be pushed");
+ continue;
+ }
+
+ \App\Jobs\User\CreateJob::dispatch($user->id);
+ } else if (!empty($req_user)) {
+ if ($dry_run) {
+ $this->info("{$user->email}: will be pushed");
+ continue;
+ }
+
+ // We push the update only if a specific user is requested,
+ // We don't want to flood the database/backend with an update of all users
+ \App\Jobs\User\UpdateJob::dispatch($user->id);
+
+ $this->info("{$user->email}: pushed");
+ } else {
+ $this->info("{$user->email}: in-sync");
+ }
+ }
+ }
+ }
+}
diff --git a/src/tests/Feature/Console/User/ResyncTest.php b/src/tests/Feature/Console/User/ResyncTest.php
new file mode 100644
index 00000000..a98364f5
--- /dev/null
+++ b/src/tests/Feature/Console/User/ResyncTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Tests\Feature\Console\User;
+
+use App\User;
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class ResyncTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('user@force-delete.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestUser('user@force-delete.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test the command
+ */
+ public function testHandle(): void
+ {
+ // Non-existing user
+ $code = \Artisan::call("user:resync unknown");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("User not found.", $output);
+
+ $user = $this->getTestUser('user@force-delete.com');
+ User::where('id', $user->id)->update([
+ 'deleted_at' => now(),
+ 'status' => User::STATUS_DELETED | User::STATUS_IMAP_READY,
+ ]);
+
+ Queue::fake();
+
+ // Test success (--dry-run)
+ $code = \Artisan::call("user:resync {$user->email} --dry-run");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertSame("{$user->email}: will be pushed", $output);
+ $this->assertTrue($user->fresh()->isDeleted());
+
+ Queue::assertNothingPushed();
+
+ // Test success
+ $code = \Artisan::call("user:resync {$user->email}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertSame("{$user->email}: pushed", $output);
+ $user->refresh();
+ $this->assertFalse($user->isDeleted());
+ $this->assertTrue($user->isImapReady());
+
+ Queue::assertPushed(\App\Jobs\User\DeleteJob::class, 1);
+ Queue::assertPushed(\App\Jobs\User\DeleteJob::class, function ($job) use ($user) {
+ $job_user_id = TestCase::getObjectProperty($job, 'userId');
+ return $job_user_id === $user->id;
+ });
+
+ Queue::fake();
+ User::withTrashed()->where('id', $user->id)->update(['status' => User::STATUS_DELETED]);
+
+ // Test nothing to be done (deleted user)
+ $code = \Artisan::call("user:resync {$user->email}");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertSame("{$user->email}: in-sync", $output);
+ Queue::assertNothingPushed();
+
+ Queue::fake();
+ User::withTrashed()->where('id', $user->id)->update([
+ 'status' => User::STATUS_DELETED | User::STATUS_IMAP_READY
+ ]);
+
+ // Remove all deleted users except one, to not interfere
+ User::withTrashed()->whereNotIn('id', [$user->id])->forceDelete();
+
+ // Test run for all deleted users
+ $code = \Artisan::call("user:resync --deleted-only");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertSame("{$user->email}: pushed", $output);
+
+ // TODO: Test other cases (non-deleted users)
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Feb 6, 5:38 AM (9 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
428113
Default Alt Text
(7 KB)

Event Timeline