Files

305 lines
9.6 KiB
PHP
Raw Permalink Normal View History

2026-03-15 17:08:23 +07:00
<?php
namespace Modules\Customer\Http\Controllers;
use App\Helper\RestaurantHelper;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Modules\Customer\Http\Requests\Auth\CustomerLoginRequest;
use Modules\Customer\Http\Requests\Auth\ProfileUpdateRequest;
use Modules\Customer\Models\OtpVerification;
use Modules\Restaurant\Models\Customer;
class CustomerAuthController extends Controller
{
use Authenticatable;
/**
* Step 1: Send OTP to phone (Create customer if not exists)
*/
public function customerLogin(CustomerLoginRequest $request)
{
$phone = $request->phone;
$restaurantId = $this->getRestaurantIdFromHeader($request);
if ($restaurantId instanceof JsonResponse) {
return $restaurantId;
}
// Check if customer already exists
$customer = Customer::where('restaurant_id', $restaurantId)
->where('phone', $phone)
->first();
// If customer not exists, create one
if (! $customer) {
$customer = Customer::create([
'name' => 'Customer '.substr($phone, -4),
'phone' => $phone,
'password' => Hash::make($phone), // temporary password
'restaurant_id' => $restaurantId,
]);
}
// Generate OTP
$otp = '1234'; // rand(100000, 999999);
$expiryTime = now()->addMinutes(5);
// Save or update OTP record
OtpVerification::updateOrCreate(
[
'restaurant_id' => $restaurantId,
'phone' => $phone,
],
[
'otp' => $otp,
'expires_at' => $expiryTime,
'updated_at' => now(),
]
);
// TODO: Send OTP via SMS Gateway (mock for now)
// Muthofun::send($phone, "Your OTP is: $otp");
return $this->ResponseSuccess([
'phone' => $phone,
'otp' => $otp, // ⚠️ remove in production
], 'OTP sent successfully.');
}
/**
* Step 2: Verify OTP and Login
*/
public function customerOtpVerify(Request $request)
{
$restaurantId = $this->getRestaurantIdFromHeader($request);
if ($restaurantId instanceof JsonResponse) {
return $restaurantId;
}
$request->validate([
'phone' => 'required|numeric|exists:customers,phone',
'otp' => 'required',
]);
$phone = $request->phone;
$otp = $request->otp;
// Fetch OTP record
$otpRecord = OtpVerification::where('restaurant_id', $restaurantId)
->where('phone', $phone)
->first();
if (! $otpRecord) {
return $this->ResponseError([], 'Invalid phone or OTP expired.', 400);
}
// Check expiry
if (now()->greaterThan($otpRecord->expires_at)) {
$otpRecord->update(['otp' => null]);
return $this->ResponseError([], 'OTP expired. Please request a new one.', 400);
}
// Check OTP validity
if ($otpRecord->otp != $otp) {
return $this->ResponseError([], 'Invalid OTP.', 400);
}
// OTP valid → Find customer
$customer = Customer::where('restaurant_id', $restaurantId)
->where('phone', $phone)
->first();
if (! $customer) {
return $this->ResponseError([], 'Customer not found.', 404);
}
// Clear OTP after success
$otpRecord->update(['otp' => null]);
// Create API token
$tokenResult = $customer->createToken('Customer Access Token');
return $this->ResponseSuccess([
'customer' => $customer,
'access_token' => $tokenResult->accessToken,
'expires_at' => $tokenResult->token->expires_at,
], 'Login successful.');
}
public function customerOtpSent(Request $request)
{
$restaurantId = $this->getRestaurantIdFromHeader($request);
if ($restaurantId instanceof JsonResponse) {
return $restaurantId;
}
$request->validate([
'phone' => 'required|numeric|exists:customers,phone',
]);
$phone = $request->phone;
// TODO
$expiryTime = now()->addMinutes(15); // OTP expiry = 2 minutes
$otp = rand(100000, 999999); // Generate random 6-digit OTP
// Check if an OTP record already exists for this phone
$otpRecord = OtpVerification::where('restaurant_id', $restaurantId)
->where('phone', $phone)
->first();
if ($otpRecord) {
// Check if existing OTP is still valid (not expired)
if (now()->lessThan($otpRecord->expires_at)) {
$remainingSeconds = now()->diffInSeconds($otpRecord->expires_at);
return $this->ResponseError([
'remaining_seconds' => $remainingSeconds,
], "An OTP has already been sent. Please wait {$remainingSeconds} seconds before requesting a new one.", 429);
}
// OTP expired → generate and resend a new one
$otpRecord->update([
'otp' => $otp,
'expires_at' => $expiryTime,
]);
} else {
// No record → create a new one
OtpVerification::create([
'phone' => $phone,
'otp' => $otp,
'expires_at' => $expiryTime,
]);
}
// TODO: Replace with your SMS sending logic
// Example: Muthofun::send($phone, "Your OTP is: $otp");
return $this->ResponseSuccess([
'phone' => $phone,
'otp' => $otp, // ⚠️ Remove in production
'expires_at' => $expiryTime->toDateTimeString(),
], 'OTP sent successfully.');
}
public function customerProfile(Request $request)
{
try {
$customer = Customer::where('id', Auth::id())->first();
if ($customer) {
$permissions = $customer->role?->permissions->pluck('name')->toArray() ?? []; // Convert permissions to an array
$customer = [
'id' => $customer->id,
'name' => $customer->first_name.' '.$customer->last_name,
'email' => $customer->email,
'phone' => $customer->phone,
'image' => $customer->avatar,
'role' => $customer->role?->name ?? 'no-role-assign',
'status' => $customer->status,
'permissions' => $permissions,
];
return $this->responseSuccess($customer, 'Profile fetched successfully.');
} else {
return $this->responseError([], 'Profile not found.');
}
} catch (Exception $e) {
return $this->responseError([], 'An error occurred while fetching the profile.');
}
}
public function customerUpdate(ProfileUpdateRequest $request)
{
$customer = Auth::guard('customer')->user();
// Get validated data
$data = $request->validated();
// Avatar upload (first time + update)
if ($request->hasFile('avatar')) {
$data['avatar'] = fileUploader(
'customers/',
'png',
$request->file('avatar'),
$customer->avatar // old image (null for first upload)
);
}
// Fill customer data
$customer->fill($data);
// Reset email verification if email changed
if ($customer->isDirty('email')) {
$customer->email_verified_at = null;
}
$customer->save();
return $this->responseSuccess(
$customer->fresh(),
'Profile Update Successfully Done.'
);
}
public function changePassword(Request $request)
{
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', 'confirmed'],
]);
$request->customer()->update([
'password' => Hash::make($validated['password']),
]);
return $this->ResponseSuccess([], 'Password Changes Successfully.');
}
public function accountDeleted(Request $request)
{
$request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
]);
$request->customer()->update([
'deleted_at' => Carbon::now(),
]);
return $this->ResponseSuccess([], 'Account Delete successfully.');
}
public function customerLogout(Request $request)
{
try {
$result = $request->customer()->token()->revoke();
if ($result) {
return $this->ResponseSuccess([], 'Logout Success');
}
} catch (Exception $e) {
return $this->ResponseSuccess([], 'Logout Failed');
}
}
private function getRestaurantIdFromHeader(Request $request)
{
$domain = $request->header('X-Domain'); // Custom header key (Change if needed)
if (! $domain) {
return $this->responseError([], 'Domain header is missing.', 400);
}
$restaurantResponse = RestaurantHelper::getRestaurantIdByDomain($domain);
if (! $restaurantResponse['status']) {
return $this->responseError([], $restaurantResponse['error'], 404);
}
return $restaurantResponse['restaurant_id'];
}
}