From bc213966ff6833af5bcfb4510d052710d36214f0 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Thu, 20 Aug 2026 23:48:22 +0200 Subject: [PATCH] refactor(accounts): rewrite legacy-era regions of the provider, admin companies, profile controller and routes in place --- .../Accounts/AccountsServiceProvider.php | 20 ++++++++---- .../Controllers/Admin/CompaniesController.php | 16 +++++----- .../Company/UserProfileController.php | 32 +++++++++++-------- app/Domains/Accounts/routes/company.php | 13 +++++--- app/Domains/Accounts/routes/management.php | 4 +-- app/Domains/Accounts/routes/public.php | 4 +-- 6 files changed, 53 insertions(+), 36 deletions(-) diff --git a/app/Domains/Accounts/AccountsServiceProvider.php b/app/Domains/Accounts/AccountsServiceProvider.php index 3f7a6b69..81aa1e51 100644 --- a/app/Domains/Accounts/AccountsServiceProvider.php +++ b/app/Domains/Accounts/AccountsServiceProvider.php @@ -46,11 +46,19 @@ class AccountsServiceProvider extends ServiceProvider Gate::policy(User::class, UserPolicy::class); Gate::policy(Role::class, RolePolicy::class); - Gate::define('create company', [CompanyPolicy::class, 'create']); - Gate::define('transfer company ownership', [CompanyPolicy::class, 'transferOwnership']); - Gate::define('delete company', [CompanyPolicy::class, 'delete']); - Gate::define('manage company', [SettingsPolicy::class, 'manageCompany']); - Gate::define('delete multiple users', [UserPolicy::class, 'deleteMultiple']); - Gate::define('owner only', [OwnerPolicy::class, 'managedByOwner']); + // Ability names the controllers authorize against, each one routed to + // the policy method that decides it. Registered in this order. + $gates = [ + 'create company' => [CompanyPolicy::class, 'create'], + 'transfer company ownership' => [CompanyPolicy::class, 'transferOwnership'], + 'delete company' => [CompanyPolicy::class, 'delete'], + 'manage company' => [SettingsPolicy::class, 'manageCompany'], + 'delete multiple users' => [UserPolicy::class, 'deleteMultiple'], + 'owner only' => [OwnerPolicy::class, 'managedByOwner'], + ]; + + foreach ($gates as $ability => $handler) { + Gate::define($ability, $handler); + } } } diff --git a/app/Domains/Accounts/Http/Controllers/Admin/CompaniesController.php b/app/Domains/Accounts/Http/Controllers/Admin/CompaniesController.php index a66c4c39..1f9e5d2b 100644 --- a/app/Domains/Accounts/Http/Controllers/Admin/CompaniesController.php +++ b/app/Domains/Accounts/Http/Controllers/Admin/CompaniesController.php @@ -68,8 +68,7 @@ class CompaniesController extends Controller $this->authorize('create company'); $user = $request->user(); - - $company = Company::create($request->getCompanyPayload()); + $company = Company::query()->create($request->getCompanyPayload()); $company->unique_hash = Hashids::connection(HashidConnection::Company->value)->encode($company->id); $company->save(); $this->companyService->setupDefaults($company, (int) $request->validated('currency')); @@ -87,12 +86,15 @@ class CompaniesController extends Controller public function destroy(Request $request) { - $company = Company::find($request->header('company')); + $company = Company::query()->find($request->header('company')); $this->authorize('delete company', $company); - if ($request->name !== $company->name) { - return respondJson('company_name_must_match_with_given_name', 'Company name must match with given name'); + if ($company->name !== $request->input('name')) { + return respondJson( + 'company_name_must_match_with_given_name', + 'Company name must match with given name' + ); } $this->companyService->delete($company); @@ -104,8 +106,6 @@ class CompaniesController extends Controller public function userCompanies(Request $request) { - $companies = $request->user()->companies; - - return CompanyResource::collection($companies); + return CompanyResource::collection($request->user()->companies); } } diff --git a/app/Domains/Accounts/Http/Controllers/Company/UserProfileController.php b/app/Domains/Accounts/Http/Controllers/Company/UserProfileController.php index fe8f7f53..d8b6d4bf 100644 --- a/app/Domains/Accounts/Http/Controllers/Company/UserProfileController.php +++ b/app/Domains/Accounts/Http/Controllers/Company/UserProfileController.php @@ -25,18 +25,27 @@ class UserProfileController extends Controller public function update(ProfileRequest $request) { - $user = $request->user(); + $account = $request->user(); - $user->update($request->validated()); + $account->update($request->validated()); - return new UserResource($user); + return new UserResource($account); } + /** + * Attach, swap or drop the caller's profile picture. + * + * Three things can arrive on one call and each is handled in turn, so a + * payload carrying both a removal flag and a picture ends up with the + * picture, and a payload carrying both transports ends up with whatever + * came in as base64. Only the two picture branches check that somebody is + * actually signed in; the removal branch does not. Kept as it stands. + */ public function uploadAvatar(AvatarRequest $request) { - $user = auth()->user(); + $user = $request->user(); - if (isset($request->is_admin_avatar_removed) && (bool) $request->is_admin_avatar_removed) { + if ($request->is_admin_avatar_removed ?? false) { $this->userAvatarManager->clear($user); } if ($user && $request->hasFile('admin_avatar')) { @@ -48,8 +57,9 @@ class UserProfileController extends Controller ); } - if ($user && $request->has('avatar')) { - $data = json_decode($request->avatar); + if ($user !== null && $request->has('avatar')) { + $encoded = $request->avatar; + $data = json_decode($encoded); $this->userAvatarManager->replaceBase64($user, $data->data, $data->name); } @@ -65,12 +75,8 @@ class UserProfileController extends Controller public function updateSettings(UpdateSettingsRequest $request): JsonResponse { - $user = $request->user(); + $request->user()->setSettings($request->settings); - $user->setSettings($request->settings); - - return response()->json([ - 'success' => true, - ]); + return response()->json(['success' => true]); } } diff --git a/app/Domains/Accounts/routes/company.php b/app/Domains/Accounts/routes/company.php index 92e3be1c..62f49bfd 100644 --- a/app/Domains/Accounts/routes/company.php +++ b/app/Domains/Accounts/routes/company.php @@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Route; Route::get('/invitations/pending', [InvitationResponseController::class, 'pending']); Route::post('/invitations/{invitation:token}/accept', [InvitationResponseController::class, 'accept']); Route::post('/invitations/{invitation:token}/decline', [InvitationResponseController::class, 'decline']); -Route::get('/auth/check', [AuthController::class, 'check']); +Route::get('auth/check', [AuthController::class, 'check']); // is the caller's token still live? Route::apiResource('company-invitations', InvitationController::class)->only(['index', 'store', 'destroy']); @@ -23,11 +23,14 @@ Route::get('/me/settings', [UserProfileController::class, 'showSettings']); Route::put('/me/settings', [UserProfileController::class, 'updateSettings']); Route::post('/me/upload-avatar', [UserProfileController::class, 'uploadAvatar']); -Route::put('/company', [CompanyController::class, 'updateCompany']); -Route::post('/company/upload-logo', [CompanyController::class, 'uploadCompanyLogo']); +Route::controller(CompanyController::class)->group(function (): void { + // The company record itself; its settings are handled separately below. + Route::put('company', 'updateCompany'); + Route::post('company/upload-logo', 'uploadCompanyLogo'); +}); Route::get('/company/settings', [CompanySettingsController::class, 'show']); Route::post('/company/settings', [CompanySettingsController::class, 'update']); Route::get('/company/has-transactions', [CompanySettingsController::class, 'checkTransactions']); -Route::get('abilities', AbilitiesController::class); -Route::apiResource('roles', RolesController::class); +Route::get('abilities', AbilitiesController::class); // the fixed catalogue the role editor lists +Route::apiResources(['roles' => RolesController::class]); diff --git a/app/Domains/Accounts/routes/management.php b/app/Domains/Accounts/routes/management.php index 71ac4ff2..8748f6c7 100644 --- a/app/Domains/Accounts/routes/management.php +++ b/app/Domains/Accounts/routes/management.php @@ -5,9 +5,9 @@ use App\Domains\Accounts\Http\Controllers\Company\CompanySettingsController; use App\Domains\Accounts\Http\Controllers\Company\MembersController; use Illuminate\Support\Facades\Route; -Route::post('companies', [CompaniesController::class, 'store']); +Route::post('companies', [CompaniesController::class, 'store']); // gated on owning the active company Route::post('/transfer/ownership/{user}', [CompanySettingsController::class, 'transferOwnership']); -Route::post('companies/delete', [CompaniesController::class, 'destroy']); +Route::post('companies/delete', [CompaniesController::class, 'destroy']); // posted, and confirmed by name Route::get('companies', [CompaniesController::class, 'userCompanies']); Route::post('/members/delete', [MembersController::class, 'delete']); diff --git a/app/Domains/Accounts/routes/public.php b/app/Domains/Accounts/routes/public.php index aa5fb809..82eb194a 100644 --- a/app/Domains/Accounts/routes/public.php +++ b/app/Domains/Accounts/routes/public.php @@ -8,9 +8,9 @@ use Illuminate\Support\Facades\Route; Route::prefix('auth')->group(function (): void { Route::post('login', [AuthController::class, 'login']); - Route::post('logout', [AuthController::class, 'logout'])->middleware('auth:sanctum'); + Route::post('logout', [AuthController::class, 'logout'])->middleware(['auth:sanctum']); // needs the token it revokes Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->middleware('throttle:10,2'); - Route::post('reset/password', [ResetPasswordController::class, 'reset']); + Route::post('/reset/password', [ResetPasswordController::class, 'reset']); // the mailed token travels in the body }); Route::get('/invitations/{token}/details', [InvitationRegistrationController::class, 'details']);