Page MenuHomePhorge

D4938.1775204528.diff
No OneTemporary

Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None

D4938.1775204528.diff

diff --git a/src/app/Console/Commands/Data/Import/LicensesCommand.php b/src/app/Console/Commands/Data/Import/LicensesCommand.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/Data/Import/LicensesCommand.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Console\Commands\Data\Import;
+
+use App\License;
+use App\Console\Command;
+
+class LicensesCommand extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'data:import:licenses {type} {file} {--tenant=}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Imports licenses from a file.';
+
+ /** @var bool Adds --tenant option handler */
+ protected $withTenant = true;
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ parent::handle();
+
+ $file = $this->argument('file');
+ $type = $this->argument('type');
+
+ if (!file_exists($file)) {
+ $this->error("File '$file' does not exist");
+ return 1;
+ }
+
+ $list = file($file);
+
+ if (empty($list)) {
+ $this->error("File '$file' is empty");
+ return 1;
+ }
+
+ $list = array_map('trim', $list);
+
+ $bar = $this->createProgressBar(count($list), "Importing license keys");
+
+ // Import licenses
+ foreach ($list as $key) {
+ License::create([
+ 'key' => $key,
+ 'type' => $type,
+ 'tenant_id' => $this->tenantId,
+ ]);
+
+ $bar->advance();
+ }
+
+ $bar->finish();
+
+ $this->info("DONE");
+ }
+}
diff --git a/src/app/Console/Commands/LicensesCommand.php b/src/app/Console/Commands/LicensesCommand.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/LicensesCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Console\ObjectListCommand;
+
+/**
+ * List licenses.
+ */
+class LicensesCommand extends ObjectListCommand
+{
+ protected $objectClass = \App\License::class;
+ protected $objectName = 'license';
+}
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -4,6 +4,7 @@
use App\Http\Controllers\RelationController;
use App\Domain;
+use App\License;
use App\Plan;
use App\Rules\Password;
use App\Rules\UserEmailDomain;
@@ -110,6 +111,64 @@
return response()->json($result);
}
+ /**
+ * Get a license information.
+ *
+ * @param string $id The account to get licenses for
+ * @param string $type License type
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function licenses(string $id, string $type)
+ {
+ $user = User::find($id);
+
+ if (!$this->checkTenant($user)) {
+ return $this->errorResponse(404);
+ }
+
+ if (!$this->guard()->user()->canRead($user)) {
+ return $this->errorResponse(403);
+ }
+
+ $licenses = $user->licenses()->where('type', $type)->orderBy('created_at')->get();
+
+ // No licenses for the user, take one if available
+ if (!count($licenses)) {
+ DB::beginTransaction();
+
+ $license = License::withObjectTenantContext($user)
+ ->where('type', $type)
+ ->whereNull('user_id')
+ ->limit(1)
+ ->lockForUpdate()
+ ->first();
+
+ if ($license) {
+ $license->user_id = $user->id;
+ $license->save();
+
+ $licenses = \collect([$license]);
+ }
+
+ DB::commit();
+ }
+
+ // Slim down the result set
+ $licenses = $licenses->map(function ($license) {
+ return [
+ 'key' => $license->key,
+ 'type' => $license->type,
+ ];
+ });
+
+ return response()->json([
+ 'list' => $licenses,
+ 'count' => count($licenses),
+ 'hasMore' => false, // TODO
+ ]);
+ }
+
/**
* Display information on the user account specified by $id.
*
diff --git a/src/app/License.php b/src/app/License.php
new file mode 100644
--- /dev/null
+++ b/src/app/License.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace App;
+
+use App\Traits\BelongsToTenantTrait;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * The eloquent definition of a License
+ *
+ * @property int $id The license identifier
+ * @property string $key The license key
+ * @property string $type An email address
+ * @property int $tenant_id Tenant identifier
+ * @property ?int $user_id User identifier
+ */
+class License extends Model
+{
+ use BelongsToTenantTrait;
+
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'updated_at' => 'datetime:Y-m-d H:i:s',
+ ];
+
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['key', 'type', 'tenant_id'];
+}
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -422,6 +422,16 @@
return ($this->status & self::STATUS_RESTRICTED) > 0;
}
+ /**
+ * Licenses whis user has.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function licenses()
+ {
+ return $this->hasMany(License::class);
+ }
+
/**
* A shortcut to get the user name.
*
diff --git a/src/database/migrations/2024_09_13_100000_create_licenses_table.php b/src/database/migrations/2024_09_13_100000_create_licenses_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2024_09_13_100000_create_licenses_table.php
@@ -0,0 +1,45 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create(
+ 'licenses',
+ function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->bigInteger('user_id')->nullable()->index();
+ $table->bigInteger('tenant_id')->unsigned()->nullable()->index();
+ $table->string('type', 16);
+ $table->string('key', 255);
+ $table->timestamps();
+
+ $table->unique(['type', 'key']);
+
+ $table->foreign('user_id')->references('id')->on('users')
+ ->onDelete('cascade')->onUpdate('cascade');
+ $table->foreign('tenant_id')->references('id')->on('tenants')
+ ->onDelete('set null')->onUpdate('cascade');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('licenses');
+ }
+};
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -164,6 +164,8 @@
Route::post('users/{id}/config', [API\V4\UsersController::class, 'setConfig']);
Route::get('users/{id}/skus', [API\V4\UsersController::class, 'skus']);
Route::get('users/{id}/status', [API\V4\UsersController::class, 'status']);
+ Route::get('users/{id}/licenses/{type}', [API\V4\UsersController::class, 'licenses']);
+
Route::apiResource('wallets', API\V4\WalletsController::class);
Route::get('wallets/{id}/transactions', [API\V4\WalletsController::class, 'transactions']);
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -5,6 +5,7 @@
use App\Discount;
use App\Domain;
use App\Http\Controllers\API\V4\UsersController;
+use App\License;
use App\Package;
use App\Plan;
use App\Sku;
@@ -290,6 +291,68 @@
// TODO: Test paging
}
+ /**
+ * Test fetching licenses for a user (GET /users/<id>/licenses/<type>)
+ */
+ public function testLicenses(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+ $user->licenses()->delete();
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/users/{$user->id}/licenses/test");
+ $response->assertStatus(401);
+
+ // Access forbidden
+ $response = $this->actingAs($jack)->get("api/v4/users/{$user->id}/licenses/test");
+ $response->assertStatus(403);
+
+ $license = License::create([
+ 'key' => (string) microtime(true),
+ 'type' => 'test',
+ 'tenant_id' => $user->tenant_id,
+ ]);
+
+ // Unknow type
+ $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/licenses/unknown");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(0, $json['list']);
+ $this->assertSame(0, $json['count']);
+ $this->assertFalse($json['hasMore']);
+
+ // Valid type, existing license - expect license assignment
+ $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/licenses/test");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(1, $json['list']);
+ $this->assertSame(1, $json['count']);
+ $this->assertFalse($json['hasMore']);
+ $this->assertSame($license->key, $json['list'][0]['key']);
+ $this->assertSame($license->type, $json['list'][0]['type']);
+
+ $license->refresh();
+ $this->assertEquals($user->id, $license->user_id);
+
+ // Try again with assigned license
+ $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/licenses/test");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(1, $json['list']);
+ $this->assertSame(1, $json['count']);
+ $this->assertFalse($json['hasMore']);
+ $this->assertSame($license->key, $json['list'][0]['key']);
+ $this->assertSame($license->type, $json['list'][0]['type']);
+ $this->assertEquals($user->id, $license->user_id);
+ }
+
/**
* Test fetching user data/profile (GET /api/v4/users/<user-id>)
*/

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 3, 8:22 AM (6 h, 35 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823203
Default Alt Text
D4938.1775204528.diff (10 KB)

Event Timeline