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 @@ -51,11 +51,11 @@ } // Process the result - $result = $result->map(function ($domain) { - $data = $domain->toArray(); - $data = array_merge($data, self::domainStatuses($domain)); - return $data; - }); + $result = $result->map( + function ($domain) { + return $this->objectToClient($domain); + } + ); $result = [ 'list' => $result, 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 @@ -37,16 +37,11 @@ } // Process the result - $result = $result->map(function ($group) { - $data = [ - 'id' => $group->id, - 'email' => $group->email, - 'name' => $group->name, - ]; - - $data = array_merge($data, self::groupStatuses($group)); - return $data; - }); + $result = $result->map( + function ($group) { + return $this->objectToClient($group); + } + ); $result = [ 'list' => $result, 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 @@ -128,9 +128,7 @@ // Process the result $result = $result->map( function ($user) { - $data = $user->toArray(); - $data = array_merge($data, self::userStatuses($user)); - return $data; + return $this->objectToClient($user, true); } ); 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 @@ -6,13 +6,16 @@ use App\Http\Controllers\Controller; use App\Backends\LDAP; use App\Rules\UserEmailDomain; -use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; class DomainsController extends Controller { + /** @var array Common object properties in the API response */ + protected static $objectProps = ['namespace', 'status', 'type']; + + /** * Return a list of domains owned by the current user * @@ -21,19 +24,17 @@ public function index() { $user = $this->guard()->user(); - $list = []; - - foreach ($user->domains() as $domain) { - if (!$domain->isPublic()) { - $data = $domain->toArray(); - $data = array_merge($data, self::domainStatuses($domain)); - $list[] = $data; - } - } - usort($list, function ($a, $b) { - return strcmp($a['namespace'], $b['namespace']); - }); + $list = \collect($user->domains()) + ->filter(function ($domain) { + return !$domain->isPublic(); + }) + ->map(function ($domain) { + return $this->objectToClient($domain); + }) + ->sortBy('namespace') + ->values() + ->all(); return response()->json($list); } @@ -252,7 +253,7 @@ return $this->errorResponse(403); } - $response = $domain->toArray(); + $response = $this->objectToClient($domain, true); // Add hash information to the response $response['hash_text'] = $domain->hash(Domain::HASH_TEXT); @@ -272,8 +273,6 @@ // Entitlements info $response['skus'] = \App\Entitlement::objectEntitlementsSummary($domain); - $response = array_merge($response, self::domainStatuses($domain)); - // Some basic information about the domain wallet $wallet = $domain->wallet(); $response['wallet'] = $wallet->toArray(); @@ -304,36 +303,8 @@ return $this->errorResponse(403); } - $response = self::statusInfo($domain); - - if (!empty(request()->input('refresh'))) { - $updated = false; - $last_step = 'none'; - - foreach ($response['process'] as $idx => $step) { - $last_step = $step['label']; - - if (!$step['state']) { - if (!$this->execProcessStep($domain, $step['label'])) { - break; - } - - $updated = true; - } - } - - if ($updated) { - $response = self::statusInfo($domain); - } - - $success = $response['isReady']; - $suffix = $success ? 'success' : 'error-' . $last_step; - - $response['status'] = $success ? 'success' : 'error'; - $response['message'] = \trans('app.process-' . $suffix); - } - - $response = array_merge($response, self::domainStatuses($domain)); + $response = $this->processStateUpdate($domain); + $response = array_merge($response, self::objectState($domain)); return response()->json($response); } @@ -419,7 +390,7 @@ * * @return array Statuses array */ - protected static function domainStatuses(Domain $domain): array + protected static function objectState(Domain $domain): array { return [ 'isLdapReady' => $domain->isLdapReady(), @@ -440,50 +411,16 @@ */ public static function statusInfo(Domain $domain): array { - $process = []; - // If that is not a public domain, add domain specific steps - $steps = [ - 'domain-new' => true, - 'domain-ldap-ready' => $domain->isLdapReady(), - 'domain-verified' => $domain->isVerified(), - 'domain-confirmed' => $domain->isConfirmed(), - ]; - - $count = count($steps); - - // Create a process check list - foreach ($steps as $step_name => $state) { - $step = [ - 'label' => $step_name, - 'title' => \trans("app.process-{$step_name}"), - 'state' => $state, - ]; - - if ($step_name == 'domain-confirmed' && !$state) { - $step['link'] = "/domain/{$domain->id}"; - } - - $process[] = $step; - - if ($state) { - $count--; - } - } - - $state = $count === 0 ? 'done' : 'running'; - - // After 180 seconds assume the process is in failed state, - // this should unlock the Refresh button in the UI - if ($count > 0 && $domain->created_at->diffInSeconds(Carbon::now()) > 180) { - $state = 'failed'; - } - - return [ - 'process' => $process, - 'processState' => $state, - 'isReady' => $count === 0, - ]; + return self::processStateInfo( + $domain, + [ + 'domain-new' => true, + 'domain-ldap-ready' => $domain->isLdapReady(), + 'domain-verified' => $domain->isVerified(), + 'domain-confirmed' => [$domain->isConfirmed(), "/domain/{$domain->id}"], + ] + ); } /** 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 @@ -7,13 +7,16 @@ use App\Group; use App\Rules\GroupName; use App\User; -use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; class GroupsController extends Controller { + /** @var array Common object properties in the API response */ + protected static $objectProps = ['email', 'name', 'status']; + + /** * Show the form for creating a new group. * @@ -75,15 +78,8 @@ $user = $this->guard()->user(); $result = $user->groups()->orderBy('name')->orderBy('email')->get() - ->map(function (Group $group) { - $data = [ - 'id' => $group->id, - 'email' => $group->email, - 'name' => $group->name, - ]; - - $data = array_merge($data, self::groupStatuses($group)); - return $data; + ->map(function ($group) { + return $this->objectToClient($group); }); return response()->json($result); @@ -139,9 +135,8 @@ return $this->errorResponse(403); } - $response = $group->toArray(); + $response = $this->objectToClient($group, true); - $response = array_merge($response, self::groupStatuses($group)); $response['statusInfo'] = self::statusInfo($group); // Group configuration, e.g. sender_policy @@ -169,49 +164,8 @@ return $this->errorResponse(403); } - $response = self::statusInfo($group); - - if (!empty(request()->input('refresh'))) { - $updated = false; - $async = false; - $last_step = 'none'; - - foreach ($response['process'] as $idx => $step) { - $last_step = $step['label']; - - if (!$step['state']) { - $exec = $this->execProcessStep($group, $step['label']); - - if (!$exec) { - if ($exec === null) { - $async = true; - } - - break; - } - - $updated = true; - } - } - - if ($updated) { - $response = self::statusInfo($group); - } - - $success = $response['isReady']; - $suffix = $success ? 'success' : 'error-' . $last_step; - - $response['status'] = $success ? 'success' : 'error'; - $response['message'] = \trans('app.process-' . $suffix); - - if ($async && !$success) { - $response['processState'] = 'waiting'; - $response['status'] = 'success'; - $response['message'] = \trans('app.process-async'); - } - } - - $response = array_merge($response, self::groupStatuses($group)); + $response = $this->processStateUpdate($group); + $response = array_merge($response, self::objectState($group)); return response()->json($response); } @@ -225,49 +179,13 @@ */ public static function statusInfo(Group $group): array { - $process = []; - $steps = [ - 'distlist-new' => true, - 'distlist-ldap-ready' => $group->isLdapReady(), - ]; - - // Create a process check list - foreach ($steps as $step_name => $state) { - $step = [ - 'label' => $step_name, - 'title' => \trans("app.process-{$step_name}"), - 'state' => $state, - ]; - - $process[] = $step; - } - - $domain = $group->domain(); - - // If that is not a public domain, add domain specific steps - if ($domain && !$domain->isPublic()) { - $domain_status = DomainsController::statusInfo($domain); - $process = array_merge($process, $domain_status['process']); - } - - $all = count($process); - $checked = count(array_filter($process, function ($v) { - return $v['state']; - })); - - $state = $all === $checked ? 'done' : 'running'; - - // After 180 seconds assume the process is in failed state, - // this should unlock the Refresh button in the UI - if ($all !== $checked && $group->created_at->diffInSeconds(Carbon::now()) > 180) { - $state = 'failed'; - } - - return [ - 'process' => $process, - 'processState' => $state, - 'isReady' => $all === $checked, - ]; + return self::processStateInfo( + $group, + [ + 'distlist-new' => true, + 'distlist-ldap-ready' => $group->isLdapReady(), + ] + ); } /** @@ -459,7 +377,7 @@ * * @return array Statuses array */ - protected static function groupStatuses(Group $group): array + protected static function objectState(Group $group): array { return [ 'isLdapReady' => $group->isLdapReady(), diff --git a/src/app/Http/Controllers/API/V4/Reseller/DomainsController.php b/src/app/Http/Controllers/API/V4/Reseller/DomainsController.php --- a/src/app/Http/Controllers/API/V4/Reseller/DomainsController.php +++ b/src/app/Http/Controllers/API/V4/Reseller/DomainsController.php @@ -38,11 +38,11 @@ } // Process the result - $result = $result->map(function ($domain) { - $data = $domain->toArray(); - $data = array_merge($data, self::domainStatuses($domain)); - return $data; - }); + $result = $result->map( + function ($domain) { + return $this->objectToClient($domain); + } + ); $result = [ 'list' => $result, diff --git a/src/app/Http/Controllers/API/V4/Reseller/GroupsController.php b/src/app/Http/Controllers/API/V4/Reseller/GroupsController.php --- a/src/app/Http/Controllers/API/V4/Reseller/GroupsController.php +++ b/src/app/Http/Controllers/API/V4/Reseller/GroupsController.php @@ -36,16 +36,11 @@ } // Process the result - $result = $result->map(function ($group) { - $data = [ - 'id' => $group->id, - 'email' => $group->email, - 'name' => $group->name, - ]; - - $data = array_merge($data, self::groupStatuses($group)); - return $data; - }); + $result = $result->map( + function ($group) { + return $this->objectToClient($group); + } + ); $result = [ 'list' => $result, diff --git a/src/app/Http/Controllers/API/V4/Reseller/UsersController.php b/src/app/Http/Controllers/API/V4/Reseller/UsersController.php --- a/src/app/Http/Controllers/API/V4/Reseller/UsersController.php +++ b/src/app/Http/Controllers/API/V4/Reseller/UsersController.php @@ -91,11 +91,11 @@ } // Process the result - $result = $result->map(function ($user) { - $data = $user->toArray(); - $data = array_merge($data, self::userStatuses($user)); - return $data; - }); + $result = $result->map( + function ($user) { + return $this->objectToClient($user, true); + } + ); $result = [ 'list' => $result, 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 @@ -9,7 +9,6 @@ use App\Rules\UserEmailLocal; use App\Sku; use App\User; -use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; @@ -37,6 +36,9 @@ */ protected $deleteBeforeCreate; + /** @var array Common object properties in the API response */ + protected static $objectProps = ['email', 'status']; + /** * Delete a user. @@ -114,9 +116,7 @@ // Process the result $result = $result->map( function ($user) { - $data = $user->toArray(); - $data = array_merge($data, self::userStatuses($user)); - return $data; + return $this->objectToClient($user); } ); @@ -206,49 +206,8 @@ return $this->errorResponse(403); } - $response = self::statusInfo($user); - - if (!empty(request()->input('refresh'))) { - $updated = false; - $async = false; - $last_step = 'none'; - - foreach ($response['process'] as $idx => $step) { - $last_step = $step['label']; - - if (!$step['state']) { - $exec = $this->execProcessStep($user, $step['label']); - - if (!$exec) { - if ($exec === null) { - $async = true; - } - - break; - } - - $updated = true; - } - } - - if ($updated) { - $response = self::statusInfo($user); - } - - $success = $response['isReady']; - $suffix = $success ? 'success' : 'error-' . $last_step; - - $response['status'] = $success ? 'success' : 'error'; - $response['message'] = \trans('app.process-' . $suffix); - - if ($async && !$success) { - $response['processState'] = 'waiting'; - $response['status'] = 'success'; - $response['message'] = \trans('app.process-async'); - } - } - - $response = array_merge($response, self::userStatuses($user)); + $response = $this->processStateUpdate($user); + $response = array_merge($response, self::objectState($user)); return response()->json($response); } @@ -262,45 +221,14 @@ */ public static function statusInfo(User $user): array { - $process = []; - $steps = [ - 'user-new' => true, - 'user-ldap-ready' => $user->isLdapReady(), - 'user-imap-ready' => $user->isImapReady(), - ]; - - // Create a process check list - foreach ($steps as $step_name => $state) { - $step = [ - 'label' => $step_name, - 'title' => \trans("app.process-{$step_name}"), - 'state' => $state, - ]; - - $process[] = $step; - } - - list ($local, $domain) = explode('@', $user->email); - $domain = Domain::where('namespace', $domain)->first(); - - // If that is not a public domain, add domain specific steps - if ($domain && !$domain->isPublic()) { - $domain_status = DomainsController::statusInfo($domain); - $process = array_merge($process, $domain_status['process']); - } - - $all = count($process); - $checked = count(array_filter($process, function ($v) { - return $v['state']; - })); - - $state = $all === $checked ? 'done' : 'running'; - - // After 180 seconds assume the process is in failed state, - // this should unlock the Refresh button in the UI - if ($all !== $checked && $user->created_at->diffInSeconds(Carbon::now()) > 180) { - $state = 'failed'; - } + $process = self::processStateInfo( + $user, + [ + 'user-new' => true, + 'user-ldap-ready' => $user->isLdapReady(), + 'user-imap-ready' => $user->isImapReady(), + ] + ); // Check if the user is a controller of his wallet $isController = $user->canDelete($user); @@ -318,7 +246,7 @@ ->values() ->all(); - return [ + $result = [ 'skus' => $skus, // TODO: This will change when we enable all users to create domains 'enableDomains' => $isController && $hasCustomDomain, @@ -326,10 +254,9 @@ 'enableDistlists' => $isController && $hasCustomDomain && in_array('distlist', $skus), 'enableUsers' => $isController, 'enableWallets' => $isController, - 'process' => $process, - 'processState' => $state, - 'isReady' => $all === $checked, ]; + + return array_merge($process, $result); } /** @@ -521,7 +448,7 @@ */ public static function userResponse(User $user): array { - $response = $user->toArray(); + $response = self::objectToClient($user, true); // Settings $response['settings'] = []; @@ -538,8 +465,6 @@ // Status info $response['statusInfo'] = self::statusInfo($user); - $response = array_merge($response, self::userStatuses($user)); - // Add more info to the wallet object output $map_func = function ($wallet) use ($user) { $result = $wallet->toArray(); @@ -574,7 +499,7 @@ * * @return array Statuses array */ - protected static function userStatuses(User $user): array + protected static function objectState(User $user): array { return [ 'isImapReady' => $user->isImapReady(), diff --git a/src/app/Http/Controllers/Controller.php b/src/app/Http/Controllers/Controller.php --- a/src/app/Http/Controllers/Controller.php +++ b/src/app/Http/Controllers/Controller.php @@ -14,6 +14,9 @@ use DispatchesJobs; use ValidatesRequests; + /** @var array Common object properties in the API response */ + protected static $objectProps = []; + /** * Common error response builder for API (JSON) responses @@ -81,4 +84,143 @@ { return Auth::guard(); } + + /** + * Object status' process information. + * + * @param object $object The object to process + * @param array $steps The steps definition + * + * @return array Process state information + */ + protected static function processStateInfo($object, array $steps): array + { + $process = []; + + // Create a process check list + foreach ($steps as $step_name => $state) { + $step = [ + 'label' => $step_name, + 'title' => \trans("app.process-{$step_name}"), + ]; + + if (is_array($state)) { + $step['link'] = $state[1]; + $state = $state[0]; + } + + $step['state'] = $state; + + $process[] = $step; + } + + // Add domain specific steps + if (method_exists($object, 'domain')) { + $domain = $object->domain(); + + // If that is not a public domain + if ($domain && !$domain->isPublic()) { + $domain_status = API\V4\DomainsController::statusInfo($domain); + $process = array_merge($process, $domain_status['process']); + } + } + + $all = count($process); + $checked = count(array_filter($process, function ($v) { + return $v['state']; + })); + + $state = $all === $checked ? 'done' : 'running'; + + // After 180 seconds assume the process is in failed state, + // this should unlock the Refresh button in the UI + if ($all !== $checked && $object->created_at->diffInSeconds(\Carbon\Carbon::now()) > 180) { + $state = 'failed'; + } + + return [ + 'process' => $process, + 'processState' => $state, + 'isReady' => $all === $checked, + ]; + } + + /** + * Object status' process information update. + * + * @param object $object The object to process + * + * @return array Process state information + */ + protected function processStateUpdate($object): array + { + $response = $this->statusInfo($object); // @phpstan-ignore-line + + if (!empty(request()->input('refresh'))) { + $updated = false; + $async = false; + $last_step = 'none'; + + foreach ($response['process'] as $idx => $step) { + $last_step = $step['label']; + + if (!$step['state']) { + $exec = $this->execProcessStep($object, $step['label']); // @phpstan-ignore-line + + if (!$exec) { + if ($exec === null) { + $async = true; + } + + break; + } + + $updated = true; + } + } + + if ($updated) { + $response = $this->statusInfo($object); // @phpstan-ignore-line + } + + $success = $response['isReady']; + $suffix = $success ? 'success' : 'error-' . $last_step; + + $response['status'] = $success ? 'success' : 'error'; + $response['message'] = \trans('app.process-' . $suffix); + + if ($async && !$success) { + $response['processState'] = 'waiting'; + $response['status'] = 'success'; + $response['message'] = \trans('app.process-async'); + } + } + + return $response; + } + + /** + * Prepare an object for the UI. + * + * @param object $object An object + * @param bool $full Include all object properties + * + * @return array Object information + */ + protected static function objectToClient($object, bool $full = false): array + { + if ($full) { + $result = $object->toArray(); + } else { + $result = ['id' => $object->id]; + + foreach (static::$objectProps as $prop) { + $result[$prop] = $object->{$prop}; + } + } + + $result = array_merge($result, static::objectState($object)); // @phpstan-ignore-line + + return $result; + } } diff --git a/src/tests/Unit/Controller/DomainsTest.php b/src/tests/Unit/Controller/DomainsTest.php deleted file mode 100644 --- a/src/tests/Unit/Controller/DomainsTest.php +++ /dev/null @@ -1,25 +0,0 @@ -markTestIncomplete(); - } - - /** - * Test DomainsController::statusInfo() - */ - public function testStatusInfo(): void - { - $this->markTestIncomplete(); - } -} diff --git a/src/tests/Unit/Controller/UsersTest.php b/src/tests/Unit/Controller/UsersTest.php deleted file mode 100644 --- a/src/tests/Unit/Controller/UsersTest.php +++ /dev/null @@ -1,25 +0,0 @@ -markTestIncomplete(); - } - - /** - * Test UsersController::statusInfo() - */ - public function testStatusInfo(): void - { - $this->markTestIncomplete(); - } -}