Rename controller namespaces: drop V1 prefix, clarify roles

V1/Admin     -> Company       (company-scoped controllers)
V1/SuperAdmin -> Admin        (platform-wide admin controllers)
V1/Customer  -> CustomerPortal (customer-facing portal)
V1/Installation -> Setup      (installation wizard)
V1/PDF       -> Pdf           (consistent casing)
V1/Modules   -> Modules       (drop V1 prefix)
V1/Webhook   -> Webhook       (drop V1 prefix)

The V1 prefix served no purpose - API versioning is in the route prefix
(/api/v1/), not the controller namespace. "Admin" was misleading for
company-scoped controllers. "SuperAdmin" is now simply "Admin" for
platform administration.
This commit is contained in:
Darko Gjorgjijoski
2026-04-03 19:15:20 +02:00
parent 0aaf0419c3
commit 64c481e963
129 changed files with 236 additions and 236 deletions

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Password;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
public function broker()
{
return Password::broker('customers');
}
/**
* Get the response for a successful password reset link.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
return response()->json([
'message' => 'Password reset email sent.',
'data' => $response,
]);
}
/**
* Get the response for a failed password reset link.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return response('Email could not be sent to this email address.', 403);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Customer\CustomerLoginRequest;
use App\Models\Company;
use App\Models\Customer;
use Hash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(CustomerLoginRequest $request, Company $company)
{
$user = Customer::where('email', $request->email)
->where('company_id', $company->id)
->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
if (! $user->enable_portal) {
throw ValidationException::withMessages([
'email' => ['Customer portal not available for this user.'],
]);
}
Auth::guard('customer')->login($user);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Contracts\Auth\CanResetPassword;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Password;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::CUSTOMER_HOME;
public function broker()
{
return Password::broker('customers');
}
/**
* Get the response for a successful password reset.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetResponse(Request $request, $response)
{
return response()->json([
'message' => 'Password reset successfully.',
]);
}
/**
* Reset the given user's password.
*
* @param CanResetPassword $user
* @param string $password
* @return void
*/
protected function resetPassword($user, $password)
{
$user->password = $password;
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
}
/**
* Get the response for a failed password reset.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
return response('Failed, Invalid Token.', 403);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Estimate;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\EstimateResource;
use App\Models\Company;
use App\Models\Estimate;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class AcceptEstimateController extends Controller
{
/**
* Handle the incoming request.
*
* @param Estimate $estimate
* @return Response
*/
public function __invoke(Request $request, Company $company, $id)
{
$estimate = $company->estimates()
->whereCustomer(Auth::guard('customer')->id())
->where('id', $id)
->first();
if (! $estimate) {
return response()->json(['error' => 'estimate_not_found'], 404);
}
$estimate->update($request->only('status'));
return new EstimateResource($estimate);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Estimate;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\EstimateResource;
use App\Models\Company;
use App\Models\Estimate;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class EstimatesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$limit = $request->has('limit') ? $request->limit : 10;
$estimates = Estimate::with([
'items',
'customer',
'taxes',
'creator',
])
->where('status', '<>', 'DRAFT')
->whereCustomer(Auth::guard('customer')->id())
->applyFilters($request->only([
'status',
'estimate_number',
'from_date',
'to_date',
'orderByField',
'orderBy',
]))
->latest()
->paginateData($limit);
return EstimateResource::collection($estimates)
->additional(['meta' => [
'estimateTotalCount' => Estimate::where('status', '<>', 'DRAFT')->whereCustomer(Auth::guard('customer')->id())->count(),
]]);
}
/**
* Display the specified resource.
*
* @param Estimate $estimate
* @return Response
*/
public function show(Company $company, $id)
{
$estimate = $company->estimates()
->whereCustomer(Auth::guard('customer')->id())
->where('id', $id)
->first();
if (! $estimate) {
return response()->json(['error' => 'estimate_not_found'], 404);
}
return new EstimateResource($estimate);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\CustomerPortal;
use App\Http\Controllers\Controller;
use App\Http\Resources\EstimateResource;
use App\Mail\EstimateViewedMail;
use App\Models\CompanySetting;
use App\Models\Customer;
use App\Models\EmailLog;
use App\Models\Estimate;
use Illuminate\Http\Request;
class EstimatePdfController extends Controller
{
public function getPdf(EmailLog $emailLog, Request $request)
{
$estimate = Estimate::find($emailLog->mailable_id);
if (! $emailLog->isExpired()) {
if ($estimate && ($estimate->status == Estimate::STATUS_SENT || $estimate->status == Estimate::STATUS_DRAFT)) {
$estimate->status = Estimate::STATUS_VIEWED;
$estimate->save();
$notifyEstimateViewed = CompanySetting::getSetting(
'notify_estimate_viewed',
$estimate->company_id
);
if ($notifyEstimateViewed == 'YES') {
$data['estimate'] = Estimate::findOrFail($estimate->id)->toArray();
$data['user'] = Customer::find($estimate->customer_id)->toArray();
$notificationEmail = CompanySetting::getSetting(
'notification_email',
$estimate->company_id
);
\Mail::to($notificationEmail)->send(new EstimateViewedMail($data));
}
}
return $estimate->getGeneratedPDFOrStream('estimate');
}
abort(403, 'Link Expired.');
}
public function getEstimate(EmailLog $emailLog)
{
$estimate = Estimate::find($emailLog->mailable_id);
return new EstimateResource($estimate);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Expense;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\ExpenseResource;
use App\Models\Company;
use App\Models\Expense;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class ExpensesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$limit = $request->has('limit') ? $request->limit : 10;
$expenses = Expense::with('category', 'creator', 'fields')
->whereUser(Auth::guard('customer')->id())
->applyFilters($request->only([
'expense_category_id',
'from_date',
'to_date',
'orderByField',
'orderBy',
]))
->paginateData($limit);
return ExpenseResource::collection($expenses)
->additional(['meta' => [
'expenseTotalCount' => Expense::whereCustomer(Auth::guard('customer')->id())->count(),
]]);
}
/**
* Display the specified resource.
*
* @param Expense $expense
* @return Response
*/
public function show(Company $company, $id)
{
$expense = $company->expenses()
->whereUser(Auth::guard('customer')->id())
->where('id', $id)
->first();
if (! $expense) {
return response()->json(['error' => 'expense_not_found'], 404);
}
return new ExpenseResource($expense);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\CustomerPortal\General;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\CustomerResource;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Module;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class BootstrapController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
$customer = Auth::guard('customer')->user();
foreach (\Menu::get('customer_portal_menu')->items->toArray() as $data) {
if ($customer) {
$menu[] = [
'title' => $data->title,
'link' => $data->link->path['url'],
];
}
}
return (new CustomerResource($customer))
->additional(['meta' => [
'menu' => $menu,
'current_customer_currency' => Currency::find($customer->currency_id),
'modules' => Module::where('enabled', true)->pluck('name'),
'current_company_language' => CompanySetting::getSetting('language', $customer->company_id),
]]);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\CustomerPortal\General;
use App\Http\Controllers\Controller;
use App\Models\Estimate;
use App\Models\Invoice;
use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class DashboardController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
$user = Auth::guard('customer')->user();
$amountDue = Invoice::whereCustomer($user->id)
->where('status', '<>', 'DRAFT')
->sum('due_amount');
$invoiceCount = Invoice::whereCustomer($user->id)
->where('status', '<>', 'DRAFT')
->count();
$estimatesCount = Estimate::whereCustomer($user->id)
->where('status', '<>', 'DRAFT')
->count();
$paymentCount = Payment::whereCustomer($user->id)
->count();
return response()->json([
'due_amount' => $amountDue,
'recentInvoices' => Invoice::whereCustomer($user->id)->where('status', '<>', 'DRAFT')->take(5)->latest()->get(),
'recentEstimates' => Estimate::whereCustomer($user->id)->where('status', '<>', 'DRAFT')->take(5)->latest()->get(),
'invoice_count' => $invoiceCount,
'estimate_count' => $estimatesCount,
'payment_count' => $paymentCount,
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\CustomerPortal\General;
use App\Http\Controllers\Controller;
use App\Http\Requests\Customer\CustomerProfileRequest;
use App\Http\Resources\Customer\CustomerResource;
use App\Models\Company;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ProfileController extends Controller
{
public function updateProfile(Company $company, CustomerProfileRequest $request)
{
$customer = Auth::guard('customer')->user();
$customer->update($request->validated());
if (isset($request->is_customer_avatar_removed) && (bool) $request->is_customer_avatar_removed) {
$customer->clearMediaCollection('customer_avatar');
}
if ($customer && $request->hasFile('customer_avatar')) {
$customer->clearMediaCollection('customer_avatar');
$customer->addMediaFromRequest('customer_avatar')
->toMediaCollection('customer_avatar');
}
if ($request->billing !== null) {
$customer->shippingAddress()->delete();
$customer->addresses()->create($request->getShippingAddress());
}
if ($request->shipping !== null) {
$customer->billingAddress()->delete();
$customer->addresses()->create($request->getBillingAddress());
}
return new CustomerResource($customer);
}
public function getUser(Request $request)
{
$customer = Auth::guard('customer')->user();
return new CustomerResource($customer);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Invoice;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\InvoiceResource;
use App\Models\Company;
use App\Models\Invoice;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class InvoicesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$limit = $request->has('limit') ? $request->limit : 10;
$invoices = Invoice::with(['items', 'customer', 'creator', 'taxes'])
->where('status', '<>', 'DRAFT')
->applyFilters($request->all())
->whereCustomer(Auth::guard('customer')->id())
->latest()
->paginateData($limit);
return InvoiceResource::collection($invoices)
->additional(['meta' => [
'invoiceTotalCount' => Invoice::where('status', '<>', 'DRAFT')->whereCustomer(Auth::guard('customer')->id())->count(),
]]);
}
public function show(Company $company, $id)
{
$invoice = $company->invoices()
->whereCustomer(Auth::guard('customer')->id())
->where('id', $id)
->first();
if (! $invoice) {
return response()->json(['error' => 'invoice_not_found'], 404);
}
return new InvoiceResource($invoice);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\CustomerPortal;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\InvoiceResource as CustomerInvoiceResource;
use App\Mail\InvoiceViewedMail;
use App\Models\CompanySetting;
use App\Models\Customer;
use App\Models\EmailLog;
use App\Models\Invoice;
use Illuminate\Http\Request;
class InvoicePdfController extends Controller
{
public function getPdf(EmailLog $emailLog, Request $request)
{
$invoice = Invoice::find($emailLog->mailable_id);
if (! $emailLog->isExpired()) {
if ($invoice && ($invoice->status == Invoice::STATUS_SENT || $invoice->status == Invoice::STATUS_DRAFT)) {
$invoice->status = Invoice::STATUS_VIEWED;
$invoice->viewed = true;
$invoice->save();
$notifyInvoiceViewed = CompanySetting::getSetting(
'notify_invoice_viewed',
$invoice->company_id
);
if ($notifyInvoiceViewed == 'YES') {
$data['invoice'] = Invoice::findOrFail($invoice->id)->toArray();
$data['user'] = Customer::find($invoice->customer_id)->toArray();
$notificationEmail = CompanySetting::getSetting(
'notification_email',
$invoice->company_id
);
\Mail::to($notificationEmail)->send(new InvoiceViewedMail($data));
}
}
if ($request->has('pdf')) {
return $invoice->getGeneratedPDFOrStream('invoice');
}
return view('app')->with([
'customer_logo' => get_company_setting('customer_portal_logo', $invoice->company_id),
'current_theme' => get_company_setting('customer_portal_theme', $invoice->company_id),
]);
}
abort(403, 'Link Expired.');
}
public function getInvoice(EmailLog $emailLog)
{
$invoice = Invoice::find($emailLog->mailable_id);
return new CustomerInvoiceResource($invoice);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Payment;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\PaymentMethodResource;
use App\Models\Company;
use App\Models\PaymentMethod;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class PaymentMethodController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request, Company $company)
{
return PaymentMethodResource::collection(PaymentMethod::where('company_id', $company->id)->get());
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\CustomerPortal\Payment;
use App\Http\Controllers\Controller;
use App\Http\Resources\Customer\PaymentResource;
use App\Models\Company;
use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class PaymentsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$limit = $request->has('limit') ? $request->limit : 10;
$payments = Payment::with(['customer', 'invoice', 'paymentMethod', 'creator'])
->whereCustomer(Auth::guard('customer')->id())
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
->applyFilters($request->only([
'payment_number',
'payment_method_id',
'orderByField',
'orderBy',
]))
->select('payments.*', 'invoices.invoice_number')
->latest()
->paginateData($limit);
return PaymentResource::collection($payments)
->additional(['meta' => [
'paymentTotalCount' => Payment::whereCustomer(Auth::guard('customer')->id())->count(),
]]);
}
/**
* Display the specified resource.
*
* @param Payment $payment
* @return Response
*/
public function show(Company $company, $id)
{
$payment = $company->payments()
->whereCustomer(Auth::guard('customer')->id())
->where('id', $id)
->first();
if (! $payment) {
return response()->json(['error' => 'payment_not_found'], 404);
}
return new PaymentResource($payment);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\CustomerPortal;
use App\Http\Controllers\Controller;
use App\Http\Resources\PaymentResource;
use App\Models\EmailLog;
use App\Models\Payment;
use Illuminate\Http\Request;
class PaymentPdfController extends Controller
{
public function getPdf(EmailLog $emailLog, Request $request)
{
if (! $emailLog->isExpired()) {
return $emailLog->mailable->getGeneratedPDFOrStream('payment');
}
abort(403, 'Link Expired.');
}
public function getPayment(EmailLog $emailLog)
{
$payment = Payment::find($emailLog->mailable_id);
return new PaymentResource($payment);
}
}