diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php
--- a/src/app/Http/Controllers/API/AuthController.php
+++ b/src/app/Http/Controllers/API/AuthController.php
@@ -82,7 +82,7 @@
$user = \App\User::where('email', $request->email)->first();
if (!$user) {
- return response()->json(['status' => 'error', 'message' => __('auth.failed')], 401);
+ return response()->json(['status' => 'error', 'message' => \trans('auth.failed')], 401);
}
return self::logonResponse($user, $request->password, $request->secondfactor);
@@ -107,7 +107,7 @@
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
return response()->json([
'status' => 'success',
- 'message' => __('auth.logoutsuccess')
+ 'message' => \trans('auth.logoutsuccess')
]);
}
@@ -161,7 +161,7 @@
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
- return response()->json(['status' => 'error', 'message' => __('auth.failed')], 401);
+ return response()->json(['status' => 'error', 'message' => \trans('auth.failed')], 401);
}
$response['access_token'] = $data->access_token;
diff --git a/src/app/Http/Controllers/API/PasswordResetController.php b/src/app/Http/Controllers/API/PasswordResetController.php
--- a/src/app/Http/Controllers/API/PasswordResetController.php
+++ b/src/app/Http/Controllers/API/PasswordResetController.php
@@ -41,12 +41,12 @@
$user = User::findByEmail($request->email);
if (!$user) {
- $errors = ['email' => __('validation.usernotexists')];
+ $errors = ['email' => \trans('validation.usernotexists')];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
if (!$user->getSetting('external_email')) {
- $errors = ['email' => __('validation.noextemail')];
+ $errors = ['email' => \trans('validation.noextemail')];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php
--- a/src/app/Http/Controllers/API/SignupController.php
+++ b/src/app/Http/Controllers/API/SignupController.php
@@ -48,7 +48,7 @@
$plans[] = [
'title' => $plan->title,
'name' => $plan->name,
- 'button' => __('app.planbutton', ['plan' => $plan->name]),
+ 'button' => \trans('app.planbutton', ['plan' => $plan->name]),
'description' => $plan->description,
];
});
diff --git a/src/app/Http/Controllers/API/V4/Admin/DomainsController.php b/src/app/Http/Controllers/API/V4/Admin/DomainsController.php
--- a/src/app/Http/Controllers/API/V4/Admin/DomainsController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/DomainsController.php
@@ -74,7 +74,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.domain-suspend-success'),
+ 'message' => \trans('app.domain-suspend-success'),
]);
}
@@ -98,7 +98,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.domain-unsuspend-success'),
+ 'message' => \trans('app.domain-unsuspend-success'),
]);
}
}
diff --git a/src/app/Http/Controllers/API/V4/Admin/GroupsController.php b/src/app/Http/Controllers/API/V4/Admin/GroupsController.php
--- a/src/app/Http/Controllers/API/V4/Admin/GroupsController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/GroupsController.php
@@ -88,7 +88,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.distlist-suspend-success'),
+ 'message' => \trans('app.distlist-suspend-success'),
]);
}
@@ -112,7 +112,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.distlist-unsuspend-success'),
+ 'message' => \trans('app.distlist-unsuspend-success'),
]);
}
}
diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
--- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
@@ -173,7 +173,57 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-reset-2fa-success'),
+ 'message' => \trans('app.user-reset-2fa-success'),
+ ]);
+ }
+
+ /**
+ * Set/Add a SKU for the user
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ * @param string $id User identifier
+ * @param string $sku SKU title
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function setSku(Request $request, $id, $sku)
+ {
+ // For now we allow adding the 'beta' SKU only
+ if ($sku != 'beta') {
+ return $this->errorResponse(404);
+ }
+
+ $user = User::find($id);
+
+ if (!$this->checkTenant($user)) {
+ return $this->errorResponse(404);
+ }
+
+ if (!$this->guard()->user()->canUpdate($user)) {
+ return $this->errorResponse(403);
+ }
+
+ $sku = Sku::withObjectTenantContext($user)->where('title', $sku)->first();
+
+ if (!$sku) {
+ return $this->errorResponse(404);
+ }
+
+ if ($user->entitlements()->where('sku_id', $sku->id)->first()) {
+ return $this->errorResponse(422, \trans('app.user-set-sku-already-exists'));
+ }
+
+ $user->assignSku($sku);
+ $entitlement = $user->entitlements()->where('sku_id', $sku->id)->first();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => \trans('app.user-set-sku-success'),
+ 'sku' => [
+ 'cost' => $entitlement->cost,
+ 'name' => $sku->name,
+ 'id' => $sku->id,
+ ]
]);
}
@@ -251,7 +301,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-suspend-success'),
+ 'message' => \trans('app.user-suspend-success'),
]);
}
@@ -279,7 +329,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-unsuspend-success'),
+ 'message' => \trans('app.user-unsuspend-success'),
]);
}
@@ -327,7 +377,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-update-success'),
+ 'message' => \trans('app.user-update-success'),
]);
}
}
diff --git a/src/app/Http/Controllers/API/V4/GroupsController.php b/src/app/Http/Controllers/API/V4/GroupsController.php
--- a/src/app/Http/Controllers/API/V4/GroupsController.php
+++ b/src/app/Http/Controllers/API/V4/GroupsController.php
@@ -46,7 +46,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.distlist-delete-success'),
+ 'message' => \trans('app.distlist-delete-success'),
]);
}
@@ -294,7 +294,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.distlist-create-success'),
+ 'message' => \trans('app.distlist-create-success'),
]);
}
@@ -352,7 +352,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.distlist-update-success'),
+ 'message' => \trans('app.distlist-update-success'),
]);
}
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
@@ -62,7 +62,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-delete-success'),
+ 'message' => \trans('app.user-delete-success'),
]);
}
@@ -113,7 +113,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-setconfig-success'),
+ 'message' => \trans('app.user-setconfig-success'),
]);
}
@@ -359,7 +359,7 @@
return response()->json([
'status' => 'success',
- 'message' => __('app.user-create-success'),
+ 'message' => \trans('app.user-create-success'),
]);
}
@@ -420,7 +420,7 @@
$response = [
'status' => 'success',
- 'message' => __('app.user-update-success'),
+ 'message' => \trans('app.user-update-success'),
];
// For self-update refresh the statusInfo in the UI
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -54,6 +54,8 @@
'user-unsuspend-success' => 'User unsuspended successfully.',
'user-reset-2fa-success' => '2-Factor authentication reset successfully.',
'user-setconfig-success' => 'User settings updated successfully.',
+ 'user-set-sku-success' => 'The subscription added successfully.',
+ 'user-set-sku-already-exists' => 'The subscription already exists.',
'search-foundxdomains' => ':x domains have been found.',
'search-foundxgroups' => ':x distribution lists have been found.',
diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php
--- a/src/resources/lang/en/ui.php
+++ b/src/resources/lang/en/ui.php
@@ -322,6 +322,7 @@
'user' => [
'2fa-hint1' => "This will remove 2-Factor Authentication entitlement as well as the user-configured factors.",
'2fa-hint2' => "Please, make sure to confirm the user identity properly.",
+ 'add-beta' => "Enable beta program",
'address' => "Address",
'aliases' => "Aliases",
'aliases-email' => "Email Aliases",
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -224,6 +224,9 @@
+
@@ -451,6 +454,7 @@
discounts: [],
external_email: '',
has2FA: false,
+ hasBeta: false,
wallet: {},
walletReload: false,
distlists: [],
@@ -529,6 +533,8 @@
if (sku.handler == 'auth2f') {
this.has2FA = true
this.sku2FA = sku.id
+ } else if (sku.handler == 'beta') {
+ this.hasBeta = true
}
}
})
@@ -559,6 +565,22 @@
$(this.$el).find('ul.nav-tabs a').on('click', this.$root.tab)
},
methods: {
+ addBetaSku() {
+ axios.post('/api/v4/users/' + this.user.id + '/skus/beta')
+ .then(response => {
+ if (response.data.status == 'success') {
+ this.$toast.success(response.data.message)
+ this.hasBeta = true
+ const sku = response.data.sku
+ this.skus.push({
+ id: sku.id,
+ name: sku.name,
+ cost: sku.cost,
+ price: this.$root.priceLabel(sku.cost, this.discount)
+ })
+ }
+ })
+ },
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
},
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -188,6 +188,7 @@
Route::get('users/{id}/discounts', 'API\V4\Reseller\DiscountsController@userDiscounts');
Route::post('users/{id}/reset2FA', 'API\V4\Admin\UsersController@reset2FA');
Route::get('users/{id}/skus', 'API\V4\Admin\SkusController@userSkus');
+ Route::post('users/{id}/skus/{sku}', 'API\V4\Admin\UsersController@setSku');
Route::post('users/{id}/suspend', 'API\V4\Admin\UsersController@suspend');
Route::post('users/{id}/unsuspend', 'API\V4\Admin\UsersController@unsuspend');
Route::apiResource('wallets', API\V4\Admin\WalletsController::class);
@@ -232,6 +233,7 @@
Route::get('users/{id}/discounts', 'API\V4\Reseller\DiscountsController@userDiscounts');
Route::post('users/{id}/reset2FA', 'API\V4\Reseller\UsersController@reset2FA');
Route::get('users/{id}/skus', 'API\V4\Reseller\SkusController@userSkus');
+ Route::post('users/{id}/skus/{sku}', 'API\V4\Admin\UsersController@setSku');
Route::post('users/{id}/suspend', 'API\V4\Reseller\UsersController@suspend');
Route::post('users/{id}/unsuspend', 'API\V4\Reseller\UsersController@unsuspend');
Route::apiResource('wallets', API\V4\Reseller\WalletsController::class);
diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php
--- a/src/tests/Browser/Admin/UserTest.php
+++ b/src/tests/Browser/Admin/UserTest.php
@@ -368,7 +368,8 @@
->assertSeeIn('table tbody tr:nth-child(6) td:last-child', '45,09 CHF/month¹')
->assertMissing('table tfoot')
->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher')
- ->assertSeeIn('#reset2fa', 'Reset 2-Factor Auth');
+ ->assertSeeIn('#reset2fa', 'Reset 2-Factor Auth')
+ ->assertMissing('#addbetasku');
});
// We don't expect John's domains here
@@ -515,4 +516,29 @@
->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (0)');
});
}
+
+ /**
+ * Test adding the beta SKU for the user
+ */
+ public function testAddBetaSku(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $this->deleteTestUser('userstest1@kolabnow.com');
+ $user = $this->getTestUser('userstest1@kolabnow.com');
+ $sku = Sku::withEnvTenantContext()->where('title', 'beta')->first();
+
+ $browser->visit(new UserPage($user->id))
+ ->click('@nav #tab-subscriptions')
+ ->waitFor('@user-subscriptions #addbetasku')
+ ->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (0)')
+ ->assertSeeIn('#addbetasku', 'Enable beta program')
+ ->click('#addbetasku')
+ ->assertToast(Toast::TYPE_SUCCESS, 'The subscription added successfully.')
+ ->waitFor('#sku' . $sku->id)
+ ->assertSeeIn("#sku{$sku->id} td:first-child", 'Private Beta (invitation only)')
+ ->assertSeeIn("#sku{$sku->id} td:last-child", '0,00 CHF/month')
+ ->assertMissing('#addbetasku')
+ ->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (1)');
+ });
+ }
}
diff --git a/src/tests/Browser/Reseller/UserTest.php b/src/tests/Browser/Reseller/UserTest.php
--- a/src/tests/Browser/Reseller/UserTest.php
+++ b/src/tests/Browser/Reseller/UserTest.php
@@ -480,4 +480,29 @@
->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (0)');
});
}
+
+ /**
+ * Test adding the beta SKU for the user
+ */
+ public function testAddBetaSku(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $this->deleteTestUser('userstest1@kolabnow.com');
+ $user = $this->getTestUser('userstest1@kolabnow.com');
+ $sku = Sku::withEnvTenantContext()->where('title', 'beta')->first();
+
+ $browser->visit(new UserPage($user->id))
+ ->click('@nav #tab-subscriptions')
+ ->waitFor('@user-subscriptions #addbetasku')
+ ->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (0)')
+ ->assertSeeIn('#addbetasku', 'Enable beta program')
+ ->click('#addbetasku')
+ ->assertToast(Toast::TYPE_SUCCESS, 'The subscription added successfully.')
+ ->waitFor('#sku' . $sku->id)
+ ->assertSeeIn("#sku{$sku->id} td:first-child", 'Private Beta (invitation only)')
+ ->assertSeeIn("#sku{$sku->id} td:last-child", '0,00 CHF/month')
+ ->assertMissing('#addbetasku')
+ ->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (1)');
+ });
+ }
}
diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php
--- a/src/tests/Feature/Controller/Admin/UsersTest.php
+++ b/src/tests/Feature/Controller/Admin/UsersTest.php
@@ -282,6 +282,8 @@
*/
public function testReset2FA(): void
{
+ Queue::fake();
+
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
$admin = $this->getTestUser('jeroen@jeroen.jeroen');
@@ -316,6 +318,58 @@
$this->assertCount(0, $sf->factors());
}
+ /**
+ * Test adding beta SKU (POST /api/v4/users//skus/beta)
+ */
+ public function testAddBetaSku(): void
+ {
+ Queue::fake();
+
+ $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $sku = Sku::withEnvTenantContext()->where(['title' => 'beta'])->first();
+
+ // Test unauthorized access to admin API
+ $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(403);
+
+ // For now we allow only the beta sku
+ $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/skus/mailbox", []);
+ $response->assertStatus(404);
+
+ $entitlements = $user->entitlements()->where('sku_id', $sku->id)->get();
+ $this->assertCount(0, $entitlements);
+
+ // Test adding the beta sku
+ $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("The subscription added successfully.", $json['message']);
+ $this->assertSame(0, $json['sku']['cost']);
+ $this->assertSame($sku->id, $json['sku']['id']);
+ $this->assertSame($sku->name, $json['sku']['name']);
+ $this->assertCount(3, $json);
+
+ $entitlements = $user->entitlements()->where('sku_id', $sku->id)->get();
+ $this->assertCount(1, $entitlements);
+
+ // Test adding the beta sku again, expect an error
+ $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+
+ $this->assertSame('error', $json['status']);
+ $this->assertSame("The subscription already exists.", $json['message']);
+ $this->assertCount(2, $json);
+
+ $entitlements = $user->entitlements()->where('sku_id', $sku->id)->get();
+ $this->assertCount(1, $entitlements);
+ }
+
/**
* Test user creation (POST /api/v4/users)
*/
diff --git a/src/tests/Feature/Controller/Reseller/UsersTest.php b/src/tests/Feature/Controller/Reseller/UsersTest.php
--- a/src/tests/Feature/Controller/Reseller/UsersTest.php
+++ b/src/tests/Feature/Controller/Reseller/UsersTest.php
@@ -299,6 +299,70 @@
$this->assertCount(0, $sf->factors());
}
+ /**
+ * Test adding beta SKU (POST /api/v4/users//skus/beta)
+ */
+ public function testAddBetaSku(): void
+ {
+ Queue::fake(); // disable jobs
+
+ $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $reseller1 = $this->getTestUser('reseller@' . \config('app.domain'));
+ $reseller2 = $this->getTestUser('reseller@sample-tenant.dev-local');
+ $sku = Sku::withEnvTenantContext()->where(['title' => 'beta'])->first();
+
+ // Test unauthorized access to reseller API
+ $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(403);
+
+ $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(403);
+
+ $response = $this->actingAs($reseller2)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(404);
+
+ // Touching admins is forbidden
+ $response = $this->actingAs($reseller1)->post("/api/v4/users/{$admin->id}/skus/beta", []);
+ $response->assertStatus(403);
+
+ // For now we allow only the beta sku
+ $response = $this->actingAs($reseller1)->post("/api/v4/users/{$user->id}/skus/mailbox", []);
+ $response->assertStatus(404);
+
+ $entitlements = $user->entitlements()->where('sku_id', $sku->id)->get();
+ $this->assertCount(0, $entitlements);
+
+ // Test adding the beta sku
+ $response = $this->actingAs($reseller1)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("The subscription added successfully.", $json['message']);
+ $this->assertSame(0, $json['sku']['cost']);
+ $this->assertSame($sku->id, $json['sku']['id']);
+ $this->assertSame($sku->name, $json['sku']['name']);
+ $this->assertCount(3, $json);
+
+ $entitlements = $user->entitlements()->where('sku_id', $sku->id)->get();
+ $this->assertCount(1, $entitlements);
+
+ // Test adding the beta sku again, expect an error
+ $response = $this->actingAs($reseller1)->post("/api/v4/users/{$user->id}/skus/beta", []);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+
+ $this->assertSame('error', $json['status']);
+ $this->assertSame("The subscription already exists.", $json['message']);
+ $this->assertCount(2, $json);
+
+ $entitlements = $user->entitlements()->where('sku_id', $sku->id)->get();
+ $this->assertCount(1, $entitlements);
+ }
+
/**
* Test user creation (POST /api/v4/users)
*/