Page MenuHomePhorge

No OneTemporary

Size
12 KB
Referenced Files
None
Subscribers
None
diff --git a/src/app/Console/Commands/Domain/CreateCommand.php b/src/app/Console/Commands/Domain/CreateCommand.php
index 2ff9558b..61a06578 100644
--- a/src/app/Console/Commands/Domain/CreateCommand.php
+++ b/src/app/Console/Commands/Domain/CreateCommand.php
@@ -1,90 +1,89 @@
<?php
namespace App\Console\Commands\Domain;
use App\Console\Command;
-use Illuminate\Support\Facades\Queue;
class CreateCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'domain:create {domain} {--force}';
/**
* The console command description.
*
* @var string
*/
protected $description = "Create a domain";
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$namespace = \strtolower($this->argument('domain'));
// must use withTrashed(), because unique constraint
$domain = \App\Domain::withTrashed()->where('namespace', $namespace)->first();
if ($domain && !$this->option('force')) {
$this->error("Domain {$namespace} already exists.");
return 1;
}
if ($domain) {
if ($domain->deleted_at) {
// set the status back to new
$domain->status = \App\Domain::STATUS_NEW;
$domain->save();
// remove existing entitlement
$entitlement = \App\Entitlement::withTrashed()->where(
[
'entitleable_id' => $domain->id,
'entitleable_type' => \App\Domain::class
]
)->first();
if ($entitlement) {
$entitlement->forceDelete();
}
// restore the domain to allow for the observer to handle the create job
$domain->restore();
$this->info(
sprintf(
"Domain %s with ID %d revived. Remember to assign it to a wallet with 'domain:set-wallet'",
$domain->namespace,
$domain->id
)
);
} else {
$this->error("Domain {$namespace} not marked as deleted... examine more closely");
return 1;
}
} else {
$domain = \App\Domain::create(
[
'namespace' => $namespace,
'type' => \App\Domain::TYPE_EXTERNAL,
]
);
$this->info(
sprintf(
"Domain %s created with ID %d. Remember to assign it to a wallet with 'domain:set-wallet'",
$domain->namespace,
$domain->id
)
);
}
}
}
diff --git a/src/app/Console/Commands/Tenant/CreateCommand.php b/src/app/Console/Commands/Tenant/CreateCommand.php
new file mode 100644
index 00000000..73023c06
--- /dev/null
+++ b/src/app/Console/Commands/Tenant/CreateCommand.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace App\Console\Commands\Tenant;
+
+use App\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Queue;
+
+class CreateCommand extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'tenant:create {user} {--title=}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = "Create a tenant (with a set of SKUs/plans/packages) and make the user a reseller.";
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $user = $this->getUser($this->argument('user'));
+
+ if (!$user) {
+ $this->error('User not found.');
+ return 1;
+ }
+
+ DB::beginTransaction();
+
+ // Create a tenant
+ $tenant = \App\Tenant::create(['title' => $this->option('title') ?: $user->name()]);
+
+ // Clone plans, packages, skus for the tenant
+ $sku_map = [];
+ \App\Sku::withEnvTenant()->where('active', true)->get()
+ ->each(function ($sku) use ($sku_map, $tenant) {
+ $sku_new = \App\Sku::create([
+ 'title' => $sku->title,
+ 'name' => $sku->getTranslations('name'),
+ 'description' => $sku->getTranslations('description'),
+ 'cost' => $sku->cost,
+ 'units_free' => $sku->units_free,
+ 'period' => $sku->period,
+ 'handler_class' => $sku->handler_class,
+ 'active' => true,
+ 'fee' => $sku->fee,
+ ]);
+
+ $sku_new->tenant_id = $tenant->id;
+ $sku_new->save();
+
+ $sku_map[$sku->id] = $sku_new->id;
+ });
+
+ $plan_map = [];
+ \App\Plan::withEnvTenant()->get()
+ ->each(function ($plan) use ($plan_map, $tenant) {
+ $plan_new = \App\Plan::create([
+ 'title' => $plan->title,
+ 'name' => $plan->getTranslations('name'),
+ 'description' => $plan->getTranslations('description'),
+ 'promo_from' => $plan->promo_from,
+ 'promo_to' => $plan->promo_to,
+ 'qty_min' => $plan->qty_min,
+ 'qty_max' => $plan->qty_max,
+ 'discount_qty' => $plan->discount_qty,
+ 'discount_rate' => $plan->discount_rate,
+ ]);
+
+ $plan_new->tenant_id = $tenant->id;
+ $plan_new->save();
+
+ $plan_map[$plan->id] = $plan_new->id;
+ });
+
+ $package_map = [];
+ \App\Package::withEnvTenant()->get()
+ ->each(function ($package) use ($package_map, $tenant) {
+ $package_new = \App\Package::create([
+ 'title' => $package->title,
+ 'name' => $package->getTranslations('name'),
+ 'description' => $package->getTranslations('description'),
+ 'discount_rate' => $package->discount_rate,
+ ]);
+
+ $package_new->tenant_id = $tenant->id;
+ $package_new->save();
+
+ $package_map[$package->id] = $package_new->id;
+ });
+
+ DB::table('package_skus')->whereIn('package_id', array_keys($package_map))->get()
+ ->each(function ($item) use ($package_map, $sku_map) {
+ if (isset($sku_map[$item->sku_id])) {
+ DB::table('package_skus')->insert([
+ 'qty' => $item->qty,
+ 'cost' => $item->cost,
+ 'sku_id' => $sku_map[$item->sku_id],
+ 'package_id' => $package_map[$item->package_id],
+ ]);
+ }
+ });
+
+ DB::table('plan_packages')->whereIn('plan_id', array_keys($plan_map))->get()
+ ->each(function ($item) use ($package_map, $plan_map) {
+ if (isset($package_map[$item->package_id])) {
+ DB::table('plan_packages')->insert([
+ 'qty' => $item->qty,
+ 'qty_min' => $item->qty_min,
+ 'qty_max' => $item->qty_max,
+ 'discount_qty' => $item->discount_qty,
+ 'discount_rate' => $item->discount_rate,
+ 'plan_id' => $plan_map[$item->plan_id],
+ 'package_id' => $package_map[$item->package_id],
+ ]);
+ }
+ });
+
+ // Disable jobs, they would fail anyway as the TENANT_ID is different
+ // TODO: We could probably do config(['app.tenant' => $tenant->id]) here
+ Queue::fake();
+
+ // Assign 'reseller' role to the user
+ $user->role = 'reseller';
+ $user->tenant_id = $tenant->id;
+ $user->save();
+
+ // Switch tenant_id for all of the user belongings
+ $user->wallets->each(function ($wallet) use ($tenant) {
+ $wallet->entitlements->each(function ($entitlement) use ($tenant) {
+ $entitlement->entitleable->tenant_id = $tenant->id;
+ $entitlement->entitleable->save();
+
+ // TODO: If user already has any entitlements, they will have to be
+ // removed/replaced by SKUs in the newly created tenant
+ // I think we don't really support this yet anyway.
+ });
+
+ // TODO: If the wallet has a discount we should remove/replace it too
+ // I think we don't really support this yet anyway.
+ });
+
+ DB::commit();
+
+ // Make sure the transaction wasn't aborted
+ $tenant = \App\Tenant::find($tenant->id);
+
+ if (!$tenant) {
+ $this->error("Failed to create a tenant.");
+ return 1;
+ }
+
+ $this->info("Created tenant {$tenant->id}.");
+ }
+}
diff --git a/src/tests/Feature/Console/Tenant/CreateTest.php b/src/tests/Feature/Console/Tenant/CreateTest.php
new file mode 100644
index 00000000..cd6d8017
--- /dev/null
+++ b/src/tests/Feature/Console/Tenant/CreateTest.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Tests\Feature\Console\Tenant;
+
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class CreateTest extends TestCase
+{
+ private $tenantId;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('test-tenant@kolabnow.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ if ($this->tenantId) {
+ Queue::fake();
+
+ \App\User::where('tenant_id', $this->tenantId)->forceDelete();
+ \App\Plan::where('tenant_id', $this->tenantId)->delete();
+ \App\Package::where('tenant_id', $this->tenantId)->delete();
+ \App\Sku::where('tenant_id', $this->tenantId)->delete();
+ \App\Tenant::find($this->tenantId)->delete();
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test command runs
+ */
+ public function testHandle(): void
+ {
+ Queue::fake();
+
+ // Warning: We're not using artisan() here, as this will not
+ // allow us to test "empty output" cases
+
+ // User not existing
+ $code = \Artisan::call("tenant:create unknown@user.com");
+ $output = trim(\Artisan::output());
+ $this->assertSame(1, $code);
+ $this->assertSame("User not found.", $output);
+
+ $user = $this->getTestUser('test-tenant@kolabnow.com');
+
+ $this->assertEmpty($user->role);
+ $this->assertEquals($user->tenant_id, \config('app.tenant_id'));
+
+ // User not existing
+ $code = \Artisan::call("tenant:create {$user->email} --title=\"Test Tenant\"");
+ $output = trim(\Artisan::output());
+ $this->assertSame(0, $code);
+ $this->assertRegExp("/^Created tenant [0-9]+./", $output);
+
+ preg_match("/^Created tenant ([0-9]+)./", $output, $matches);
+ $this->tenantId = $matches[1];
+
+ $tenant = \App\Tenant::find($this->tenantId);
+ $user->refresh();
+
+ $this->assertNotEmpty($tenant);
+ $this->assertSame('Test Tenant', $tenant->title);
+ $this->assertSame('reseller', $user->role);
+ $this->assertSame($tenant->id, $user->tenant_id);
+
+ // Assert cloned SKUs
+ $skus = \App\Sku::where('tenant_id', \config('app.tenant_id'))->where('active', true);
+
+ $skus->each(function ($sku) use ($tenant) {
+ $sku_new = \App\Sku::where('tenant_id', $tenant->id)
+ ->where('title', $sku->title)->get();
+
+ $this->assertSame(1, $sku_new->count());
+ $sku_new = $sku_new->first();
+ $this->assertSame($sku->name, $sku_new->name);
+ $this->assertSame($sku->description, $sku_new->description);
+ $this->assertSame($sku->cost, $sku_new->cost);
+ $this->assertSame($sku->units_free, $sku_new->units_free);
+ $this->assertSame($sku->period, $sku_new->period);
+ $this->assertSame($sku->handler_class, $sku_new->handler_class);
+ $this->assertNotEmpty($sku_new->active);
+ });
+
+ // TODO: Plans, packages
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Jan 30, 11:29 PM (5 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426074
Default Alt Text
(12 KB)

Event Timeline