diff --git a/src/app/Handlers/DomainHosting.php b/src/app/Handlers/DomainHosting.php --- a/src/app/Handlers/DomainHosting.php +++ b/src/app/Handlers/DomainHosting.php @@ -13,4 +13,21 @@ { return \App\Domain::class; } + + /** + * SKU handler metadata. + * + * @param \App\Sku $sku The SKU object + * + * @return array + */ + public static function metadata(\App\Sku $sku): array + { + $data = parent::metadata($sku); + + $data['readonly'] = true; + $data['enabled'] = true; + + return $data; + } } diff --git a/src/app/Handlers/Group.php b/src/app/Handlers/Group.php --- a/src/app/Handlers/Group.php +++ b/src/app/Handlers/Group.php @@ -13,4 +13,21 @@ { return \App\Group::class; } + + /** + * SKU handler metadata. + * + * @param \App\Sku $sku The SKU object + * + * @return array + */ + public static function metadata(\App\Sku $sku): array + { + $data = parent::metadata($sku); + + $data['readonly'] = true; + $data['enabled'] = true; + + return $data; + } } diff --git a/src/app/Handlers/Resource.php b/src/app/Handlers/Resource.php --- a/src/app/Handlers/Resource.php +++ b/src/app/Handlers/Resource.php @@ -13,4 +13,21 @@ { return \App\Resource::class; } + + /** + * SKU handler metadata. + * + * @param \App\Sku $sku The SKU object + * + * @return array + */ + public static function metadata(\App\Sku $sku): array + { + $data = parent::metadata($sku); + + $data['readonly'] = true; + $data['enabled'] = true; + + return $data; + } } diff --git a/src/app/Handlers/SharedFolder.php b/src/app/Handlers/SharedFolder.php --- a/src/app/Handlers/SharedFolder.php +++ b/src/app/Handlers/SharedFolder.php @@ -13,4 +13,21 @@ { return \App\SharedFolder::class; } + + /** + * SKU handler metadata. + * + * @param \App\Sku $sku The SKU object + * + * @return array + */ + public static function metadata(\App\Sku $sku): array + { + $data = parent::metadata($sku); + + $data['readonly'] = true; + $data['enabled'] = true; + + return $data; + } } diff --git a/src/app/Http/Controllers/API/V4/DomainsController.php b/src/app/Http/Controllers/API/V4/DomainsController.php --- a/src/app/Http/Controllers/API/V4/DomainsController.php +++ b/src/app/Http/Controllers/API/V4/DomainsController.php @@ -205,16 +205,8 @@ // Status info $response['statusInfo'] = self::statusInfo($domain); - // Entitlements info - $response['skus'] = \App\Entitlement::objectEntitlementsSummary($domain); - - // Some basic information about the domain wallet - $wallet = $domain->wallet(); - $response['wallet'] = $wallet->toArray(); - if ($wallet->discount) { - $response['wallet']['discount'] = $wallet->discount->discount; - $response['wallet']['discount_description'] = $wallet->discount->description; - } + // Entitlements/Wallet info + SkusController::objectEntitlements($domain, $response); return response()->json($response); } 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 @@ -184,6 +184,8 @@ return response()->json(['status' => 'error', 'errors' => $errors], 422); } + // SkusController::updateEntitlements($group, $request->skus); + $group->members = $members; $group->save(); diff --git a/src/app/Http/Controllers/API/V4/ResourcesController.php b/src/app/Http/Controllers/API/V4/ResourcesController.php --- a/src/app/Http/Controllers/API/V4/ResourcesController.php +++ b/src/app/Http/Controllers/API/V4/ResourcesController.php @@ -132,6 +132,8 @@ return response()->json(['status' => 'error', 'errors' => $errors], 422); } + // SkusController::updateEntitlements($resource, $request->skus); + $resource->save(); return response()->json([ diff --git a/src/app/Http/Controllers/API/V4/SharedFoldersController.php b/src/app/Http/Controllers/API/V4/SharedFoldersController.php --- a/src/app/Http/Controllers/API/V4/SharedFoldersController.php +++ b/src/app/Http/Controllers/API/V4/SharedFoldersController.php @@ -118,6 +118,8 @@ DB::beginTransaction(); + // SkusController::updateEntitlements($folder, $request->skus); + if ($name && $name != $folder->name) { $folder->name = $name; } diff --git a/src/app/Http/Controllers/API/V4/SkusController.php b/src/app/Http/Controllers/API/V4/SkusController.php --- a/src/app/Http/Controllers/API/V4/SkusController.php +++ b/src/app/Http/Controllers/API/V4/SkusController.php @@ -9,72 +9,45 @@ class SkusController extends ResourceController { /** - * Get a list of SKUs available to the domain. - * - * @param int $id Domain identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function domainSkus($id) - { - $domain = \App\Domain::find($id); - - if (!$this->checkTenant($domain)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($domain)) { - return $this->errorResponse(403); - } - - return $this->objectSkus($domain); - } - - /** * Get a list of active SKUs. * * @return \Illuminate\Http\JsonResponse */ public function index() { - // Note: Order by title for consistent ordering in tests - $skus = Sku::withSubjectTenantContext()->where('active', true)->orderBy('title')->get(); - - $response = []; - - foreach ($skus as $sku) { - if ($data = $this->skuElement($sku)) { - $response[] = $data; - } - } - - usort($response, function ($a, $b) { - return ($b['prio'] <=> $a['prio']); - }); - - return response()->json($response); - } - - /** - * Get a list of SKUs available to the user. - * - * @param int $id User identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function userSkus($id) - { - $user = \App\User::find($id); + $type = request()->input('type'); - if (!$this->checkTenant($user)) { - return $this->errorResponse(404); - } + // Note: Order by title for consistent ordering in tests + $response = Sku::withSubjectTenantContext()->where('active', true)->orderBy('title') + ->get() + ->transform(function ($sku) { + return $this->skuElement($sku); + }) + ->filter(function ($sku) use ($type) { + return !$type || $sku['type'] === $type; + }) + ->sortByDesc('prio') + ->values(); + + if ($type) { + $wallet = $this->guard()->user()->wallet(); + + // Figure out the cost for a new object of the specified type + $response = $response->map(function ($sku) use ($wallet) { + $sku['nextCost'] = $sku['cost']; + if ($sku['cost'] && $sku['units_free']) { + $count = $wallet->entitlements()->where('sku_id', $sku['id'])->count(); + + if ($count < $sku['units_free']) { + $sku['nextCost'] = 0; + } + } - if (!$this->guard()->user()->canRead($user)) { - return $this->errorResponse(403); + return $sku; + }); } - return $this->objectSkus($user); + return response()->json($response->all()); } /** @@ -84,9 +57,9 @@ * * @return \Illuminate\Http\JsonResponse */ - protected static function objectSkus($object) + public static function objectSkus($object) { - $type = $object instanceof \App\Domain ? 'domain' : 'user'; + $type = \lcfirst(\class_basename($object::class)); $response = []; // Note: Order by title for consistent ordering in tests @@ -118,6 +91,75 @@ } /** + * Include SKUs/Wallet information in the object's response. + * + * @param object $object User/Domain/etc object + * @param array $response The response to put the data into + */ + public static function objectEntitlements($object, &$response = []): void + { + // Object's entitlements information + $response['skus'] = \App\Entitlement::objectEntitlementsSummary($object); + + // Some basic information about the object's wallet + $wallet = $object->wallet(); + $response['wallet'] = $wallet->toArray(); + if ($wallet->discount) { + $response['wallet']['discount'] = $wallet->discount->discount; + $response['wallet']['discount_description'] = $wallet->discount->description; + } + } + + /** + * Update object entitlements. + * + * @param object $object The object for update + * @param array $rSkus List of SKU IDs requested for the user in the form [id=>qty] + */ + public static function updateEntitlements($object, $rSkus): void + { + if (!is_array($rSkus)) { + return; + } + + // list of skus, [id=>obj] + $skus = Sku::withEnvTenantContext()->get()->mapWithKeys( + function ($sku) { + return [$sku->id => $sku]; + } + ); + + // existing entitlement's SKUs + $eSkus = []; + + $object->entitlements()->groupBy('sku_id') + ->selectRaw('count(*) as total, sku_id')->each( + function ($e) use (&$eSkus) { + $eSkus[$e->sku_id] = $e->total; + } + ); + + foreach ($skus as $skuID => $sku) { + $e = array_key_exists($skuID, $eSkus) ? $eSkus[$skuID] : 0; + $r = array_key_exists($skuID, $rSkus) ? $rSkus[$skuID] : 0; + + if ($sku->handler_class == \App\Handlers\Mailbox::class) { + if ($r != 1) { + throw new \Exception("Invalid quantity of mailboxes"); + } + } + + if ($e > $r) { + // remove those entitled more than existing + $object->removeSku($sku, ($e - $r)); + } elseif ($e < $r) { + // add those requested more than entitled + $object->assignSku($sku, ($r - $e)); + } + } + } + + /** * Convert SKU information to metadata used by UI to * display the form control * 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 @@ -165,6 +165,7 @@ // Check if the user is a controller of his wallet $isController = $user->canDelete($user); + $isDegraded = $user->isDegraded(); $hasCustomDomain = $user->wallet()->entitlements() ->where('entitleable_type', Domain::class) ->count() > 0; @@ -301,7 +302,7 @@ DB::beginTransaction(); - $this->updateEntitlements($user, $request->skus); + SkusController::updateEntitlements($user, $request->skus); if (!empty($settings)) { $user->setSettings($settings); @@ -334,55 +335,6 @@ } /** - * Update user entitlements. - * - * @param \App\User $user The user - * @param array $rSkus List of SKU IDs requested for the user in the form [id=>qty] - */ - protected function updateEntitlements(User $user, $rSkus) - { - if (!is_array($rSkus)) { - return; - } - - // list of skus, [id=>obj] - $skus = Sku::withEnvTenantContext()->get()->mapWithKeys( - function ($sku) { - return [$sku->id => $sku]; - } - ); - - // existing entitlement's SKUs - $eSkus = []; - - $user->entitlements()->groupBy('sku_id') - ->selectRaw('count(*) as total, sku_id')->each( - function ($e) use (&$eSkus) { - $eSkus[$e->sku_id] = $e->total; - } - ); - - foreach ($skus as $skuID => $sku) { - $e = array_key_exists($skuID, $eSkus) ? $eSkus[$skuID] : 0; - $r = array_key_exists($skuID, $rSkus) ? $rSkus[$skuID] : 0; - - if ($sku->handler_class == \App\Handlers\Mailbox::class) { - if ($r != 1) { - throw new \Exception("Invalid quantity of mailboxes"); - } - } - - if ($e > $r) { - // remove those entitled more than existing - $user->removeSku($sku, ($e - $r)); - } elseif ($e < $r) { - // add those requested more than entitled - $user->assignSku($sku, ($r - $e)); - } - } - } - - /** * Create a response data array for specified user. * * @param \App\User $user User object diff --git a/src/app/Http/Controllers/RelationController.php b/src/app/Http/Controllers/RelationController.php --- a/src/app/Http/Controllers/RelationController.php +++ b/src/app/Http/Controllers/RelationController.php @@ -321,10 +321,37 @@ $response['aliases'] = $resource->aliases()->pluck('alias')->all(); } + // Entitlements/Wallet info + if (method_exists($resource, 'wallet')) { + API\V4\SkusController::objectEntitlements($resource, $response); + } + return response()->json($response); } /** + * Get a list of SKUs available to the resource. + * + * @param int $id Resource identifier + * + * @return \Illuminate\Http\JsonResponse + */ + public function skus($id) + { + $resource = $this->model::find($id); + + if (!$this->checkTenant($resource)) { + return $this->errorResponse(404); + } + + if (!$this->guard()->user()->canRead($resource)) { + return $this->errorResponse(403); + } + + return API\V4\SkusController::objectSkus($resource); + } + + /** * Fetch resource status (and reload setup process) * * @param int $id Resource identifier diff --git a/src/resources/js/app.js b/src/resources/js/app.js --- a/src/resources/js/app.js +++ b/src/resources/js/app.js @@ -367,18 +367,20 @@ return response }, error => { - let loader = error.config.loader - if (loader) { - stopLoading(loader) - } + if (error.config) { + let loader = error.config.loader + if (loader) { + stopLoading(loader) + } - // Do not display the error in a toast message, pass the error as-is - if (axios.isCancel(error) || error.config.ignoreErrors) { - return Promise.reject(error) - } + // Do not display the error in a toast message, pass the error as-is + if (axios.isCancel(error) || error.config.ignoreErrors) { + return Promise.reject(error) + } - if (error.config.onFinish) { - error.config.onFinish() + if (error.config.onFinish) { + error.config.onFinish() + } } let error_msg diff --git a/src/resources/vue/Distlist/Info.vue b/src/resources/vue/Distlist/Info.vue --- a/src/resources/vue/Distlist/Info.vue +++ b/src/resources/vue/Distlist/Info.vue @@ -38,6 +38,10 @@ +
+ + +
{{ $t('btn.submit') }} @@ -65,11 +69,13 @@