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 @@ -11,7 +11,7 @@ /** * Remove the specified domain. * - * @param int $id Domain identifier + * @param string $id Domain identifier * * @return \Illuminate\Http\JsonResponse */ 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 @@ -14,7 +14,7 @@ /** * Delete a user. * - * @param int $id User identifier + * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ @@ -227,44 +227,6 @@ } /** - * Display information on the user account specified by $id. - * - * @param int $id The account to show information for. - * - * @return \Illuminate\Http\JsonResponse - */ - public function show($id) - { - $user = User::find($id); - - if (!$this->checkTenant($user)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($user)) { - return $this->errorResponse(403); - } - - $response = $this->userResponse($user); - - // Simplified Entitlement/SKU information, - // TODO: I agree this format may need to be extended in future - $response['skus'] = []; - foreach ($user->entitlements as $ent) { - $sku = $ent->sku; - if (!isset($response['skus'][$sku->id])) { - $response['skus'][$sku->id] = ['costs' => [], 'count' => 0]; - } - $response['skus'][$sku->id]['count']++; - $response['skus'][$sku->id]['costs'][] = $ent->cost; - } - - $response['config'] = $user->getConfig(); - - return response()->json($response); - } - - /** * Create a new user record. * * @param \Illuminate\Http\Request $request The API request. 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 @@ -3,48 +3,30 @@ namespace App\Http\Controllers\API\V4; use App\Domain; -use App\Http\Controllers\Controller; +use App\Http\Controllers\RelationController; use App\Backends\LDAP; use App\Rules\UserEmailDomain; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; -class DomainsController extends Controller +class DomainsController extends RelationController { - /** @var array Common object properties in the API response */ - protected static $objectProps = ['namespace', 'type']; + /** @var string Resource localization label */ + protected $label = 'domain'; + /** @var string Resource model name */ + protected $model = Domain::class; - /** - * Return a list of domains owned by the current user - * - * @return \Illuminate\Http\JsonResponse - */ - public function index() - { - $user = $this->guard()->user(); + /** @var array Common object properties in the API response */ + protected $objectProps = ['namespace', 'type']; - $list = $user->domains(true, false) - ->orderBy('namespace') - ->get() - ->map(function ($domain) { - return $this->objectToClient($domain); - }) - ->all(); + /** @var array Resource listing order (column names) */ + protected $order = ['namespace']; - return response()->json($list); - } + /** @var array Resource relation method arguments */ + protected $relationArgs = [true, false]; - /** - * Show the form for creating a new domain. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - return $this->errorResponse(404); - } /** * Confirm ownership of the specified domain (via DNS check). @@ -82,7 +64,7 @@ /** * Remove the specified domain. * - * @param int $id Domain identifier + * @param string $id Domain identifier * * @return \Illuminate\Http\JsonResponse */ @@ -113,50 +95,6 @@ } /** - * Show the form for editing the specified domain. - * - * @param int $id Domain identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - return $this->errorResponse(404); - } - - /** - * Set the domain configuration. - * - * @param int $id Domain identifier - * - * @return \Illuminate\Http\JsonResponse|void - */ - public function setConfig($id) - { - $domain = Domain::find($id); - - if (empty($domain)) { - return $this->errorResponse(404); - } - - // Only owner (or admin) has access to the domain - if (!$this->guard()->user()->canUpdate($domain)) { - return $this->errorResponse(403); - } - - $errors = $domain->setConfig(request()->input()); - - if (!empty($errors)) { - return response()->json(['status' => 'error', 'errors' => $errors], 422); - } - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.domain-setconfig-success'), - ]); - } - - /** * Create a domain. * * @param \Illuminate\Http\Request $request @@ -234,7 +172,7 @@ /** * Get the information about the specified domain. * - * @param int $id Domain identifier + * @param string $id Domain identifier * * @return \Illuminate\Http\JsonResponse|void */ @@ -282,44 +220,6 @@ } /** - * Fetch domain status (and reload setup process) - * - * @param int $id Domain identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function status($id) - { - $domain = Domain::find($id); - - if (!$this->checkTenant($domain)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($domain)) { - return $this->errorResponse(403); - } - - $response = $this->processStateUpdate($domain); - $response = array_merge($response, self::objectState($domain)); - - return response()->json($response); - } - - /** - * Update the specified domain. - * - * @param \Illuminate\Http\Request $request - * @param int $id Domain identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function update(Request $request, $id) - { - return $this->errorResponse(404); - } - - /** * Provide DNS MX information to configure specified domain for */ protected static function getMXConfig(string $namespace): array @@ -387,7 +287,7 @@ * * @return array Statuses array */ - protected static function objectState(Domain $domain): array + protected static function objectState($domain): array { return [ 'isLdapReady' => $domain->isLdapReady(), @@ -406,7 +306,7 @@ * * @return array Status information */ - public static function statusInfo(Domain $domain): array + public static function statusInfo($domain): array { // If that is not a public domain, add domain specific steps return self::processStateInfo( 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 @@ -2,7 +2,7 @@ namespace App\Http\Controllers\API\V4; -use App\Http\Controllers\Controller; +use App\Http\Controllers\RelationController; use App\Domain; use App\Group; use App\Rules\GroupName; @@ -11,164 +11,20 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; -class GroupsController extends Controller +class GroupsController extends RelationController { - /** @var array Common object properties in the API response */ - protected static $objectProps = ['email', 'name']; - - - /** - * Show the form for creating a new group. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - return $this->errorResponse(404); - } - - /** - * Delete a group. - * - * @param int $id Group identifier - * - * @return \Illuminate\Http\JsonResponse The response - */ - public function destroy($id) - { - $group = Group::find($id); - - if (!$this->checkTenant($group)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canDelete($group)) { - return $this->errorResponse(403); - } - - $group->delete(); - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.distlist-delete-success'), - ]); - } - - /** - * Show the form for editing the specified group. - * - * @param int $id Group identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - return $this->errorResponse(404); - } - - /** - * Listing of groups belonging to the authenticated user. - * - * The group-entitlements billed to the current user wallet(s) - * - * @return \Illuminate\Http\JsonResponse - */ - public function index() - { - $user = $this->guard()->user(); - - $result = $user->groups()->orderBy('name')->orderBy('email')->get() - ->map(function ($group) { - return $this->objectToClient($group); - }); - - return response()->json($result); - } - - /** - * Set the group configuration. - * - * @param int $id Group identifier - * - * @return \Illuminate\Http\JsonResponse|void - */ - public function setConfig($id) - { - $group = Group::find($id); - - if (!$this->checkTenant($group)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canUpdate($group)) { - return $this->errorResponse(403); - } - - $errors = $group->setConfig(request()->input()); - - if (!empty($errors)) { - return response()->json(['status' => 'error', 'errors' => $errors], 422); - } + /** @var string Resource localization label */ + protected $label = 'distlist'; - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.distlist-setconfig-success'), - ]); - } + /** @var string Resource model name */ + protected $model = Group::class; - /** - * Display information of a group specified by $id. - * - * @param int $id The group to show information for. - * - * @return \Illuminate\Http\JsonResponse - */ - public function show($id) - { - $group = Group::find($id); + /** @var array Resource listing order (column names) */ + protected $order = ['name', 'email']; - if (!$this->checkTenant($group)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($group)) { - return $this->errorResponse(403); - } - - $response = $this->objectToClient($group, true); - - $response['statusInfo'] = self::statusInfo($group); - - // Group configuration, e.g. sender_policy - $response['config'] = $group->getConfig(); - - return response()->json($response); - } - - /** - * Fetch group status (and reload setup process) - * - * @param int $id Group identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function status($id) - { - $group = Group::find($id); - - if (!$this->checkTenant($group)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($group)) { - return $this->errorResponse(403); - } - - $response = $this->processStateUpdate($group); - $response = array_merge($response, self::objectState($group)); + /** @var array Common object properties in the API response */ + protected $objectProps = ['email', 'name']; - return response()->json($response); - } /** * Group status (extended) information @@ -177,7 +33,7 @@ * * @return array Status information */ - public static function statusInfo(Group $group): array + public static function statusInfo($group): array { return self::processStateInfo( $group, @@ -377,7 +233,7 @@ * * @return array Statuses array */ - protected static function objectState(Group $group): array + protected static function objectState($group): array { return [ 'isLdapReady' => $group->isLdapReady(), diff --git a/src/app/Http/Controllers/API/V4/PackagesController.php b/src/app/Http/Controllers/API/V4/PackagesController.php --- a/src/app/Http/Controllers/API/V4/PackagesController.php +++ b/src/app/Http/Controllers/API/V4/PackagesController.php @@ -3,49 +3,12 @@ namespace App\Http\Controllers\API\V4; use App\Package; -use App\Http\Controllers\Controller; +use App\Http\Controllers\ResourceController; use Illuminate\Http\Request; -class PackagesController extends Controller +class PackagesController extends ResourceController { /** - * Show the form for creating a new package. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - // TODO - return $this->errorResponse(404); - } - - /** - * Remove the specified package from storage. - * - * @param int $id Package identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function destroy($id) - { - // TODO - return $this->errorResponse(404); - } - - /** - * Show the form for editing the specified package. - * - * @param int $id Package identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - // TODO - return $this->errorResponse(404); - } - - /** * Display a listing of packages. * * @return \Illuminate\Http\JsonResponse @@ -69,44 +32,4 @@ return response()->json($response); } - - /** - * Store a newly created package in storage. - * - * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\JsonResponse - */ - public function store(Request $request) - { - // TODO - return $this->errorResponse(404); - } - - /** - * Display the specified package. - * - * @param int $id Package identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function show($id) - { - // TODO - return $this->errorResponse(404); - } - - /** - * Update the specified package in storage. - * - * @param \Illuminate\Http\Request $request Request object - * @param int $id Package identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function update(Request $request, $id) - { - // TODO - return $this->errorResponse(404); - } } 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 @@ -2,169 +2,43 @@ namespace App\Http\Controllers\API\V4; -use App\Http\Controllers\Controller; +use App\Http\Controllers\RelationController; use App\Resource; use App\Rules\ResourceName; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; -class ResourcesController extends Controller +class ResourcesController extends RelationController { - /** @var array Common object properties in the API response */ - protected static $objectProps = ['email', 'name']; - - /** - * Show the form for creating a new resource. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - return $this->errorResponse(404); - } - - /** - * Delete a resource. - * - * @param int $id Resource identifier - * - * @return \Illuminate\Http\JsonResponse The response - */ - public function destroy($id) - { - $resource = Resource::find($id); - - if (!$this->checkTenant($resource)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canDelete($resource)) { - return $this->errorResponse(403); - } - - $resource->delete(); - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.resource-delete-success'), - ]); - } - - /** - * Show the form for editing the specified resource. - * - * @param int $id Resource identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - return $this->errorResponse(404); - } - - /** - * Listing of resources belonging to the authenticated user. - * - * The resource-entitlements billed to the current user wallet(s) - * - * @return \Illuminate\Http\JsonResponse - */ - public function index() - { - $user = $this->guard()->user(); - - $result = $user->resources()->orderBy('name')->get() - ->map(function (Resource $resource) { - return $this->objectToClient($resource); - }); - - return response()->json($result); - } - - /** - * Set the resource configuration. - * - * @param int $id Resource identifier - * - * @return \Illuminate\Http\JsonResponse|void - */ - public function setConfig($id) - { - $resource = Resource::find($id); + /** @var string Resource localization label */ + protected $label = 'resource'; - if (!$this->checkTenant($resource)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canUpdate($resource)) { - return $this->errorResponse(403); - } + /** @var string Resource model name */ + protected $model = Resource::class; - $errors = $resource->setConfig(request()->input()); + /** @var array Resource listing order (column names) */ + protected $order = ['name']; - if (!empty($errors)) { - return response()->json(['status' => 'error', 'errors' => $errors], 422); - } - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.resource-setconfig-success'), - ]); - } - - /** - * Display information of a resource specified by $id. - * - * @param int $id Resource identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function show($id) - { - $resource = Resource::find($id); - - if (!$this->checkTenant($resource)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($resource)) { - return $this->errorResponse(403); - } - - $response = $this->objectToClient($resource, true); - - $response['statusInfo'] = self::statusInfo($resource); + /** @var array Common object properties in the API response */ + protected $objectProps = ['email', 'name']; - // Resource configuration, e.g. invitation_policy - $response['config'] = $resource->getConfig(); - - return response()->json($response); - } /** - * Fetch resource status (and reload setup process) + * Prepare resource statuses for the UI * - * @param int $id Resource identifier + * @param \App\Resource $resource Resource object * - * @return \Illuminate\Http\JsonResponse + * @return array Statuses array */ - public function status($id) + protected static function objectState($resource): array { - $resource = Resource::find($id); - - if (!$this->checkTenant($resource)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($resource)) { - return $this->errorResponse(403); - } - - $response = $this->processStateUpdate($resource); - $response = array_merge($response, self::objectState($resource)); - - return response()->json($response); + return [ + 'isLdapReady' => $resource->isLdapReady(), + 'isImapReady' => $resource->isImapReady(), + 'isActive' => $resource->isActive(), + 'isDeleted' => $resource->isDeleted() || $resource->trashed(), + ]; } /** @@ -174,7 +48,7 @@ * * @return array Status information */ - public static function statusInfo(Resource $resource): array + public static function statusInfo($resource): array { return self::processStateInfo( $resource, @@ -332,21 +206,4 @@ return false; } - - /** - * Prepare resource statuses for the UI - * - * @param \App\Resource $resource Resource object - * - * @return array Statuses array - */ - protected static function objectState(Resource $resource): array - { - return [ - 'isLdapReady' => $resource->isLdapReady(), - 'isImapReady' => $resource->isImapReady(), - 'isActive' => $resource->isActive(), - 'isDeleted' => $resource->isDeleted() || $resource->trashed(), - ]; - } } 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 @@ -2,7 +2,7 @@ namespace App\Http\Controllers\API\V4; -use App\Http\Controllers\Controller; +use App\Http\Controllers\RelationController; use App\SharedFolder; use App\Rules\SharedFolderName; use App\Rules\SharedFolderType; @@ -10,162 +10,36 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; -class SharedFoldersController extends Controller +class SharedFoldersController extends RelationController { - /** @var array Common object properties in the API response */ - protected static $objectProps = ['email', 'name', 'type']; - - /** - * Show the form for creating a new shared folder. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - return $this->errorResponse(404); - } - - /** - * Delete a shared folder. - * - * @param int $id Shared folder identifier - * - * @return \Illuminate\Http\JsonResponse The response - */ - public function destroy($id) - { - $folder = SharedFolder::find($id); - - if (!$this->checkTenant($folder)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canDelete($folder)) { - return $this->errorResponse(403); - } - - $folder->delete(); - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.shared-folder-delete-success'), - ]); - } - - /** - * Show the form for editing the specified shared folder. - * - * @param int $id Shared folder identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - return $this->errorResponse(404); - } - - /** - * Listing of a shared folders belonging to the authenticated user. - * - * The shared-folder entitlements billed to the current user wallet(s) - * - * @return \Illuminate\Http\JsonResponse - */ - public function index() - { - $user = $this->guard()->user(); - - $result = $user->sharedFolders()->orderBy('name')->get() - ->map(function (SharedFolder $folder) { - return $this->objectToClient($folder); - }); - - return response()->json($result); - } - - /** - * Set the shared folder configuration. - * - * @param int $id Shared folder identifier - * - * @return \Illuminate\Http\JsonResponse|void - */ - public function setConfig($id) - { - $folder = SharedFolder::find($id); + /** @var string Resource localization label */ + protected $label = 'shared-folder'; - if (!$this->checkTenant($folder)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canUpdate($folder)) { - return $this->errorResponse(403); - } + /** @var string Resource model name */ + protected $model = SharedFolder::class; - $errors = $folder->setConfig(request()->input()); + /** @var array Resource listing order (column names) */ + protected $order = ['name']; - if (!empty($errors)) { - return response()->json(['status' => 'error', 'errors' => $errors], 422); - } - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.shared-folder-setconfig-success'), - ]); - } - - /** - * Display information of a shared folder specified by $id. - * - * @param int $id Shared folder identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function show($id) - { - $folder = SharedFolder::find($id); - - if (!$this->checkTenant($folder)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($folder)) { - return $this->errorResponse(403); - } - - $response = $this->objectToClient($folder, true); - - $response['statusInfo'] = self::statusInfo($folder); + /** @var array Common object properties in the API response */ + protected $objectProps = ['email', 'name', 'type']; - // Shared folder configuration, e.g. acl - $response['config'] = $folder->getConfig(); - - return response()->json($response); - } /** - * Fetch a shared folder status (and reload setup process) + * Prepare shared folder statuses for the UI * - * @param int $id Shared folder identifier + * @param \App\SharedFolder $folder Shared folder object * - * @return \Illuminate\Http\JsonResponse + * @return array Statuses array */ - public function status($id) + protected static function objectState($folder): array { - $folder = SharedFolder::find($id); - - if (!$this->checkTenant($folder)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($folder)) { - return $this->errorResponse(403); - } - - $response = $this->processStateUpdate($folder); - $response = array_merge($response, self::objectState($folder)); - - return response()->json($response); + return [ + 'isLdapReady' => $folder->isLdapReady(), + 'isImapReady' => $folder->isImapReady(), + 'isActive' => $folder->isActive(), + 'isDeleted' => $folder->isDeleted() || $folder->trashed(), + ]; } /** @@ -175,7 +49,7 @@ * * @return array Status information */ - public static function statusInfo(SharedFolder $folder): array + public static function statusInfo($folder): array { return self::processStateInfo( $folder, @@ -337,21 +211,4 @@ return false; } - - /** - * Prepare shared folder statuses for the UI - * - * @param \App\SharedFolder $folder Shared folder object - * - * @return array Statuses array - */ - protected static function objectState(SharedFolder $folder): array - { - return [ - 'isLdapReady' => $folder->isLdapReady(), - 'isImapReady' => $folder->isImapReady(), - 'isActive' => $folder->isActive(), - 'isDeleted' => $folder->isDeleted() || $folder->trashed(), - ]; - } } 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 @@ -2,37 +2,13 @@ namespace App\Http\Controllers\API\V4; -use App\Http\Controllers\Controller; +use App\Http\Controllers\ResourceController; use App\Sku; use Illuminate\Http\Request; -class SkusController extends Controller +class SkusController extends ResourceController { /** - * Show the form for creating a new sku. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - // TODO - return $this->errorResponse(404); - } - - /** - * Remove the specified sku from storage. - * - * @param int $id SKU identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function destroy($id) - { - // TODO - return $this->errorResponse(404); - } - - /** * Get a list of SKUs available to the domain. * * @param int $id Domain identifier @@ -55,19 +31,6 @@ } /** - * Show the form for editing the specified sku. - * - * @param int $id SKU identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - // TODO - return $this->errorResponse(404); - } - - /** * Get a list of active SKUs. * * @return \Illuminate\Http\JsonResponse @@ -93,46 +56,6 @@ } /** - * Store a newly created sku in storage. - * - * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\JsonResponse - */ - public function store(Request $request) - { - // TODO - return $this->errorResponse(404); - } - - /** - * Display the specified sku. - * - * @param int $id SKU identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function show($id) - { - // TODO - return $this->errorResponse(404); - } - - /** - * Update the specified sku in storage. - * - * @param \Illuminate\Http\Request $request Request object - * @param int $id SKU identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function update(Request $request, $id) - { - // TODO - return $this->errorResponse(404); - } - - /** * Get a list of SKUs available to the user. * * @param int $id User identifier 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 @@ -2,7 +2,7 @@ namespace App\Http\Controllers\API\V4; -use App\Http\Controllers\Controller; +use App\Http\Controllers\RelationController; use App\Domain; use App\Group; use App\Rules\UserEmailDomain; @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; -class UsersController extends Controller +class UsersController extends RelationController { /** @const array List of user setting keys available for modification in UI */ public const USER_SETTINGS = [ @@ -36,37 +36,15 @@ */ protected $deleteBeforeCreate; - /** @var array Common object properties in the API response */ - protected static $objectProps = ['email']; - - - /** - * Delete a user. - * - * @param int $id User identifier - * - * @return \Illuminate\Http\JsonResponse The response - */ - public function destroy($id) - { - $user = User::withEnvTenantContext()->find($id); + /** @var string Resource localization label */ + protected $label = 'user'; - if (empty($user)) { - return $this->errorResponse(404); - } + /** @var string Resource model name */ + protected $model = User::class; - // User can't remove himself until he's the controller - if (!$this->guard()->user()->canDelete($user)) { - return $this->errorResponse(403); - } + /** @var array Common object properties in the API response */ + protected $objectProps = ['email']; - $user->delete(); - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.user-delete-success'), - ]); - } /** * Listing of users. @@ -130,48 +108,17 @@ } /** - * Set user config. - * - * @param int $id The user - * - * @return \Illuminate\Http\JsonResponse - */ - public function setConfig($id) - { - $user = User::find($id); - - if (empty($user)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canUpdate($user)) { - return $this->errorResponse(403); - } - - $errors = $user->setConfig(request()->input()); - - if (!empty($errors)) { - return response()->json(['status' => 'error', 'errors' => $errors], 422); - } - - return response()->json([ - 'status' => 'success', - 'message' => \trans('app.user-setconfig-success'), - ]); - } - - /** * Display information on the user account specified by $id. * - * @param int $id The account to show information for. + * @param string $id The account to show information for. * * @return \Illuminate\Http\JsonResponse */ public function show($id) { - $user = User::withEnvTenantContext()->find($id); + $user = User::find($id); - if (empty($user)) { + if (!$this->checkTenant($user)) { return $this->errorResponse(404); } @@ -188,38 +135,13 @@ } /** - * Fetch user status (and reload setup process) - * - * @param int $id User identifier - * - * @return \Illuminate\Http\JsonResponse - */ - public function status($id) - { - $user = User::withEnvTenantContext()->find($id); - - if (empty($user)) { - return $this->errorResponse(404); - } - - if (!$this->guard()->user()->canRead($user)) { - return $this->errorResponse(403); - } - - $response = $this->processStateUpdate($user); - $response = array_merge($response, self::objectState($user)); - - return response()->json($response); - } - - /** * User status (extended) information * * @param \App\User $user User object * * @return array Status information */ - public static function statusInfo(User $user): array + public static function statusInfo($user): array { $process = self::processStateInfo( $user, @@ -452,7 +374,7 @@ */ public static function userResponse(User $user): array { - $response = self::objectToClient($user, true); + $response = array_merge($user->toArray(), self::objectState($user)); // Settings $response['settings'] = []; @@ -503,7 +425,7 @@ * * @return array Statuses array */ - protected static function objectState(User $user): array + protected static function objectState($user): array { return [ 'isImapReady' => $user->isImapReady(), diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php --- a/src/app/Http/Controllers/API/V4/WalletsController.php +++ b/src/app/Http/Controllers/API/V4/WalletsController.php @@ -4,7 +4,7 @@ use App\Transaction; use App\Wallet; -use App\Http\Controllers\Controller; +use App\Http\Controllers\ResourceController; use App\Providers\PaymentProvider; use Carbon\Carbon; use Illuminate\Http\Request; @@ -12,41 +12,9 @@ /** * API\WalletsController */ -class WalletsController extends Controller +class WalletsController extends ResourceController { /** - * Display a listing of the resource. - * - * @return \Illuminate\Http\JsonResponse - */ - public function index() - { - return $this->errorResponse(404); - } - - /** - * Show the form for creating a new resource. - * - * @return \Illuminate\Http\JsonResponse - */ - public function create() - { - return $this->errorResponse(404); - } - - /** - * Store a newly created resource in storage. - * - * @param \Illuminate\Http\Request $request - * - * @return \Illuminate\Http\JsonResponse - */ - public function store(Request $request) - { - return $this->errorResponse(404); - } - - /** * Return data of the specified wallet. * * @param string $id A wallet identifier @@ -77,43 +45,6 @@ } /** - * Show the form for editing the specified resource. - * - * @param int $id - * - * @return \Illuminate\Http\JsonResponse - */ - public function edit($id) - { - return $this->errorResponse(404); - } - - /** - * Update the specified resource in storage. - * - * @param \Illuminate\Http\Request $request - * @param string $id - * - * @return \Illuminate\Http\JsonResponse - */ - public function update(Request $request, $id) - { - return $this->errorResponse(404); - } - - /** - * Remove the specified resource from storage. - * - * @param int $id - * - * @return \Illuminate\Http\JsonResponse - */ - public function destroy($id) - { - return $this->errorResponse(404); - } - - /** * Download a receipt in pdf format. * * @param string $id Wallet identifier 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,9 +14,6 @@ use DispatchesJobs; use ValidatesRequests; - /** @var array Common object properties in the API response */ - protected static $objectProps = []; - /** * Common error response builder for API (JSON) responses @@ -84,143 +81,4 @@ { 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/app/Http/Controllers/RelationController.php b/src/app/Http/Controllers/RelationController.php new file mode 100644 --- /dev/null +++ b/src/app/Http/Controllers/RelationController.php @@ -0,0 +1,335 @@ +model::find($id); + + if (!$this->checkTenant($resource)) { + return $this->errorResponse(404); + } + + if (!$this->guard()->user()->canDelete($resource)) { + return $this->errorResponse(403); + } + + $resource->delete(); + + return response()->json([ + 'status' => 'success', + 'message' => \trans("app.{$this->label}-delete-success"), + ]); + } + + /** + * Listing of resources belonging to the authenticated user. + * + * The resource entitlements billed to the current user wallet(s) + * + * @return \Illuminate\Http\JsonResponse + */ + public function index() + { + $user = $this->guard()->user(); + + $method = Str::plural(\lcfirst(\class_basename($this->model))); + + $query = call_user_func_array([$user, $method], $this->relationArgs); + + if (!empty($this->order)) { + foreach ($this->order as $col) { + $query->orderBy($col); + } + } + + $result = $query->get() + ->map(function ($resource) { + return $this->objectToClient($resource); + }); + + return response()->json($result); + } + + /** + * Prepare resource statuses for the UI + * + * @param object $resource Resource object + * + * @return array Statuses array + */ + protected static function objectState($resource): array + { + return []; + } + + /** + * Prepare a resource object for the UI. + * + * @param object $object An object + * @param bool $full Include all object properties + * + * @return array Object information + */ + protected function objectToClient($object, bool $full = false): array + { + if ($full) { + $result = $object->toArray(); + } else { + $result = ['id' => $object->id]; + + foreach ($this->objectProps as $prop) { + $result[$prop] = $object->{$prop}; + } + } + + $result = array_merge($result, $this->objectState($object)); + + return $result; + } + + /** + * 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); + + 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); + } + + $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; + } + + /** + * Set the resource configuration. + * + * @param int $id Resource identifier + * + * @return \Illuminate\Http\JsonResponse|void + */ + public function setConfig($id) + { + $resource = $this->model::find($id); + + if (!method_exists($this->model, 'setConfig')) { + return $this->errorResponse(404); + } + + if (!$this->checkTenant($resource)) { + return $this->errorResponse(404); + } + + if (!$this->guard()->user()->canUpdate($resource)) { + return $this->errorResponse(403); + } + + $errors = $resource->setConfig(request()->input()); + + if (!empty($errors)) { + return response()->json(['status' => 'error', 'errors' => $errors], 422); + } + + return response()->json([ + 'status' => 'success', + 'message' => \trans("app.{$this->label}-setconfig-success"), + ]); + } + + /** + * Display information of a resource specified by $id. + * + * @param string $id The resource to show information for. + * + * @return \Illuminate\Http\JsonResponse + */ + public function show($id) + { + $resource = $this->model::find($id); + + if (!$this->checkTenant($resource)) { + return $this->errorResponse(404); + } + + if (!$this->guard()->user()->canRead($resource)) { + return $this->errorResponse(403); + } + + $response = $this->objectToClient($resource, true); + + if (!empty($statusInfo = $this->statusInfo($resource))) { + $response['statusInfo'] = $statusInfo; + } + + // Resource configuration, e.g. sender_policy, invitation_policy, acl + if (method_exists($resource, 'getConfig')) { + $response['config'] = $resource->getConfig(); + } + + return response()->json($response); + } + + /** + * Fetch resource status (and reload setup process) + * + * @param int $id Resource identifier + * + * @return \Illuminate\Http\JsonResponse + */ + public function status($id) + { + $resource = $this->model::find($id); + + if (!$this->checkTenant($resource)) { + return $this->errorResponse(404); + } + + if (!$this->guard()->user()->canRead($resource)) { + return $this->errorResponse(403); + } + + $response = $this->processStateUpdate($resource); + $response = array_merge($response, $this->objectState($resource)); + + return response()->json($response); + } + + /** + * Resource status (extended) information + * + * @param object $resource Resource object + * + * @return array Status information + */ + public static function statusInfo($resource): array + { + return []; + } +} diff --git a/src/app/Http/Controllers/ResourceController.php b/src/app/Http/Controllers/ResourceController.php new file mode 100644 --- /dev/null +++ b/src/app/Http/Controllers/ResourceController.php @@ -0,0 +1,91 @@ +errorResponse(404); + } + + /** + * Delete a resource. + * + * @param string $id Resource identifier + * + * @return \Illuminate\Http\JsonResponse The response + */ + public function destroy($id) + { + return $this->errorResponse(404); + } + + /** + * Show the form for editing the specified resource. + * + * @param string $id Resource identifier + * + * @return \Illuminate\Http\JsonResponse + */ + public function edit($id) + { + return $this->errorResponse(404); + } + + /** + * Listing of resources belonging to the authenticated user. + * + * The resource entitlements billed to the current user wallet(s) + * + * @return \Illuminate\Http\JsonResponse + */ + public function index() + { + return $this->errorResponse(404); + } + + /** + * Display information of a resource specified by $id. + * + * @param string $id The resource to show information for. + * + * @return \Illuminate\Http\JsonResponse + */ + public function show($id) + { + return $this->errorResponse(404); + } + + /** + * Create a new resource. + * + * @param \Illuminate\Http\Request $request The API request. + * + * @return \Illuminate\Http\JsonResponse The response + */ + public function store(Request $request) + { + return $this->errorResponse(404); + } + + /** + * Update a resource. + * + * @param \Illuminate\Http\Request $request The API request. + * @param string $id Resource identifier + * + * @return \Illuminate\Http\JsonResponse The response + */ + public function update(Request $request, $id) + { + return $this->errorResponse(404); + } +} diff --git a/src/routes/api.php b/src/routes/api.php --- a/src/routes/api.php +++ b/src/routes/api.php @@ -68,34 +68,34 @@ Route::get('auth-attempts/{id}/details', 'API\V4\AuthAttemptsController@details'); Route::get('auth-attempts', 'API\V4\AuthAttemptsController@index'); - Route::apiResource('domains', API\V4\DomainsController::class); + Route::apiResource('domains', 'API\V4\DomainsController'); Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm'); Route::get('domains/{id}/skus', 'API\V4\SkusController@domainSkus'); Route::get('domains/{id}/status', 'API\V4\DomainsController@status'); Route::post('domains/{id}/config', 'API\V4\DomainsController@setConfig'); - Route::apiResource('groups', API\V4\GroupsController::class); + Route::apiResource('groups', 'API\V4\GroupsController'); Route::get('groups/{id}/status', 'API\V4\GroupsController@status'); Route::post('groups/{id}/config', 'API\V4\GroupsController@setConfig'); - Route::apiResource('packages', API\V4\PackagesController::class); + Route::apiResource('packages', 'API\V4\PackagesController'); - Route::apiResource('resources', API\V4\ResourcesController::class); + Route::apiResource('resources', 'API\V4\ResourcesController'); Route::get('resources/{id}/status', 'API\V4\ResourcesController@status'); Route::post('resources/{id}/config', 'API\V4\ResourcesController@setConfig'); - Route::apiResource('shared-folders', API\V4\SharedFoldersController::class); + Route::apiResource('shared-folders', 'API\V4\SharedFoldersController'); Route::get('shared-folders/{id}/status', 'API\V4\SharedFoldersController@status'); Route::post('shared-folders/{id}/config', 'API\V4\SharedFoldersController@setConfig'); - Route::apiResource('skus', API\V4\SkusController::class); + Route::apiResource('skus', 'API\V4\SkusController'); - Route::apiResource('users', API\V4\UsersController::class); + Route::apiResource('users', 'API\V4\UsersController'); Route::post('users/{id}/config', 'API\V4\UsersController@setConfig'); Route::get('users/{id}/skus', 'API\V4\SkusController@userSkus'); Route::get('users/{id}/status', 'API\V4\UsersController@status'); - Route::apiResource('wallets', API\V4\WalletsController::class); + Route::apiResource('wallets', 'API\V4\WalletsController'); Route::get('wallets/{id}/transactions', 'API\V4\WalletsController@transactions'); Route::get('wallets/{id}/receipts', 'API\V4\WalletsController@receipts'); Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\WalletsController@receiptDownload'); @@ -184,26 +184,26 @@ 'prefix' => $prefix . 'api/v4', ], function () { - Route::apiResource('domains', API\V4\Admin\DomainsController::class); + Route::apiResource('domains', 'API\V4\Admin\DomainsController'); Route::get('domains/{id}/skus', 'API\V4\Admin\SkusController@domainSkus'); Route::post('domains/{id}/suspend', 'API\V4\Admin\DomainsController@suspend'); Route::post('domains/{id}/unsuspend', 'API\V4\Admin\DomainsController@unsuspend'); - Route::apiResource('groups', API\V4\Admin\GroupsController::class); + Route::apiResource('groups', 'API\V4\Admin\GroupsController'); Route::post('groups/{id}/suspend', 'API\V4\Admin\GroupsController@suspend'); Route::post('groups/{id}/unsuspend', 'API\V4\Admin\GroupsController@unsuspend'); - Route::apiResource('resources', API\V4\Admin\ResourcesController::class); - Route::apiResource('shared-folders', API\V4\Admin\SharedFoldersController::class); - Route::apiResource('skus', API\V4\Admin\SkusController::class); - Route::apiResource('users', API\V4\Admin\UsersController::class); + Route::apiResource('resources', 'API\V4\Admin\ResourcesController'); + Route::apiResource('shared-folders', 'API\V4\Admin\SharedFoldersController'); + Route::apiResource('skus', 'API\V4\Admin\SkusController'); + Route::apiResource('users', 'API\V4\Admin\UsersController'); 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); + Route::apiResource('wallets', 'API\V4\Admin\WalletsController'); Route::post('wallets/{id}/one-off', 'API\V4\Admin\WalletsController@oneOff'); Route::get('wallets/{id}/transactions', 'API\V4\Admin\WalletsController@transactions'); @@ -220,16 +220,16 @@ 'prefix' => $prefix . 'api/v4', ], function () { - Route::apiResource('domains', API\V4\Reseller\DomainsController::class); + Route::apiResource('domains', 'API\V4\Reseller\DomainsController'); Route::get('domains/{id}/skus', 'API\V4\Reseller\SkusController@domainSkus'); Route::post('domains/{id}/suspend', 'API\V4\Reseller\DomainsController@suspend'); Route::post('domains/{id}/unsuspend', 'API\V4\Reseller\DomainsController@unsuspend'); - Route::apiResource('groups', API\V4\Reseller\GroupsController::class); + Route::apiResource('groups', 'API\V4\Reseller\GroupsController'); Route::post('groups/{id}/suspend', 'API\V4\Reseller\GroupsController@suspend'); Route::post('groups/{id}/unsuspend', 'API\V4\Reseller\GroupsController@unsuspend'); - Route::apiResource('invitations', API\V4\Reseller\InvitationsController::class); + Route::apiResource('invitations', 'API\V4\Reseller\InvitationsController'); Route::post('invitations/{id}/resend', 'API\V4\Reseller\InvitationsController@resend'); Route::post('payments', 'API\V4\Reseller\PaymentsController@store'); @@ -241,17 +241,17 @@ Route::get('payments/pending', 'API\V4\Reseller\PaymentsController@payments'); Route::get('payments/has-pending', 'API\V4\Reseller\PaymentsController@hasPayments'); - Route::apiResource('resources', API\V4\Reseller\ResourcesController::class); - Route::apiResource('shared-folders', API\V4\Reseller\SharedFoldersController::class); - Route::apiResource('skus', API\V4\Reseller\SkusController::class); - Route::apiResource('users', API\V4\Reseller\UsersController::class); + Route::apiResource('resources', 'API\V4\Reseller\ResourcesController'); + Route::apiResource('shared-folders', 'API\V4\Reseller\SharedFoldersController'); + Route::apiResource('skus', 'API\V4\Reseller\SkusController'); + Route::apiResource('users', 'API\V4\Reseller\UsersController'); 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); + Route::apiResource('wallets', 'API\V4\Reseller\WalletsController'); Route::post('wallets/{id}/one-off', 'API\V4\Reseller\WalletsController@oneOff'); Route::get('wallets/{id}/receipts', 'API\V4\Reseller\WalletsController@receipts'); Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\Reseller\WalletsController@receiptDownload');