migrate to gtea from bistbucket

This commit is contained in:
2026-03-15 17:08:23 +07:00
commit 129ca2260c
3716 changed files with 566316 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Authentication\Models\User;
use Modules\HRM\Models\Attendance;
use Modules\HRM\Models\AttendanceLog;
class AttendanceSyncController extends Controller
{
public function allUsersGet(Request $request)
{
$request->validate([
'restaurant_id' => 'required|integer|exists:restaurants,id',
]);
$users = User::select('id', 'first_name', 'last_name', 'user_type', 'role_id')
->where('restaurant_id', (int) $request->restaurant_id)
->get();
return $this->responseSuccess(
$users,
_lang('Users have been fetched successfully.')
);
}
public function sync(Request $request)
{
$request->validate([
'logs' => ['required', 'array'],
'logs.*.id' => ['required', 'integer'],
'logs.*.timestamp' => ['required', 'date'],
'logs.*.type' => ['required', 'integer'],
]);
$restaurantId = 1; // or from device
$inserted = 0;
$skipped = 0;
DB::beginTransaction();
try {
foreach ($request->logs as $log) {
$employeeId = $log['id'];
$dateTime = Carbon::parse($log['timestamp']);
$date = $dateTime->toDateString();
$type = $this->mapPunchType($log['type']);
/**
* 1️⃣ Avoid duplicate punch
*/
$exists = AttendanceLog::where([
'employee_id' => $employeeId,
'restaurant_id' => $restaurantId,
'punch_time' => $dateTime,
'type' => $type,
])->exists();
if ($exists) {
$skipped++;
continue;
}
/**
* 2️⃣ Get or create daily attendance
*/
$attendance = Attendance::firstOrCreate(
[
'employee_id' => $employeeId,
'date' => $date,
],
[
'restaurant_id' => $restaurantId,
'status' => 'present',
]
);
/**
* 3️⃣ Insert punch log
*/
AttendanceLog::create([
'attendance_id' => $attendance->id,
'employee_id' => $employeeId,
'restaurant_id' => $restaurantId,
'type' => $type,
'punch_time' => $dateTime,
]);
/**
* 4️⃣ Recalculate attendance summary
*/
$this->recalculateWorkedHours($attendance);
$inserted++;
}
DB::commit();
return $this->responseSuccess([
'inserted' => $inserted,
'skipped' => $skipped,
'message' => 'Attendance synced successfully',
], 'Profile fetched successfully.');
} catch (\Throwable $e) {
DB::rollBack();
return $this->responseError([
'message' => 'Attendance sync failed',
'error' => $e->getMessage(),
], $e->getMessage());
}
}
private function mapPunchType(int $type): string
{
return in_array($type, [0, 4]) ? 'in' : 'out';
}
/**
* RECALCULATE TOTAL WORKED HOURS BASED ON ALL LOGS
*/
private function recalculateWorkedHours(Attendance $attendance)
{
$logs = AttendanceLog::where('attendance_id', $attendance->id)
->orderBy('punch_time')
->get();
if ($logs->count() < 2) {
return;
}
$first = Carbon::parse($logs->first()->punch_time);
$last = Carbon::parse($logs->last()->punch_time);
$hours = round($first->diffInMinutes($last) / 60, 2);
$attendance->update([
'first_clock_in' => $first,
'last_clock_out' => $last,
'hours_worked' => $hours,
]);
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\ResponseTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Modules\Authentication\Models\User;
class AuthController extends Controller
{
use ResponseTrait;
public function user(Request $request): JsonResponse
{
try {
$user = User::where('id', Auth::id())->first();
if ($user) {
$permissions = $user->role?->permissions->pluck('name')->toArray() ?? []; // Convert permissions to an array
$user = [
'id' => $user->id,
'name' => $user->first_name.' '.$user->last_name,
'email' => $user->email,
'phone' => $user->phone,
'image' => $user->avatar,
'role' => $user->role?->name ?? 'no-role-assign',
'status' => $user->status,
'restaurant_info' => $user?->restaurant,
'rider_info' => $user?->rider,
'permissions' => $permissions,
];
return $this->responseSuccess($user, '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 login(Request $request): JsonResponse
{
$user = User::where('phone', $request->phone)->with('rider')->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return $this->responseError([], 'The provided credentials are incorrect..', 403);
}
$accessToken = $user->createToken('app')->accessToken;
return $this->responseSuccess([
'user' => $user,
'access_token' => $accessToken,
'check' => 'Droplet Update',
]);
}
public function profileUpdate(Request $request): JsonResponse
{
try {
// ✅ Validate input
$request->validate([
'name' => 'nullable|string|max:255',
'email' => 'nullable|email|max:255',
'avatar' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg,webp|max:2048',
]);
$user = Auth::user();
$updateData = [];
// 🧠 Update name if provided
if ($request->filled('name')) {
$updateData['first_name'] = $request->input('name');
$updateData['last_name'] = null;
}
// 🧠 Update email if provided
if ($request->filled('email')) {
$updateData['email'] = $request->input('email');
}
// 🧹 Handle avatar upload using fileUploader()
if ($request->hasFile('avatar')) {
// 🔁 Delete old avatar if exists
if (
$user->avatar &&
Storage::disk('public')->exists('users/'.$user->avatar)
) {
Storage::disk('public')->delete('users/'.$user->avatar);
}
// 💾 Use custom uploader and store only the filename
$filename = fileUploader('users/', 'png', $request->file('avatar')); // should return filename only
$updateData['avatar'] = $filename;
}
// 💾 Perform update
if (! empty($updateData)) {
$user->update($updateData);
}
return $this->responseSuccess([
'name' => $user->first_name.$user->last_name,
'email' => $user->email,
'avatar' => $user->avatar, // ✅ manual full URL from filename
], 'Profile updated successfully.');
} catch (ValidationException $e) {
return $this->responseError($e->errors(), 'Validation failed.', 422);
} catch (Exception $e) {
return $this->responseError([], 'Something went wrong.', 500);
}
}
public function changePassword(Request $request): JsonResponse
{
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return $this->responseSuccess([], 'Password Changes Successfully Done.');
}
public function logout(Request $request)
{
try {
$result = $request->user()->token()->revoke();
if ($result) {
return $this->responseSuccess([], 'Logout Success');
}
} catch (Exception $e) {
return $this->responseSuccess([], 'Logout Failed');
}
}
}

View File

@@ -0,0 +1,253 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Mail\BillingInvoiceMail;
use App\Traits\Authenticatable;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Models\Subscription;
use Modules\Authentication\Models\SubscriptionItem;
class BillingController extends Controller
{
use Authenticatable;
public function expiringSoon(Request $request): JsonResponse
{
try {
$days = $request->get('days', 7); // default next 7 days
$query = Subscription::with([
'package:id,name',
'restaurant:id,name', // include restaurant info
'user:id,name,email',
])
->where('status', 1)
->whereNotNull('end_date')
->whereBetween('end_date', [
now()->startOfDay(),
now()->addDays($days)->endOfDay(),
]);
// Optional date range filter
if ($request->filled(['from_date', 'to_date'])) {
$query->whereBetween('end_date', [
Carbon::parse($request->from_date)->startOfDay(),
Carbon::parse($request->to_date)->endOfDay(),
]);
}
// Optional filtering by restaurant_id
if ($request->filled('restaurant_id')) {
$query->where('restaurant_id', $request->restaurant_id);
}
// Sorting
$sortBy = $request->get('sort_by', 'end_date');
$sortOrder = $request->get('sort_order', 'asc');
$subscriptions = $query->orderBy($sortBy, $sortOrder)
->paginate($request->get('per_page', 20));
return $this->responseSuccess($subscriptions, 'Expiring subscriptions fetched successfully.');
} catch (\Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function transactions(Request $request): JsonResponse
{
$query = SubscriptionItem::with(['subscription', 'package', 'user']);
// Optional Restaurant Filter
if ($request->filled('restaurant_id')) {
$query->where('restaurant_id', $request->restaurant_id);
}
// Status filter
if ($request->filled('status')) {
$query->where('status', $request->status);
}
// Date range
if ($request->filled(['from_date', 'to_date'])) {
$query->whereBetween('created_at', [
Carbon::parse($request->from_date)->startOfDay(),
Carbon::parse($request->to_date)->endOfDay(),
]);
}
// Sorting
$sortBy = $request->get('sort_by', 'created_at');
$sortOrder = $request->get('sort_order', 'desc');
$transactions = $query->orderBy($sortBy, $sortOrder)
->paginate($request->get('per_page', 20));
return $this->responseSuccess($transactions, 'Transactions list fetched successfully.');
}
public function invoices(Request $request): JsonResponse
{
$query = SubscriptionItem::with(['subscription.restaurant', 'package', 'user'])
->where('status', 1);
// Optional Restaurant Filter
if ($request->filled('restaurant_id')) {
$query->where('restaurant_id', $request->restaurant_id);
}
// Date range
if ($request->filled(['from_date', 'to_date'])) {
$query->whereBetween('created_at', [
Carbon::parse($request->from_date)->startOfDay(),
Carbon::parse($request->to_date)->endOfDay(),
]);
}
$invoices = $query
->orderBy($request->get('sort_by', 'created_at'), $request->get('sort_order', 'desc'))
->paginate($request->get('per_page', 20));
// Append Invoice Number
$invoices->getCollection()->transform(function ($item) {
$item->invoice_no = 'INV-'.str_pad($item->id, 6, '0', STR_PAD_LEFT);
return $item;
});
return $this->responseSuccess($invoices, 'Invoices list fetched successfully.');
}
public function revenueAnalytics(Request $request): JsonResponse
{
$baseQuery = SubscriptionItem::where('status', 1);
// Optional Restaurant Filter
if ($request->filled('restaurant_id')) {
$baseQuery->where('restaurant_id', $request->restaurant_id);
}
// Date range
if ($request->filled(['from_date', 'to_date'])) {
$baseQuery->whereBetween('created_at', [
Carbon::parse($request->from_date)->startOfDay(),
Carbon::parse($request->to_date)->endOfDay(),
]);
}
$totalRevenue = (clone $baseQuery)->sum('amount');
$totalTransactions = (clone $baseQuery)->count();
$monthlyRevenue = (clone $baseQuery)
->selectRaw('YEAR(created_at) year, MONTH(created_at) month, SUM(amount) total')
->groupBy('year', 'month')
->orderBy('year', 'desc')
->get();
$packageWise = (clone $baseQuery)
->selectRaw('package_id, SUM(amount) total')
->groupBy('package_id')
->with('package:id,name')
->get();
return $this->responseSuccess([
'total_revenue' => $totalRevenue,
'total_transactions' => $totalTransactions,
'monthly_revenue' => $monthlyRevenue,
'package_wise' => $packageWise,
], 'Revenue analytics fetched successfully.');
}
public function sendBillingNotification(Request $request): JsonResponse
{
$request->validate([
'restaurant_ids' => 'required|array',
'type' => 'required|in:email,sms,both',
'notification_for' => 'required|string',
]);
$restaurants = Restaurant::with(['subscriptions.package'])
->whereIn('id', $request->restaurant_ids)
->get();
foreach ($restaurants as $restaurant) {
$subscription = $restaurant->subscriptions()
->where('status', 1)
->latest()
->first();
if (! $subscription) {
continue;
}
$invoiceData = $this->buildInvoiceData($restaurant, $subscription);
$paymentUrl = $this->generatePaymentUrl($restaurant, $subscription);
$domainInfo = $this->getDomainInfo($restaurant);
/** EMAIL **/
if (in_array($request->type, ['email', 'both'])) {
Mail::to($restaurant->email)
->queue(new BillingInvoiceMail(
$restaurant,
$subscription,
$invoiceData,
$paymentUrl,
$domainInfo
));
}
/** SMS **/
if (in_array($request->type, ['sms', 'both'])) {
// TODO: Implement SMS sending logic here
// $this->sendSms(
// $restaurant->phone,
// $this->buildSmsMessage($restaurant, $subscription, $paymentUrl)
// );
}
}
return $this->responseSuccess([], 'Billing notification sent successfully');
}
private function buildInvoiceData($restaurant, $subscription)
{
return (object) [
'invoice_no' => 'INV-'.str_pad($subscription->id, 6, '0', STR_PAD_LEFT),
'amount' => $subscription->package->price,
'package_name' => $subscription->package->name,
'start_date' => $subscription->start_date,
'end_date' => $subscription->end_date,
'status' => 'Unpaid',
'month' => now()->format('Y-m'),
];
}
private function getDomainInfo($restaurant)
{
return [
'domain' => $restaurant->domain ?? null,
'domain_expiry' => $restaurant->domain_expiry ?? null,
];
}
private function generatePaymentUrl($restaurant, $subscription)
{
return config('app.frontend_url')
.'/billing/upgrade?restaurant_id='
.encrypt($restaurant->id);
}
private function buildSmsMessage($restaurant, $subscription, $paymentUrl)
{
return "Hello {$restaurant->name}, your subscription expires on "
.Carbon::parse($subscription->end_date)->format('d M Y')
.". Upgrade now: {$paymentUrl}";
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Modules\Authentication\Http\Requests\Settings\MailConfigUpdateRequest;
use Modules\Authentication\Http\Requests\Settings\SAASSettingUpdateRequest;
use Modules\Authentication\Models\Package;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Models\SAASSetting;
class DashboardController extends Controller
{
public function saasDashboardData(Request $request): JsonResponse
{
$year = (int) $request->year ?? now()->year;
$totalRestaurantsCount = Restaurant::count();
$totalPackages = Package::count();
$totalActiveRestaurant = Restaurant::where('status', 1)->count();
$totalInactiveRestaurant = Restaurant::where('status', 2)->count();
// Monthly transaction collection for current year
$monthlyData = DB::table('subscription_items')
->selectRaw('MONTH(created_at) as month, SUM(amount) as total_paid')
->whereYear('created_at', $year)
->whereNull('deleted_at')
->groupBy(DB::raw('MONTH(created_at)'))
->pluck('total_paid', 'month');
$monthlyReport = collect(range(1, 12))->map(function ($month) use ($monthlyData) {
return [
'month' => Carbon::create()->month($month)->format('F'),
'total_paid' => $monthlyData->get($month, 0),
];
});
// Total unique restaurants with active subscriptions
$totalRestaurants = DB::table('subscriptions')
->where('status', 'active')
->whereNull('deleted_at')
->distinct('restaurant_id')
->count('restaurant_id');
// Plan usage with percentages
$packageUsage = DB::table('subscriptions')
->join('packages', 'subscriptions.package_id', '=', 'packages.id')
->where('subscriptions.status', 'active')
->whereNull('subscriptions.deleted_at')
->select('packages.id as package_id', 'packages.name as plan_name', DB::raw('COUNT(DISTINCT subscriptions.restaurant_id) as restaurant_count'))
->groupBy('packages.id', 'packages.name')
->get()
->map(function ($item) use ($totalRestaurants) {
return [
'package_id' => $item->package_id,
'plan_name' => $item->plan_name,
'restaurant_count' => $item->restaurant_count,
'usage_percent' => $totalRestaurants > 0
? round(($item->restaurant_count / $totalRestaurants) * 100, 2)
: 0,
];
});
return $this->responseSuccess([
'total_restaurants' => $totalRestaurantsCount,
'total_packages' => $totalPackages,
'total_active_restaurant' => $totalActiveRestaurant,
'total_inactive_restaurant' => $totalInactiveRestaurant,
'transactions' => $monthlyReport,
'package_report' => $packageUsage,
], _lang('SaaS Dashboard data has been fetched successfully.'));
}
public function getSAASSettings(Request $request)
{
$saas_settings = DB::table('s_a_a_s_settings')->pluck('value', 'name')->toArray();
return $this->responseSuccess($saas_settings, _lang('SaaS settings fetched successfully.'));
}
public function saasSettingsUpdate(SAASSettingUpdateRequest $request): JsonResponse
{
try {
// If POST request, update or create settings
DB::beginTransaction();
foreach ($request->validated() as $key => $value) {
if ($key === '_token') {
continue;
}
SAASSetting::updateOrInsert(
[
'name' => $key,
],
[
'value' => $value,
'updated_at' => now(),
'created_at' => now(),
]
);
}
DB::commit();
return $this->responseSuccess([], _lang('SAAS Settings have been successfully updated.'));
} catch (\Exception $e) {
DB::rollBack();
return $this->responseError(['error' => $e->getMessage()], _lang('Failed to store records unsuccessfully.'));
}
}
public function getMailConfig()
{
$mailConfig = [
'MAIL_MAILER' => env('MAIL_MAILER'),
'MAIL_HOST' => env('MAIL_HOST'),
'MAIL_PORT' => env('MAIL_PORT'),
'MAIL_USERNAME' => env('MAIL_USERNAME'),
'MAIL_PASSWORD' => env('MAIL_PASSWORD') ? '********' : null, // mask password
'MAIL_FROM_ADDRESS' => env('MAIL_FROM_ADDRESS'),
'MAIL_FROM_NAME' => env('MAIL_FROM_NAME'),
];
return $this->responseSuccess(
$mailConfig,
_lang('Mail configuration fetched successfully.')
);
}
public function updateMailConfig(MailConfigUpdateRequest $request)
{
foreach ($request->validated() as $key => $value) {
setEnvValue($key, $value);
}
// Clear config cache so changes apply immediately
Artisan::call('config:clear');
Artisan::call('cache:clear');
return $this->responseSuccess(
[],
_lang('Mail configuration updated successfully.')
);
}
public function uploadLogo(Request $request): JsonResponse
{
$request->validate([
'logo' => 'required|image|mimes:jpeg,png,jpg|max:8192',
]);
$imageUrl = null;
if (isset($request['logo'])) {
if (isset($request['logo']) && $request['logo']->isValid()) {
$imageUrl = fileUploader('logos/', 'png', $request['logo']);
}
}
// Prepare data
$data = [
'name' => 'logo',
'value' => $imageUrl,
'updated_at' => Carbon::now(),
];
// Update or create logo setting
$saasSetting = SAASSetting::updateOrCreate(
[
'name' => 'logo',
],
$data
);
return $this->responseSuccess(
$saasSetting,
_lang('SAAS settings logo have been successfully updated')
);
}
public function getPackages(Request $request): JsonResponse
{
$perPage = $request->get('per_page', 10);
$getPackages = Package::paginate($perPage);
return $this->responseSuccess($getPackages, 'Packages fetched successfully.');
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Authentication\Models\Feedback;
use Modules\Authentication\Repositories\FeedbackRepository;
class FeedbackController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private FeedbackRepository $feedback) {}
public function getFeedbacks(Request $request): JsonResponse
{
$perPage = $request->get('per_page', 10);
$getFeedbacks = Feedback::with('user')->paginate($perPage);
return $this->responseSuccess($getFeedbacks, 'Feedbacks fetched successfully.');
}
public function index(): JsonResponse
{
try {
return $this->responseSuccess(
$this->feedback->getAll(request()->all()),
'Feedback has been fetched successfully.'
);
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(Request $request): JsonResponse
{
try {
return $this->responseSuccess(
$this->feedback->create($request->all()),
'Feedback has been created successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->feedback->getById($id),
'Feedback has been fetched successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function update(Request $request, int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->feedback->update($id, $this->getUpdateRequest($request)),
'Feedback has been updated successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->feedback->delete($id),
'Feedback has been deleted successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Modules\Authentication\Models\Gallery;
class GalleryController extends Controller
{
public function index()
{
$galleries = Gallery::all();
return $this->responseSuccess(
$galleries,
'Galleries has been fetched successfully.'
);
}
public function store(Request $request)
{
$request->validate([
'title' => 'nullable|string|max:255',
'image' => 'required',
]);
$image = $request->file('image');
$path = $image->store('gallery', 'public');
$gallery = Gallery::create([
'title' => $request->title,
'type' => $request->type,
'image' => asset('storage/'.$path),
]);
return response()->json($gallery, 201);
}
public function update(Request $request, $id)
{
$gallery = Gallery::where('id', $id)->first();
if (! $gallery) {
return $this->responseError([], 'Gallery Image Not Found');
}
$request->validate([
'title' => 'nullable|string|max:255',
'image' => 'nullable',
]);
if ($request->hasFile('image')) {
// Delete old image
$oldPath = str_replace(asset('storage/'), '', $gallery->image);
Storage::disk('public')->delete($oldPath);
// Store new image
$image = $request->file('image');
$path = $image->store('gallery', 'public');
$gallery->image = asset('storage/'.$path);
}
if ($request->has('title')) {
$gallery->title = $request->title;
$gallery->type = $request->type;
}
$gallery->save();
return $this->responseSuccess(
$gallery,
'Gallery image update successfully.'
);
}
public function destroy(int $id)
{
$gallery = Gallery::where('id', $id)->first();
if (! $gallery) {
return $this->responseError([], 'Gallery Image Not Found');
}
// Delete file from storage
$path = str_replace(asset('storage/'), '', $gallery->image);
Storage::disk('public')->delete($path);
// Delete DB record
$gallery->delete();
return $this->responseSuccess(
$gallery,
'Gallery image delete successfully.'
);
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Authentication\Models\Language;
class LanguageController extends Controller
{
/**
* List all languages or get a specific language by name
*/
public function index(Request $request): JsonResponse
{
try {
$name = $request->query('name');
if ($name) {
$lang = Language::where('name', $name)->first();
if (! $lang) {
return $this->responseError([], 'Language not found', 404);
}
$filePath = storage_path("app/public/{$lang->file_path}");
if (! file_exists($filePath)) {
return $this->responseError([], 'Language file not found', 404);
}
$json = json_decode(file_get_contents($filePath), true);
return $this->responseSuccess([
'name' => $lang->name,
'code' => $lang->code,
'flag_url' => $lang->flag ? asset("storage/flags/{$lang->flag}") : null,
'file_url' => $lang->file_path ? asset("storage/{$lang->file_path}") : null,
'data' => $json,
'is_default' => $lang->is_default,
'status' => $lang->status,
], 'Language fetched successfully.');
}
// Fetch all languages, default first
$languages = Language::orderByDesc('is_default')->get()->map(function ($lang) {
return [
'id' => $lang->id,
'name' => $lang->name,
'code' => $lang->code,
'flag_url' => $lang->flag ? asset("storage/flags/{$lang->flag}") : null,
'file_url' => $lang->file_path ? asset("storage/{$lang->file_path}") : null,
'is_default' => $lang->is_default,
'status' => $lang->status,
];
});
return $this->responseSuccess($languages, 'Languages fetched successfully.');
} catch (\Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Store a new language
*/
public function store(Request $request): JsonResponse
{
$request->validate([
'name' => 'required|string',
'code' => 'required|string|max:10|unique:languages,code',
'file' => 'required|file|mimes:json',
'flag' => 'nullable|image|mimes:png,jpg,jpeg,svg',
'is_default' => 'nullable|boolean',
]);
DB::beginTransaction();
try {
$fileName = $request->name.'.json';
$filePath = $request->file('file')->storeAs('languages', $fileName, 'public');
$flagPath = null;
if ($request->hasFile('flag')) {
$flagPath = fileUploader('flags/', 'png', $request->file('flag'));
}
$lang = Language::create([
'name' => $request->name,
'code' => $request->code,
'file_path' => $filePath,
'flag' => $flagPath,
'is_default' => $request->input('is_default', false),
'status' => 'active',
]);
DB::commit();
return $this->responseSuccess($lang, 'Language created successfully.');
} catch (\Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
/**
* Update language (name, code, file, flag, default)
*/
public function update(Request $request, int $id): JsonResponse
{
$lang = Language::findOrFail($id);
$request->validate([
'name' => 'sometimes|required|string',
'code' => 'sometimes|required|string|max:10|unique:languages,code,'.$lang->id,
'file' => 'sometimes|file|mimes:json',
'flag' => 'nullable|image|mimes:png,jpg,jpeg,svg|max:2048',
'is_default' => 'nullable|boolean',
]);
DB::beginTransaction();
try {
if ($request->has('name')) {
$lang->name = $request->name;
}
if ($request->has('code')) {
$lang->code = $request->code;
}
if ($request->hasFile('file')) {
$fileName = $lang->name.'.json';
$lang->file_path = $request->file('file')->storeAs('languages', $fileName, 'public');
}
if ($request->hasFile('flag')) {
$lang->flag = fileUploader('flags/', 'png', $request->file('flag'), $lang->flag);
}
if ($request->has('is_default') && $request->is_default) {
Language::where('is_default', true)->update(['is_default' => false]);
$lang->is_default = true;
}
$lang->save();
DB::commit();
return $this->responseSuccess($lang, 'Language updated successfully.');
} catch (\Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
/**
* Export JSON file
*/
public function export($id)
{
try {
$lang = Language::findOrFail($id);
$filePath = storage_path("app/public/{$lang->file_path}");
if (! file_exists($filePath)) {
return $this->responseError([], 'File not found', 404);
}
return response()->download($filePath, $lang->name.'.json');
} catch (\Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Set default language
*/
public function setDefault($id): JsonResponse
{
DB::beginTransaction();
try {
// Get the language to set as default
$lang = Language::findOrFail($id);
// Set all languages is_default = false
Language::where('is_default', true)->update(['is_default' => false]);
// Set selected language as default
$lang->is_default = true;
$lang->save();
DB::commit();
return $this->responseSuccess([], "$lang->name has been set as default.");
} catch (\Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
/**
* Delete language
*/
public function destroy(int $id): JsonResponse
{
try {
$lang = Language::findOrFail($id);
if ($lang->file_path && file_exists(storage_path("app/public/{$lang->file_path}"))) {
unlink(storage_path("app/public/{$lang->file_path}"));
}
if ($lang->flag && file_exists(storage_path("app/public/{$lang->flag}"))) {
unlink(storage_path("app/public/{$lang->flag}"));
}
$lang->delete();
return $this->responseSuccess([], 'Language deleted successfully.');
} catch (\Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function updateKey(Request $request, $name): JsonResponse
{
$request->validate([
'key' => 'required|string',
'value' => 'required|string',
]);
$lang = Language::where('name', $name)->firstOrFail();
$path = storage_path("app/public/{$lang->file_path}");
if (! file_exists($path)) {
return response()->json(['error' => 'Language file not found'], 404);
}
$json = json_decode(file_get_contents($path), true) ?? [];
$json[$request->key] = $request->value;
file_put_contents($path, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
return response()->json(['success' => true, 'message' => 'Key updated']);
}
public function import(Request $request, $name): JsonResponse
{
$request->validate([
'file' => 'required|file|mimes:json',
]);
$lang = Language::where('code', $name)->firstOrFail();
$fileName = $name.'.json';
$path = $request->file('file')->storeAs('languages', $fileName, 'public');
$lang->update(['file_path' => $path]);
return response()->json(['success' => true, 'message' => 'Language updated']);
}
}

View File

@@ -0,0 +1,365 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use App\Traits\ResponseTrait;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Modules\Authentication\Models\Package;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Models\Setting;
use Modules\Authentication\Models\Subscription;
use Modules\Authentication\Models\SubscriptionItem;
use Modules\Authentication\Models\User;
use Modules\Frontend\Models\Onboarding;
use Spatie\Permission\Models\Role;
class OnboardingsController extends Controller
{
use Authenticatable, ResponseTrait;
public function index(Request $request): JsonResponse
{
$query = Onboarding::query()
->whereNull('approved_by') // Only unapproved list
->orderBy('id', 'desc');
// 🔍 Search filter (restaurant name, email, phone)
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('restaurant_name', 'LIKE', "%{$search}%")
->orWhere('restaurant_email', 'LIKE', "%{$search}%")
->orWhere('restaurant_phone', 'LIKE', "%{$search}%")
->orWhere('name', 'LIKE', "%{$search}%") // Owner name
->orWhere('email', 'LIKE', "%{$search}%"); // Owner email
});
}
// 🎯 Status filter
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$onboardings = $query->get();
return $this->responseSuccess($onboardings);
}
public function approve(int $onboardingId): JsonResponse
{
// Start a transaction to ensure atomicity
DB::beginTransaction();
try {
// Fetch the data to approve
$onboarding = Onboarding::where('id', $onboardingId)->first();
if (! $onboarding) {
return $this->responseError([], 'No Data Found!', 404);
}
// Update the approval status and details
$onboarding->status = 'approved';
$onboarding->approved_by = $this->getCurrentUserId();
$onboarding->approved_at = Carbon::now();
$onboarding->save();
// Create the new restaurant
$restaurant = Restaurant::create([
'name' => $onboarding->restaurant_name,
'type' => $onboarding->restaurant_type,
'email' => $onboarding->restaurant_email,
'phone' => $onboarding->restaurant_phone,
'domain' => $onboarding->restaurant_domain,
'address' => $onboarding->restaurant_address,
'logo' => $onboarding->restaurant_logo,
]);
$defaultSettings = [
// 🏠 General Information
['type' => 'general', 'option_key' => 'restaurant_name', 'option_value' => 'Restaurant'],
['type' => 'general', 'option_key' => 'site_title', 'option_value' => 'restaurant'],
['type' => 'general', 'option_key' => 'phone', 'option_value' => '01XXXXXXXXX'],
['type' => 'general', 'option_key' => 'email', 'option_value' => 'restaurant@gmail.com'],
['type' => 'general', 'option_key' => 'language', 'option_value' => 'en'],
['type' => 'general', 'option_key' => 'google_map', 'option_value' => ''],
['type' => 'general', 'option_key' => 'address', 'option_value' => 'Asia,Dhaka-1219'],
['type' => 'general', 'option_key' => 'on_google_map', 'option_value' => ''],
['type' => 'general', 'option_key' => 'restaurant_code', 'option_value' => '987654'],
['type' => 'general', 'option_key' => 'currency_symbol', 'option_value' => '$'],
['type' => 'general', 'option_key' => 'logo', 'option_value' => 'logo.png'],
['type' => 'general', 'option_key' => 'mail_type', 'option_value' => 'mail'],
['type' => 'general', 'option_key' => 'disabled_website', 'option_value' => 'no'],
['type' => 'general', 'option_key' => 'copyright_text', 'option_value' => '&copy; Copyright 2025. All Rights Reserved by FueDevs LTD'],
['type' => 'general', 'option_key' => 'facebook_link', 'option_value' => 'https://www.facebook.com/'],
['type' => 'general', 'option_key' => 'google_plus_link', 'option_value' => 'https://www.google.com/'],
['type' => 'general', 'option_key' => 'youtube_link', 'option_value' => 'https://www.youtube.com/'],
['type' => 'general', 'option_key' => 'whats_app_link', 'option_value' => ''],
['type' => 'general', 'option_key' => 'twitter_link', 'option_value' => 'https://www.twitter.com'],
['type' => 'general', 'option_key' => 'eiin_code', 'option_value' => ''],
['type' => 'general', 'option_key' => 'sms_gateway', 'option_value' => 'twilio'],
['type' => 'general', 'option_key' => 'bulk_sms_api_key', 'option_value' => ''],
['type' => 'general', 'option_key' => 'bulk_sms_sender_id', 'option_value' => ''],
['type' => 'general', 'option_key' => 'twilio_sid', 'option_value' => ''],
['type' => 'general', 'option_key' => 'twilio_token', 'option_value' => ''],
['type' => 'general', 'option_key' => 'twilio_from_number', 'option_value' => ''],
['type' => 'general', 'option_key' => 'header_notice', 'option_value' => ''],
['type' => 'general', 'option_key' => 'app_version', 'option_value' => '1.0.0'],
['type' => 'general', 'option_key' => 'app_url', 'option_value' => 'drive-link'],
// 🎨 Restaurant Identity & Display
['type' => 'general', 'option_key' => 'tagline', 'option_value' => 'Delicious Food, Fresh Taste'],
['type' => 'general', 'option_key' => 'favicon', 'option_value' => 'favicon.png'],
['type' => 'general', 'option_key' => 'theme_color', 'option_value' => '#ff6b00'],
['type' => 'general', 'option_key' => 'background_image', 'option_value' => 'bg.jpg'],
// 💰 Finance / POS / Invoice
['type' => 'pos', 'option_key' => 'tax_type', 'option_value' => 'exclusive'],
['type' => 'pos', 'option_key' => 'tax_percentage', 'option_value' => '10'],
['type' => 'pos', 'option_key' => 'service_charge', 'option_value' => '5'],
['type' => 'pos', 'option_key' => 'default_currency', 'option_value' => 'USD'],
['type' => 'pos', 'option_key' => 'billing_prefix', 'option_value' => 'INV-'],
['type' => 'pos', 'option_key' => 'invoice_footer', 'option_value' => 'Thank you! Visit again.'],
['type' => 'pos', 'option_key' => 'enable_kitchen_print', 'option_value' => 'yes'],
['type' => 'pos', 'option_key' => 'enable_customer_copy', 'option_value' => 'yes'],
// 🚚 Online Ordering & Delivery
['type' => 'order', 'option_key' => 'enable_online_order', 'option_value' => 'yes'],
['type' => 'order', 'option_key' => 'delivery_charge', 'option_value' => '50'],
['type' => 'order', 'option_key' => 'minimum_order_amount', 'option_value' => '100'],
['type' => 'order', 'option_key' => 'auto_accept_order', 'option_value' => 'no'],
['type' => 'order', 'option_key' => 'estimated_preparation_time', 'option_value' => '30'],
// 🔔 Notifications / Integrations
['type' => 'integration', 'option_key' => 'slack_webhook_url', 'option_value' => ''],
['type' => 'integration', 'option_key' => 'telegram_bot_token', 'option_value' => ''],
['type' => 'integration', 'option_key' => 'telegram_chat_id', 'option_value' => ''],
['type' => 'integration', 'option_key' => 'twilio_sms_enabled', 'option_value' => 'yes'],
['type' => 'integration', 'option_key' => 'email_notifications', 'option_value' => 'yes'],
['type' => 'integration', 'option_key' => 'whatsapp_notifications', 'option_value' => 'no'],
// 🧾 Reports & Logs
['type' => 'system', 'option_key' => 'auto_backup', 'option_value' => 'daily'],
['type' => 'system', 'option_key' => 'report_timezone', 'option_value' => 'Asia/Dhaka'],
['type' => 'system', 'option_key' => 'data_retention_days', 'option_value' => '365'],
// 💻 UI/UX Preferences
['type' => 'ui', 'option_key' => 'sidebar_collapsed', 'option_value' => 'no'],
['type' => 'ui', 'option_key' => 'dark_mode', 'option_value' => 'no'],
['type' => 'ui', 'option_key' => 'default_dashboard', 'option_value' => 'sales'],
// 💳 Payment Gateways
['type' => 'payment', 'option_key' => 'razorpay_key', 'option_value' => ''],
['type' => 'payment', 'option_key' => 'razorpay_secret', 'option_value' => ''],
['type' => 'payment', 'option_key' => 'stripe_key', 'option_value' => ''],
['type' => 'payment', 'option_key' => 'stripe_secret', 'option_value' => ''],
['type' => 'payment', 'option_key' => 'cash_on_delivery', 'option_value' => 'yes'],
// 👨‍🍳 Kitchen & Staff
['type' => 'staff', 'option_key' => 'max_table_capacity', 'option_value' => '10'],
['type' => 'staff', 'option_key' => 'default_shift_start', 'option_value' => '09:00'],
['type' => 'staff', 'option_key' => 'default_shift_end', 'option_value' => '23:00'],
['type' => 'staff', 'option_key' => 'auto_logout_idle_minutes', 'option_value' => '60'],
// 🎨 Color Combination
['type' => 'system_color', 'option_key' => 'primary_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'secondary_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'primary_container_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'dark_primary_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'dark_secondary_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'dark_container_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'text_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'dark_text_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'sidebar_selected_bg_color', 'option_value' => null],
['type' => 'system_color', 'option_key' => 'sidebar_selected_text_color', 'option_value' => null],
// 🚚 Delivery / Online features
['type' => 'delivery', 'option_key' => 'is_online', 'option_value' => false],
['type' => 'delivery', 'option_key' => 'latitude', 'option_value' => '23.8103'],
['type' => 'delivery', 'option_key' => 'longitude', 'option_value' => '90.4125'],
['type' => 'delivery', 'option_key' => 'delivery_radius_km', 'option_value' => 5.00],
['type' => 'delivery', 'option_key' => 'delivery_fee', 'option_value' => 0],
['type' => 'delivery', 'option_key' => 'delivery_partner_count', 'option_value' => 0],
['type' => 'delivery', 'option_key' => 'delivery_time_avg', 'option_value' => 30],
['type' => 'delivery', 'option_key' => 'pickup_enabled', 'option_value' => true],
// 🕒 Operational hours & capacity
['type' => 'operational', 'option_key' => 'opening_time', 'option_value' => '09:00:00'],
['type' => 'operational', 'option_key' => 'closing_time', 'option_value' => '22:00:00'],
['type' => 'operational', 'option_key' => 'auto_accept_orders', 'option_value' => false],
['type' => 'operational', 'option_key' => 'pre_order_enabled', 'option_value' => false],
['type' => 'operational', 'option_key' => 'max_order_capacity', 'option_value' => 50],
// 📊 Analytics / reviews
['type' => 'analytics', 'option_key' => 'avg_rating', 'option_value' => 0.00],
['type' => 'analytics', 'option_key' => 'review_count', 'option_value' => 0],
['type' => 'analytics', 'option_key' => 'total_orders', 'option_value' => 0],
['type' => 'analytics', 'option_key' => 'last_order_time', 'option_value' => null],
['type' => 'analytics', 'option_key' => 'last_active_time', 'option_value' => null],
// 🎯 Marketing & social
['type' => 'marketing', 'option_key' => 'loyalty_points_enabled', 'option_value' => false],
['type' => 'marketing', 'option_key' => 'offers_enabled', 'option_value' => false],
['type' => 'marketing', 'option_key' => 'social_media_links', 'option_value' => json_encode([
'facebook' => '',
'instagram' => '',
'twitter' => '',
'linkedin' => '',
])],
// 💻 Future-proof / extra
['type' => 'system', 'option_key' => 'settings', 'option_value' => json_encode([])],
['type' => 'system', 'option_key' => 'uuid', 'option_value' => uniqid('rest_')],
// Email Config
['type' => 'email_config', 'option_key' => 'email_smtp_host', 'option_value' => null],
['type' => 'email_config', 'option_key' => 'email_smtp_port', 'option_value' => null],
['type' => 'email_config', 'option_key' => 'email_smtp_username', 'option_value' => null],
['type' => 'email_config', 'option_key' => 'email_smtp_password', 'option_value' => null],
['type' => 'email_config', 'option_key' => 'email_smtp_encryption', 'option_value' => null],
// SMS Config
['type' => 'sms_config', 'option_key' => 'twilio_api_key', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'twilio_api_secret', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'twilio_sender_id', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'twilio_api_url', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'twilio_is_default', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'nexmo_api_key', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'nexmo_api_secret', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'nexmo_sender_id', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'nexmo_api_url', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'nexmo_is_default', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'muthofun_api_key', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'smsglobal_api_key', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'smsglobal_api_secret', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'smsglobal_sender_id', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'smsglobal_api_url', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'smsglobal_extra_key', 'option_value' => null],
['type' => 'sms_config', 'option_key' => 'smsglobal_is_default', 'option_value' => null],
];
foreach ($defaultSettings as &$setting) {
$setting['setting_type'] = 'Restaurant';
$setting['restaurant_id'] = $restaurant->id;
$setting['created_at'] = now();
$setting['updated_at'] = now();
}
Setting::insert($defaultSettings);
// Create the associated user
$user = User::create([
'first_name' => $onboarding->name,
'email' => $onboarding->email,
'phone' => $onboarding->phone,
'password' => $onboarding->password,
'restaurant_id' => $restaurant->id,
'avatar' => $onboarding->avatar,
'role_id' => 2,
]);
$role = Role::where('id', 2)->first();
$user->assignRole($role);
// Commit the transaction
DB::commit();
// Respond with a success message
return response()->json([
'status' => true,
'message' => 'Restaurant approved and created successfully.',
'data' => [
'restaurant' => $restaurant,
'user' => $user,
],
]);
} catch (Exception $e) {
// Rollback in case of any failure
DB::rollBack();
return response()->json([
'status' => false,
'message' => 'An error occurred: '.$e->getMessage(),
], 500);
}
}
public function upgrade(Request $request): JsonResponse
{
$validated = $request->validate([
'restaurant_id' => 'required|exists:restaurants,id',
'package_id' => 'required|exists:packages,id',
'extra_days' => 'nullable|integer|min:0',
'payment_method' => 'required|string',
'amount_paid' => 'required|numeric|min:0',
'notes' => 'nullable|string',
]);
$restaurantId = $validated['restaurant_id'];
$package = Package::findOrFail($validated['package_id']);
$extraDays = $validated['extra_days'] ?? 0;
$startDate = Carbon::now();
$endDate = $startDate->copy()->addDays($package->duration_days + $extraDays);
// Fetch or create subscription for the restaurant
$subscription = Subscription::where('restaurant_id', $restaurantId)->first();
if ($subscription) {
// Update existing subscription
$subscription->update([
'package_id' => $package->id,
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
'transactions_details' => json_encode([
'invoice_no' => 'INV-'.strtoupper(Str::random(6)),
'issued_at' => now()->toDateString(),
'notes' => $validated['notes'] ?? 'Upgraded Package',
'amount' => $validated['amount_paid'],
]),
]);
} else {
// Fallback (shouldnt happen unless subscription was deleted)
$subscription = Subscription::create([
'restaurant_id' => $restaurantId,
'package_id' => $package->id,
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
'user_id' => Auth::id(),
'transactions_details' => json_encode([
'invoice_no' => 'INV-'.strtoupper(Str::random(6)),
'issued_at' => now()->toDateString(),
'notes' => $validated['notes'] ?? 'Initial Subscription',
'amount' => $validated['amount_paid'],
]),
]);
}
// Create new subscription item
SubscriptionItem::create([
'user_id' => Auth::id(),
'restaurant_id' => $subscription->restaurant_id,
'subscription_id' => $subscription->id,
'reference_no' => 'PAY-'.strtoupper(Str::random(8)),
'amount_paid' => $validated['amount_paid'],
'payment_method' => $validated['payment_method'],
]);
// Respond with a success message
return $this->responseSuccess([
'restaurantId' => $restaurantId,
'package' => $package,
'extraDays' => $extraDays,
'subscription' => $subscription,
], 'Subscription updated successfully.');
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\Package\PackageStoreRequest;
use App\Http\Requests\Package\PackageUpdateRequest;
use App\Services\Package\PackageService;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PackageController extends Controller
{
public function __construct(
private PackageService $service
) {}
public function index(Request $request): JsonResponse
{
$packages = $this->service->getAll($request);
return $this->responseSuccess($packages);
}
public function store(PackageStoreRequest $request)
{
DB::beginTransaction();
try {
$package = $this->service->create($request->validated());
DB::commit();
return $this->responseSuccess($package, 'Package has been create successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id)
{
$package = $this->service->getById($id);
return $this->responseSuccess($package, 'Package has been fetch successfully.');
}
public function update(PackageUpdateRequest $request, int $id)
{
DB::beginTransaction();
try {
// Get validated data
$data = $request->validated();
// List of all checkbox/boolean fields
$booleanFields = [
'sms_management_enabled',
'chat_enable',
'landing_page_enable',
'blog_enable',
'auto_renew',
];
// Set missing checkboxes to 0
foreach ($booleanFields as $field) {
if (! isset($data[$field])) {
$data[$field] = 0;
}
}
$package = $this->service->update($id, $data);
DB::commit();
return $this->responseSuccess($package, 'Package has been updated successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id)
{
DB::beginTransaction();
try {
$this->service->delete($id);
DB::commit();
return $this->responseSuccess([], 'Package has been delete successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Authentication\Services\PermissionService;
class PermissionController extends Controller
{
public function __construct(
private PermissionService $service
) {}
public function index(Request $request): JsonResponse
{
$perPage = request('per_page') ?? 10;
$permissions = $this->service->index($request, $perPage);
if (! $permissions) {
return $this->responseError([], 'No Data Found!', 404);
}
return $this->responseSuccess($permissions);
}
public function store(Request $request): JsonResponse
{
DB::beginTransaction();
try {
$this->service->create($request->validated());
DB::commit();
return $this->responseSuccess([]);
} catch (\Exception $e) {
DB::rollBack();
return $this->responseError([], 'Data Process Error!');
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Authentication\Http\Requests\Restaurant\RestaurantStoreRequest;
use Modules\Authentication\Http\Requests\Restaurant\RestaurantUpdateRequest;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Repositories\RestaurantRepository;
class RestaurantController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private RestaurantRepository $restaurant) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess(
$this->restaurant->getAll(request()->all()),
'Restaurant has been fetched successfully.'
);
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(RestaurantStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess(
$this->restaurant->create($request->all()),
'Restaurant has been created successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->restaurant->getById($id),
'Restaurant has been fetched successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function update(RestaurantUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->restaurant->update($id, $this->getUpdateRequest($request)),
'Restaurant has been updated successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function destroy(int $id): JsonResponse
{
try {
$restaurant = $this->restaurant->getById($id);
if (! $restaurant) {
return $this->responseError([], 'Restaurant Not Found');
}
if ($restaurant->logo) {
fileUploader('restaurant/', 'png', null, $restaurant->logo);
}
return $this->responseSuccess(
$this->restaurant->delete($id),
'Restaurant has been deleted successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function statusUpdate(int $restaurantId): JsonResponse
{
try {
$restaurant = Restaurant::find($restaurantId);
if ($restaurant) {
$restaurant->status = $restaurant->status == 1 ? 0 : 1;
$restaurant->save();
return $this->responseSuccess(
['status' => $restaurant->status],
'Restaurant status updated successfully.'
);
}
return $this->responseError([], 'Restaurant not found.', 404);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function restaurantTheme(Request $request)
{
$restaurant = Restaurant::where('id', $request->restaurant_id)->first();
$restaurant->update([
'theme_id' => $request->theme_id,
]);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Authentication\Http\Requests\RestaurantImageSAASSetting\RestaurantImageSAASSettingStoreRequest;
use Modules\Authentication\Http\Requests\RestaurantImageSAASSetting\RestaurantImageSAASSettingUpdateRequest;
use Modules\Authentication\Models\RestaurantImageSAASSetting;
use Modules\Authentication\Repositories\RestaurantImageSAASSettingRepository;
class RestaurantImageSAASSettingController extends Controller
{
public function __construct(private RestaurantImageSAASSettingRepository $repo) {}
public function index(): JsonResponse
{
try {
$restaurantImageSAASSetting = RestaurantImageSAASSetting::first();
return $this->responseSuccess($restaurantImageSAASSetting, 'RestaurantImageSAASSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(RestaurantImageSAASSettingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'RestaurantImageSAASSetting has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'RestaurantImageSAASSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(RestaurantImageSAASSettingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'RestaurantImageSAASSetting has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'RestaurantImageSAASSetting has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Authentication\Http\Requests\RestaurantImageSetting\RestaurantImageSettingStoreRequest;
use Modules\Authentication\Http\Requests\RestaurantImageSetting\RestaurantImageSettingUpdateRequest;
use Modules\Authentication\Models\RestaurantImageSetting;
use Modules\Authentication\Repositories\RestaurantImageSettingRepository;
class RestaurantImageSettingController extends Controller
{
public function __construct(private RestaurantImageSettingRepository $repo) {}
public function index(): JsonResponse
{
try {
$restaurantImageSetting = RestaurantImageSetting::where('restaurant_id', getUserRestaurantId())->first();
return $this->responseSuccess($restaurantImageSetting, 'RestaurantImageSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(RestaurantImageSettingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'RestaurantImageSetting has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'RestaurantImageSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(RestaurantImageSettingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'RestaurantImageSetting has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'RestaurantImageSetting has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\HasPermissionTrait;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Authentication\Http\Requests\Role\RoleStoreRequest;
use Modules\Authentication\Http\Requests\Role\RoleUpdateRequest;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Services\PermissionService;
use Modules\Authentication\Services\RoleService;
class RoleController extends Controller
{
use HasPermissionTrait;
public function __construct(
private RoleService $service,
private PermissionService $permissionService
) {}
public function index(Request $request)
{
$perPage = request('perPage') ?? env('PER_PAGE');
$roles = $this->service->index($request, (int) $perPage);
if (! $roles) {
return $this->responseSuccess([], 'No Data Found!');
}
return $this->responseSuccess($roles);
}
public function store(RoleStoreRequest $request)
{
DB::beginTransaction();
try {
$this->service->create($request->validated());
DB::commit();
return $this->responseSuccess([]);
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine());
}
}
public function show(int $id)
{
$role = $this->service->getById($id);
if (! $role) {
return $this->responseSuccess([], 'No Data Found!');
}
return $this->responseSuccess($role);
}
public function edit(Request $request, int $id)
{
$permissions = $this->permissionService->getAll($request);
$groupedPermissions = [];
foreach ($permissions as $permission) {
$group = explode('-', $permission->name)[0];
if (! isset($groupedPermissions[$group])) {
$groupedPermissions[$group] = [];
}
$groupedPermissions[$group][] = $permission;
}
return $this->responseSuccess([]);
}
public function update(RoleUpdateRequest $request, int $id)
{
// Block system generated roles (ID: 17)
if ($id >= 1 && $id <= 7) {
return $this->responseError([], 'System-generated roles cannot be updated.', 422);
}
DB::beginTransaction();
try {
$this->service->update($id, $request->validated());
DB::commit();
return $this->responseSuccess([]);
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine());
}
}
public function destroy(int $id)
{
// Block system generated roles (ID: 17)
if ($id >= 1 && $id <= 7) {
return $this->responseError([], 'System-generated roles cannot be deleted.', 422);
}
DB::beginTransaction();
try {
$data = $this->service->delete($id);
if (! $data) {
return $this->responseError([], 'Data Not Found!');
}
DB::commit();
return $this->responseSuccess([], 'Data successfully delete!');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine());
}
}
public function restaurantTheme(Request $request)
{
// Validate request
$validated = $request->validate([
'restaurant_id' => 'required|integer|exists:restaurants,id',
'theme_id' => 'required|integer|exists:themes,id',
]);
// Fetch restaurant safely
$restaurant = Restaurant::findOrFail($validated['restaurant_id']);
// Update theme
$restaurant->update([
'theme_id' => $validated['theme_id'],
]);
// Return formatted API response
return response()->json([
'status' => true,
'message' => 'Restaurant theme updated successfully.',
'data' => [
'restaurant_id' => $restaurant->id,
'theme_id' => $restaurant->theme_id,
],
], 200);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Authentication\Models\SAASFaq;
use Modules\Authentication\Repositories\SAASFaqRepository;
use Modules\Frontend\Http\Requests\Faq\FaqStoreRequest;
use Modules\Frontend\Http\Requests\Faq\FaqUpdateRequest;
class SAASFaqController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private SAASFaqRepository $faqQuestion) {}
public function getSaasFaqs(Request $request): JsonResponse
{
$perPage = $request->get('per_page', 10);
$getSaasFaqs = SAASFaq::paginate($perPage);
return $this->responseSuccess($getSaasFaqs, 'Saas Faqs fetched successfully.');
}
public function index(): JsonResponse
{
try {
return $this->responseSuccess(
$this->faqQuestion->getAll(request()->all()),
'Faq has been fetched successfully.'
);
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FaqStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess(
$this->faqQuestion->create($request->all()),
'Faq has been created successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->faqQuestion->getById($id),
'Faq has been fetched successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function update(FaqUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->faqQuestion->update($id, $this->getUpdateRequest($request)),
'Faq has been updated successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->faqQuestion->delete($id),
'Faq has been deleted successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Authentication\Http\Requests\SAASSubscription\SAASSubscriptionStoreRequest;
use Modules\Authentication\Http\Requests\SAASSubscription\SAASSubscriptionUpdateRequest;
use Modules\Authentication\Models\SAASSubscription;
use Modules\Authentication\Repositories\SAASSubscriptionRepository;
class SAASSubscriptionController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private SAASSubscriptionRepository $sAASSubscriptionRepository) {}
public function getSAASSubscriptions(Request $request): JsonResponse
{
$perPage = $request->get('per_page', 10);
$getSAASSubscriptions = SAASSubscription::paginate($perPage);
return $this->responseSuccess($getSAASSubscriptions, 'Saas Subscriptions fetched successfully.');
}
public function index(): JsonResponse
{
try {
return $this->responseSuccess(
$this->sAASSubscriptionRepository->getAll(request()->all()),
'SAAS Subscription has been fetched successfully.'
);
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(SAASSubscriptionStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess(
$this->sAASSubscriptionRepository->create($request->all()),
'SAAS Subscription has been created successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->sAASSubscriptionRepository->getById($id),
'SAAS Subscription has been fetched successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function update(SAASSubscriptionUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->sAASSubscriptionRepository->update($id, $this->getUpdateRequest($request)),
'SAAS Subscription has been updated successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->sAASSubscriptionRepository->delete($id),
'SAAS Subscription has been deleted successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function publicStore(SAASSubscriptionStoreRequest $request): JsonResponse
{
$subscriptions = SAASSubscription::create([
'email' => $request->email,
]);
return $this->responseSuccess(
$subscriptions,
'SAAS Subscription has been created successfully.'
);
}
}

View File

@@ -0,0 +1,240 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
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\DB;
use Illuminate\Support\Str;
use Modules\Authentication\Models\Package;
use Modules\Authentication\Models\Subscription;
use Modules\Authentication\Models\SubscriptionItem;
use Modules\Authentication\Models\SubscriptionUpgradeRequest;
class SAASSubscriptionUpgradeController extends Controller
{
use Authenticatable;
public function requestUpgradeList(Request $request): JsonResponse
{
$perPage = $request->input('per_page', 15);
$search = $request->input('search');
$query = SubscriptionUpgradeRequest::where('status', 'pending')
->with(['restaurant', 'package'])
->when($search, function ($q) use ($search) {
$q->whereHas('restaurant', function ($q2) use ($search) {
$q2->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
});
})
->orderBy('id', 'desc');
$upgradeRequests = $query->paginate($perPage);
return $this->responseSuccess($upgradeRequests, 'Upgrade requests fetched successfully.');
}
public function requestUpgrade(Request $request): JsonResponse
{
$validated = $request->validate([
'package_id' => 'required|exists:packages,id',
'notes' => 'nullable|string',
]);
$restaurantId = $this->getCurrentRestaurantId(); // assume this gets the current user's restaurant ID
$package = Package::findOrFail($validated['package_id']);
// Check for existing pending upgrade request for the same restaurant & package
$existingPending = SubscriptionUpgradeRequest::where('restaurant_id', $restaurantId)
// ->where('package_id', $package->id)
->where('status', 'pending')
->first();
if ($existingPending) {
// Update the existing pending request
$existingPending->update([
'extra_days' => (int) $package->duration_days,
'payment_method' => 'online', // or from config
'amount_paid' => $package->price,
'notes' => $validated['notes'] ?? null,
]);
$upgradeRequest = $existingPending;
} else {
// Create a new upgrade request
$upgradeRequest = SubscriptionUpgradeRequest::create([
'restaurant_id' => $restaurantId,
'package_id' => $package->id,
'extra_days' => (int) $package->duration_days,
'payment_method' => 'online',
'amount_paid' => $package->price,
'notes' => $validated['notes'] ?? null,
'status' => 'pending',
]);
}
return $this->responseSuccess($upgradeRequest, 'Upgrade request submitted successfully.');
}
public function approveRequest(int $requestId): JsonResponse
{
DB::beginTransaction();
try {
$subscriptionRequest = SubscriptionUpgradeRequest::where('id', $requestId)->where('status', 'pending')->first();
if (! $subscriptionRequest) {
return $this->responseError([], 'No pending request found.', 404);
}
$package = Package::findOrFail($subscriptionRequest->package_id);
$extraDays = $subscriptionRequest->extra_days ?? 0;
$startDate = Carbon::now();
$endDate = $startDate->copy()->addDays($package->duration_days + $extraDays);
// Update or create subscription
$subscription = Subscription::where('restaurant_id', $subscriptionRequest->restaurant_id)->first();
if ($subscription) {
$subscription->update([
'package_id' => $package->id,
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
'transactions_details' => json_encode([
'invoice_no' => 'INV-'.strtoupper(Str::random(6)),
'issued_at' => now()->toDateString(),
'notes' => $subscriptionRequest->notes,
'amount' => $subscriptionRequest->amount_paid,
]),
]);
} else {
$subscription = Subscription::create([
'restaurant_id' => $subscriptionRequest->restaurant_id,
'package_id' => $package->id,
'user_id' => Auth::id(),
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
'transactions_details' => json_encode([
'invoice_no' => 'INV-'.strtoupper(Str::random(6)),
'issued_at' => now()->toDateString(),
'notes' => $subscriptionRequest->notes,
'amount' => $subscriptionRequest->amount_paid,
]),
]);
}
SubscriptionItem::create([
'user_id' => Auth::id(),
'restaurant_id' => $subscriptionRequest->restaurant_id,
'subscription_id' => $subscription->id,
'reference_no' => 'PAY-'.strtoupper(Str::random(8)),
'amount_paid' => $subscriptionRequest->amount_paid,
'payment_method' => $subscriptionRequest->payment_method,
]);
$subscriptionRequest->update([
'status' => 'approved',
'approved_by' => auth()->id(),
'approved_at' => now(),
]);
DB::commit();
return $this->responseSuccess($subscription, 'Subscription request approved successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], 'Error: '.$e->getMessage());
}
}
public function paymentSubscriptions(Request $request): JsonResponse
{
$request->validate([
'restaurant_id' => 'required|exists:restaurants,id',
'package_id' => 'required|exists:packages,id',
'upgrade_request_id' => 'nullable|exists:subscription_upgrade_requests,id',
'payment_method' => 'nullable|string',
'notes' => 'nullable|string',
'amount_paid' => 'nullable|numeric',
'extra_days' => 'nullable|integer',
]);
DB::beginTransaction();
try {
$package = Package::findOrFail((int) $request->package_id);
$startDate = Carbon::now();
$extraDays = $request->input('extra_days', 0);
$endDate = $startDate->copy()->addDays($package->duration_days + $extraDays);
$subscription = Subscription::where('restaurant_id', (int) $request->restaurant_id)->first();
$invoiceData = [
'invoice_no' => 'INV-'.strtoupper(Str::random(6)),
'issued_at' => now()->toDateString(),
'notes' => $request->input('notes', 'Payment for upgrade'),
'amount' => $request->input('amount_paid', $package->price),
];
if ($subscription) {
$subscription->update([
'package_id' => $package->id,
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
'transactions_details' => $invoiceData,
]);
} else {
$subscription = Subscription::create([
'restaurant_id' => (int) $request->restaurant_id,
'package_id' => $package->id,
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
'transactions_details' => $invoiceData,
]);
}
SubscriptionItem::create([
'subscription_id' => $subscription->id,
'reference_no' => 'PAY-'.strtoupper(Str::random(8)),
'amount_paid' => $invoiceData['amount'],
'payment_method' => $request->input('payment_method', 'online'),
]);
// If upgrading through a pending request, mark it as approved
if ($request->filled('upgrade_request_id')) {
$upgradeRequest = SubscriptionUpgradeRequest::where('id', $request->upgrade_request_id)
->where('status', 'pending')
->first();
if (! $upgradeRequest) {
return $this->responseError([], 'No pending upgrade request found.', 404);
}
$upgradeRequest->update([
'status' => 'approved',
'approved_by' => auth()->id(),
'approved_at' => now(),
]);
}
DB::commit();
return $this->responseSuccess($subscription, 'Subscription payment processed successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], 'Error: '.$e->getMessage());
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Authentication\Http\Requests\Auth\UserCreateRequest;
use Modules\Authentication\Http\Requests\Auth\UserUpdateRequest;
use Modules\Authentication\Repositories\UserRepository;
class UserController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private UserRepository $user) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess(
$this->user->getAll(request()->all()),
'User has been fetched successfully.'
);
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(UserCreateRequest $request): JsonResponse
{
try {
return $this->responseSuccess(
$this->user->create($request->all()),
'User has been created successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->user->getById($id),
'User has been fetched successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function update(UserUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess(
$this->user->update($id, $this->getUpdateRequest($request)),
'User has been updated successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
public function destroy(int $id): JsonResponse
{
try {
$user = $this->user->getById($id);
if (! $user) {
return $this->responseError([], 'User Not Found');
}
if ($user->image) {
fileRemover('users/', $user->image);
}
return $this->responseSuccess(
$this->user->delete($id),
'User has been deleted successfully.'
);
} catch (Exception $exception) {
return $this->responseError([], $exception->getMessage(), $exception->getCode());
}
}
}

View File

@@ -0,0 +1,447 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Helper\DomainHelper;
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\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Modules\Authentication\Http\Requests\Settings\SettingUpdateRequest;
use Modules\Authentication\Models\Setting;
use Modules\Authentication\Models\User;
use Modules\HRM\Models\Attendance;
use Modules\HRM\Models\AttendanceLog;
class UtilityController extends Controller
{
use Authenticatable;
public function restaurantSettingsUpdate(SettingUpdateRequest $request): JsonResponse
{
try {
if ($request->isMethod('get')) {
$settings = Setting::where('restaurant_id', $this->getCurrentRestaurantId())
->pluck('option_value', 'option_key'); // Return settings as key-value pairs
return $this->responseSuccess($settings, 'System Settings fetched successfully.');
}
// If POST request, update or create settings
DB::beginTransaction();
foreach ($request->validated() as $key => $value) {
if ($key === '_token') {
continue;
}
Setting::updateOrInsert(
[
'restaurant_id' => $this->getCurrentRestaurantId(),
'option_key' => $key,
],
[
'option_value' => $value,
'updated_at' => now(),
'created_at' => now(),
]
);
}
DB::commit();
return $this->responseSuccess([], 'System Settings have been successfully updated.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError(['error' => $e->getMessage()], 'Failed to store records unsuccessfully.');
}
}
public function restaurantLogoUpdate(Request $request): JsonResponse
{
$request->validate([
'logo' => 'required|image|mimes:jpeg,png,jpg|max:8192',
]);
$image = $request->file('logo');
$name = 'logo.'.$image->getClientOriginalExtension();
$destinationPath = public_path('/uploads/logos');
$image->move($destinationPath, $name);
$data = [];
$data['option_value'] = $name;
$data['updated_at'] = Carbon::now();
if (Setting::where('restaurant_id', $this->getCurrentRestaurantId())->where('option_key', 'app_logo')->exists()) {
Setting::where('restaurant_id', $this->getCurrentRestaurantId())->where('option_key', '=', 'app_logo')->update($data);
} else {
$data['option_value'] = 'app_logo';
$data['created_at'] = Carbon::now();
Setting::insert($data);
}
return $this->responseSuccess(
[],
'Restaurant logo have been successfully updated'
);
}
public function getAllImageFolderUrls(): JsonResponse
{
$uploadBasePath = public_path('uploads');
$storageBasePath = public_path('storage');
$uploadBaseUrl = url('uploads');
$storageBaseUrl = url('storage');
$data = [];
// Helper to recursively get folders and create URLs
$getFolders = function ($basePath, $baseUrl, $prefix = '') use (&$data) {
if (! File::exists($basePath)) {
return;
}
$folders = File::directories($basePath);
foreach ($folders as $folderPath) {
$relativePath = str_replace($basePath.DIRECTORY_SEPARATOR, '', $folderPath);
$key = str_replace(['/', '\\'], '_', ($prefix.$relativePath)).'_url';
$data[$key] = str_replace('\\', '/', $baseUrl.'/'.$relativePath.'/');
// Recursive scan subfolders
$thisFolder = $basePath.DIRECTORY_SEPARATOR.$relativePath;
$getSub = File::directories($thisFolder);
foreach ($getSub as $subFolder) {
$subRelative = str_replace($basePath.DIRECTORY_SEPARATOR, '', $subFolder);
$key = str_replace(['/', '\\'], '_', ($prefix.$subRelative)).'_url';
$data[$key] = str_replace('\\', '/', $baseUrl.'/'.$subRelative.'/');
}
}
};
// Scan and build URLs from public/uploads
$getFolders($uploadBasePath, $uploadBaseUrl);
// Scan and build URLs from public/storage
$getFolders($storageBasePath, $storageBaseUrl, 'storage_');
// Add any manually defined static folders (optional)
$data['system_logo_url'] = url('/uploads/logos/');
return response()->json([
'status' => 'success',
'message' => 'All image folder URLs fetched successfully.',
'data' => $data,
]);
}
public function linkStorage(): JsonResponse
{
$publicStorage = public_path('storage');
$target = storage_path('app/public');
try {
if (! File::exists($publicStorage)) {
File::link($target, $publicStorage);
return response()->json(['status' => 'success', 'message' => 'Storage link created.']);
} else {
return response()->json(['status' => 'info', 'message' => 'Storage link already exists.']);
}
} catch (Exception $e) {
return response()->json(['status' => 'error', 'message' => $e->getMessage()]);
}
}
public function unlinkStorage(): JsonResponse
{
$publicStorage = public_path('storage');
try {
if (is_link($publicStorage)) {
unlink($publicStorage);
} elseif (is_dir($publicStorage)) {
// Attempt to delete via Laravel
if (! File::deleteDirectory($publicStorage)) {
// Fallback for stubborn directories
exec('rm -rf '.escapeshellarg($publicStorage));
}
}
return response()->json([
'status' => 'success',
'message' => 'The storage link or directory has been removed.',
]);
} catch (Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'Failed: '.$e->getMessage(),
]);
}
}
public function clearAllCache(): JsonResponse
{
try {
Artisan::call('config:clear');
Artisan::call('cache:clear');
Artisan::call('route:clear');
Artisan::call('view:clear');
return response()->json(['status' => 'success', 'message' => 'All caches cleared.']);
} catch (Exception $e) {
return response()->json(['status' => 'error', 'message' => $e->getMessage()]);
}
}
// Predefined models map (from your dump)
protected $models = [
'gallery' => 'Modules\\Authentication\\Models\\Gallery',
'restaurant' => 'Modules\\Authentication\\Models\\Restaurant',
'setting' => 'Modules\\Authentication\\Models\\Setting',
'user' => 'Modules\\Authentication\\Models\\User',
'aboutus' => 'Modules\\Frontend\\Models\\AboutUs',
'banner' => 'Modules\\Frontend\\Models\\Banner',
'contact' => 'Modules\\Frontend\\Models\\Contact',
'faqquestion' => 'Modules\\Frontend\\Models\\FaqQuestion',
'mobileappsection' => 'Modules\\Frontend\\Models\\MobileAppSection',
'onboarding' => 'Modules\\Frontend\\Models\\Onboarding',
'ourhistory' => 'Modules\\Frontend\\Models\\OurHistory',
'page' => 'Modules\\Frontend\\Models\\Page',
'policy' => 'Modules\\Frontend\\Models\\Policy',
'readytojoinus' => 'Modules\\Frontend\\Models\\ReadyToJoinUs',
'testimonial' => 'Modules\\Frontend\\Models\\Testimonial',
'theme' => 'Modules\\Frontend\\Models\\Theme',
'whychooseus' => 'Modules\\Frontend\\Models\\WhyChooseUs',
'paymentrequest' => 'Modules\\Gateways\\Models\\PaymentRequest',
'addon' => 'Modules\\Restaurant\\Models\\Addon',
'addonfood' => 'Modules\\Restaurant\\Models\\AddonFood',
'customer' => 'Modules\\Restaurant\\Models\\Customer',
'foodavailability' => 'Modules\\Restaurant\\Models\\FoodAvailability',
'foodcategory' => 'Modules\\Restaurant\\Models\\FoodCategory',
'fooditem' => 'Modules\\Restaurant\\Models\\FoodItem',
'foodvariant' => 'Modules\\Restaurant\\Models\\FoodVariant',
'foodvariantingredient' => 'Modules\\Restaurant\\Models\\FoodVariantIngredient',
'ingredient' => 'Modules\\Restaurant\\Models\\Ingredient',
'ingredientdamage' => 'Modules\\Restaurant\\Models\\IngredientDamage',
'menutype' => 'Modules\\Restaurant\\Models\\MenuType',
'menusection' => 'Modules\\Restaurant\\Models\\MenuSection',
'menucategory' => 'Modules\\Restaurant\\Models\\MenuCategory',
'order' => 'Modules\\Restaurant\\Models\\Order',
'orderitem' => 'Modules\\Restaurant\\Models\\OrderItem',
'paymentmethod' => 'Modules\\Restaurant\\Models\\PaymentMethod',
'purchase' => 'Modules\\Restaurant\\Models\\Purchase',
'purchaseitem' => 'Modules\\Restaurant\\Models\\PurchaseItem',
'purchasereturn' => 'Modules\\Restaurant\\Models\\PurchaseReturn',
'reservation' => 'Modules\\Restaurant\\Models\\Reservation',
'reservationunavailable' => 'Modules\\Restaurant\\Models\\ReservationUnavailable',
'stock' => 'Modules\\Restaurant\\Models\\Stock',
'supplier' => 'Modules\\Restaurant\\Models\\Supplier',
'table' => 'Modules\\Restaurant\\Models\\Table',
'unit' => 'Modules\\Restaurant\\Models\\Unit',
'restaurantschedules' => 'Modules\\Restaurant\\Models\\RestaurantSchedule',
'kitchen' => 'Modules\\Restaurant\\Models\\Kitchen',
'unitmeasurements' => 'Modules\\Restaurant\\Models\\Kitchen',
'hotel' => 'Modules\\Booking\\Models\\Hotel',
'floor' => 'Modules\\Booking\\Models\\Floor',
'roomtype' => 'Modules\\Booking\\Models\\RoomType',
'room' => 'Modules\\Booking\\Models\\Room',
'booking' => 'Modules\\Booking\\Models\\Booking',
];
public function globalStatusUpdate(Request $request)
{
$request->validate([
'id' => 'required|integer',
'type' => 'required|string',
'status' => 'required|string',
]);
$type = strtolower($request->type);
$id = $request->id;
$status = $request->status;
if (! isset($this->models[$type])) {
return $this->responseError([], 'Invalid type', 400);
}
$modelClass = $this->models[$type];
$item = $modelClass::find($id);
if (! $item) {
return $this->responseError([], 'Invalid type', 400);
}
$item->status = $status;
$item->save();
return $this->responseSuccess(
$item,
'Status updated successfully.'
);
}
private function getClassFromFile($file, $modulePath)
{
$relative = str_replace(base_path().'/', '', $file->getPathname());
$class = str_replace('/', '\\', substr($relative, 0, -4)); // remove .php
return $class;
}
public function domainCheck(Request $request): JsonResponse
{
$domain = $request->query('domain');
$info = DomainHelper::getDomainInfo($domain);
// Add expiration warning
$warning = DomainHelper::expirationWarning($info['expiration'] ?? null);
$info['expiration_warning'] = $warning;
return response()->json([
'success' => true,
'data' => $info,
]);
}
public function allUsersGet(Request $request)
{
$request->validate([
'restaurant_id' => 'required|integer|exists:restaurants,id',
]);
$users = User::select('id', 'first_name', 'last_name', 'user_type', 'role_id')
->where('restaurant_id', (int) $request->restaurant_id)
->get();
return $this->responseSuccess(
$users,
_lang('Users have been fetched successfully.')
);
}
public function sync(Request $request)
{
$request->validate([
'logs' => ['required', 'array'],
'logs.*.id' => ['required', 'integer'],
'logs.*.timestamp' => ['required', 'date'],
'logs.*.type' => ['required', 'integer'],
]);
$restaurantId = $this->getCurrentRestaurantId(); // or from device
$inserted = 0;
$skipped = 0;
DB::beginTransaction();
try {
foreach ($request->logs as $log) {
$employeeId = $log['id'];
$dateTime = Carbon::parse($log['timestamp']);
$date = $dateTime->toDateString();
$type = $this->mapPunchType($log['type']);
/**
* 1️⃣ Avoid duplicate punch
*/
$exists = AttendanceLog::where([
'employee_id' => $employeeId,
'restaurant_id' => $restaurantId,
'punch_time' => $dateTime,
'type' => $type,
])->exists();
if ($exists) {
$skipped++;
continue;
}
/**
* 2️⃣ Get or create daily attendance
*/
$attendance = Attendance::firstOrCreate(
[
'employee_id' => $employeeId,
'date' => $date,
],
[
'restaurant_id' => $restaurantId,
'status' => 'present',
]
);
/**
* 3️⃣ Insert punch log
*/
AttendanceLog::create([
'attendance_id' => $attendance->id,
'employee_id' => $employeeId,
'restaurant_id' => $restaurantId,
'type' => $type,
'punch_time' => $dateTime,
]);
/**
* 4️⃣ Recalculate attendance summary
*/
$this->recalculateWorkedHours($attendance);
$inserted++;
}
DB::commit();
return response()->json([
'status' => true,
'inserted' => $inserted,
'skipped' => $skipped,
'message' => 'Attendance synced successfully',
]);
} catch (\Throwable $e) {
DB::rollBack();
return response()->json([
'status' => false,
'message' => 'Attendance sync failed',
'error' => $e->getMessage(),
], 500);
}
}
private function mapPunchType(int $type): string
{
return in_array($type, [0, 4]) ? 'in' : 'out';
}
/**
* RECALCULATE TOTAL WORKED HOURS BASED ON ALL LOGS
*/
private function recalculateWorkedHours(Attendance $attendance)
{
$logs = AttendanceLog::where('attendance_id', $attendance->id)
->orderBy('punch_time')
->get();
if ($logs->count() < 2) {
return;
}
$first = Carbon::parse($logs->first()->punch_time);
$last = Carbon::parse($logs->last()->punch_time);
$hours = round($first->diffInMinutes($last) / 60, 2);
$attendance->update([
'first_clock_in' => $first,
'last_clock_out' => $last,
'hours_worked' => $hours,
]);
}
}

View File

@@ -0,0 +1,158 @@
<?php
namespace Modules\Authentication\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Modules\Authentication\Models\ZoomMeeting;
class ZoomController extends Controller
{
use Authenticatable;
private $zoomToken;
public function __construct()
{
$this->zoomToken = $this->generateZoomToken();
}
private function generateZoomToken()
{
$response = Http::asForm()->withHeaders([
'Authorization' => 'Basic '.base64_encode(config('zoom.client_id').':'.config('zoom.client_secret')),
])->post('https://zoom.us/oauth/token', [
'grant_type' => 'account_credentials',
'account_id' => config('zoom.account_id'),
]);
return $response->json()['access_token'] ?? null;
}
public function index(Request $request): JsonResponse
{
$perPage = request('per_page', 10);
$zoomMeetings = ZoomMeeting::where('start_time', '>=', Carbon::now('Asia/Dhaka')->toDateTimeString())
->latest()
->paginate($perPage);
return $this->responseSuccess(
$zoomMeetings,
'Zoom Meetings list fetch successfully.'
);
}
public function store(Request $request): JsonResponse
{
// Validate the request data
$request->validate([
'topic' => 'required|string|max:255',
'start_time' => 'required|date_format:Y-m-d\TH:i:s\Z', // ISO 8601 format (Asia/Dhaka)
'duration' => 'required|integer|min:1', // Duration in minutes
'password' => 'nullable|string|min:6|max:10', // Optional password, min 6 chars
'agenda' => 'nullable|string|max:5000',
]);
$startTimeUtc = Carbon::parse($request->start_time, 'Asia/Dhaka')->setTimezone('Asia/Dhaka')->toIso8601String();
$response = Http::withToken($this->zoomToken)->post(config('zoom.base_url').'users/me/meetings', [
'topic' => $request->topic,
'type' => 2, // Scheduled Meeting
'start_time' => $startTimeUtc,
'duration' => $request->duration,
'password' => '123456',
'agenda' => 'Live Class',
'timezone' => 'Asia/Dhaka',
'settings' => [
'host_video' => true,
'participant_video' => true,
'waiting_room' => false,
],
]);
if ($response->successful()) {
$data = $response->json();
$meeting = ZoomMeeting::create([
'restaurant_id' => $this->getCurrentRestaurantId(),
'meeting_id' => $data['id'],
'topic' => $data['topic'],
'agenda' => 'Live Class',
'start_time' => $data['start_time'],
'duration' => $data['duration'],
'password' => '123456',
'join_url' => $data['join_url'],
'start_url' => $data['start_url'],
'created_by' => Auth::id(),
]);
return $this->responseSuccess(
$meeting,
'Zoom Meeting create successfully.'
);
}
return $this->responseError([], 'Zoom API Error');
}
public function show(string $id): JsonResponse
{
$zoomMeeting = ZoomMeeting::where('meeting_id', $id)->first();
if (! $zoomMeeting) {
return $this->responseError($zoomMeeting, 'Zoom Meeting not found');
}
return $this->responseSuccess(
$zoomMeeting,
'Zoom Meeting fetch successfully.'
);
}
public function update(Request $request, int $id): JsonResponse
{
$zoomMeeting = ZoomMeeting::where('meeting_id', $id)->first();
if (! $zoomMeeting) {
return $this->responseError($zoomMeeting, 'Zoom Meeting not found');
}
$response = Http::withToken($this->zoomToken)->patch(config('zoom.base_url')."meetings/{$id}", [
'topic' => $request->topic ?? $zoomMeeting->topic,
'start_time' => $request->start_time ?? $zoomMeeting->start_time,
'duration' => $request->duration ?? $zoomMeeting->duration,
'password' => $request->password ?? $zoomMeeting->password,
]);
if ($response->successful()) {
$zoomMeeting->update($request->only(['topic', 'start_time', 'duration', 'password']));
return $this->responseSuccess(
$zoomMeeting,
'Zoom Meeting update successfully.'
);
}
return $this->responseError([], 'Failed to update meeting');
}
public function destroy(int $id): JsonResponse
{
$zoomMeeting = ZoomMeeting::where('meeting_id', $id)->first();
if (! $zoomMeeting) {
return $this->responseError($zoomMeeting, 'Zoom Meeting not found');
}
$response = Http::withToken($this->zoomToken)->delete(config('zoom.base_url')."meetings/{$id}");
if ($response->successful()) {
$zoomMeeting->delete();
return $this->responseSuccess(
[],
'Zoom Meeting deleted successfully.'
);
}
return $this->responseError([], 'Failed to delete meeting');
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Modules\Authentication\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $request->user(),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Modules\Authentication\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class UserCreateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'first_name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z\s]+$/'],
'last_name' => ['nullable', 'string', 'max:255', 'regex:/^[a-zA-Z\s]+$/'],
'email' => ['nullable', 'string', 'email', 'max:255', 'regex:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', 'unique:users,email'],
'phone' => ['required', 'string', 'regex:/^(\+?\d{1,4}|\d{1,4})?\d{7,15}$/', 'unique:users,phone'],
'password' => ['required', 'confirmed', 'string', 'min:8'],
'role_id' => ['required', 'integer', 'exists:roles,id'],
'avatar' => ['nullable'],
];
}
public function messages()
{
return [
'email.unique' => 'The email has already been taken.',
'phone.unique' => 'The phone number has already been registered.',
'password.confirmed' => 'The password confirmation does not match.',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Modules\Authentication\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class UserUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'first_name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z\s]+$/'],
'last_name' => ['nullable', 'string', 'max:255', 'regex:/^[a-zA-Z\s]+$/'],
'email' => [
'nullable',
'string',
'email',
'max:255',
'regex:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/',
],
'phone' => [
'nullable',
'string',
'regex:/^(\+?\d{1,4}|\d{1,4})?\d{7,15}$/',
],
'role_id' => ['required', 'integer', 'exists:roles,id'],
'avatar' => ['nullable'],
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Authentication\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PermissionStoreRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|max:255',
];
}
public function message(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Authentication\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PermissionUpdateRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|max:255',
];
}
public function message(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\Authentication\Http\Requests\Plan;
use Illuminate\Foundation\Http\FormRequest;
class PlanStoreRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'student_limit' => 'required|integer|min:0',
'branch_limit' => 'required|integer|min:0',
'price' => 'required|numeric|min:0|max:999999.99',
'duration_days' => 'nullable|integer|min:1',
'is_custom' => 'required|boolean',
'is_free' => 'required|boolean',
'status' => 'nullable|in:0,1',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\Authentication\Http\Requests\Plan;
use Illuminate\Foundation\Http\FormRequest;
class PlanUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'sometimes|required|string|max:255',
'description' => 'nullable|string',
'student_limit' => 'sometimes|required|integer|min:0',
'branch_limit' => 'sometimes|required|integer|min:0',
'price' => 'sometimes|required|numeric|min:0|max:999999.99',
'duration_days' => 'nullable|integer|min:1',
'is_custom' => 'sometimes|required|boolean',
'is_free' => 'sometimes|required|boolean',
'status' => 'nullable|required|in:0,1',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\Authentication\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Modules\Authentication\Models\User;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Modules\Authentication\Http\Requests\Restaurant;
use Illuminate\Foundation\Http\FormRequest;
class RestaurantStoreRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100', 'regex:/^[a-zA-Z\s]+$/u'],
'email' => [
'nullable',
'string',
'email:rfc,dns',
'max:100',
'unique:users,email', // Ensure unique email for new users
],
'phone' => [
'required',
'string',
'regex:/^(\+?\d{1,4}|\d{1,4})?\d{7,15}$/',
'unique:users,phone', // Ensure unique phone for new users
],
'restaurant_type' => ['required', 'string', 'max:100'],
'domain' => ['required', 'string', 'max:100', 'regex:/^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z]{2,}$/'],
'address' => ['required', 'string', 'max:191'],
'logo' => ['nullable'],
'platform' => ['nullable', 'string', 'in:WEB,APP'],
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Authentication\Http\Requests\Restaurant;
use Illuminate\Foundation\Http\FormRequest;
class RestaurantUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => ['nullable', 'string', 'max:100', 'regex:/^[a-zA-Z\s]+$/u'],
'restaurant_type' => ['nullable', 'string', 'max:100'],
'address' => ['nullable', 'string', 'max:191'],
'logo' => ['nullable'],
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Authentication\Http\Requests\RestaurantImageSAASSetting;
use Illuminate\Foundation\Http\FormRequest;
class RestaurantImageSAASSettingStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'header_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'header_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'banner_image' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Authentication\Http\Requests\RestaurantImageSAASSetting;
use Illuminate\Foundation\Http\FormRequest;
class RestaurantImageSAASSettingUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
'header_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'header_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'banner_image' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Authentication\Http\Requests\RestaurantImageSetting;
use Illuminate\Foundation\Http\FormRequest;
class RestaurantImageSettingStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'header_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'header_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'banner_image' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Authentication\Http\Requests\RestaurantImageSetting;
use Illuminate\Foundation\Http\FormRequest;
class RestaurantImageSettingUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
'header_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'header_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_light_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'footer_logo_dark_theme' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
'banner_image' => ['nullable', 'image', 'mimes:png,jpg,jpeg,webp', 'max:2048'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Authentication\Http\Requests\Role;
use Illuminate\Foundation\Http\FormRequest;
class RoleStoreRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required',
'permissions' => 'array',
];
}
public function messages()
{
return [
'name.required' => 'Role name is required',
'permissions.array' => 'Permissions must be an array',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Authentication\Http\Requests\Role;
use Illuminate\Foundation\Http\FormRequest;
class RoleUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required',
'permissions' => 'array',
];
}
public function messages()
{
return [
'name.required' => 'Role name is required',
'permissions.array' => 'Permissions must be an array',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Authentication\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RoleStoreRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string|max:255',
'permissions' => 'required|array',
];
}
public function message(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Authentication\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RoleUpdateRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string|max:255',
'permissions' => 'required|array',
];
}
public function message(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\Authentication\Http\Requests\SAASSubscription;
use Illuminate\Foundation\Http\FormRequest;
class SAASSubscriptionStoreRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'email' => [
'nullable',
'string',
'email:rfc,dns',
'max:100',
'unique:s_a_a_s_subscriptions,email', // Ensure unique email for new s_a_a_s_subscriptions
],
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Modules\Authentication\Http\Requests\SAASSubscription;
use Illuminate\Foundation\Http\FormRequest;
class SAASSubscriptionUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'email' => [
'nullable',
'string',
'email:rfc,dns',
'max:100',
],
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\Authentication\Http\Requests\Settings;
use Illuminate\Foundation\Http\FormRequest;
class MailConfigUpdateRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'MAIL_MAILER' => 'required|string|max:50',
'MAIL_HOST' => 'required|string|max:255',
'MAIL_PORT' => 'required|numeric',
'MAIL_USERNAME' => 'nullable|string|max:255',
'MAIL_PASSWORD' => 'nullable|string|max:255',
'MAIL_FROM_ADDRESS' => 'required|email|max:255',
'MAIL_FROM_NAME' => 'required|string|max:255',
];
}
public function message(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Modules\Authentication\Http\Requests\Settings;
use Illuminate\Foundation\Http\FormRequest;
class SAASSettingUpdateRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'site_name' => 'nullable|string|max:255',
'site_title' => 'nullable|string|max:255',
'phone' => 'nullable|string|max:255',
'office_phone' => 'nullable|string|max:255',
'email' => 'nullable|string|max:255',
'language' => 'nullable|string|max:255',
'google_map' => 'nullable|string|max:255',
'address' => 'nullable|string|max:255',
'on_google_map' => 'nullable|string|max:255',
'currency_symbol' => 'nullable|string|max:255',
'logo' => 'nullable|string|max:255',
'disabled_website' => 'nullable|string|max:255',
'copyright_text' => 'nullable|string|max:255',
'facebook_link' => 'nullable|string|max:255',
'google_plus_link' => 'nullable|string|max:255',
'youtube_link' => 'nullable|string|max:255',
'whats_app_link' => 'nullable|string|max:255',
'twitter_link' => 'nullable|string|max:255',
'header_notice' => 'nullable|string|max:255',
'app_version' => 'nullable|string|max:255',
'app_url' => 'nullable|string|max:255',
'bkash' => 'nullable|string|max:255',
'paystack' => 'nullable|string|max:255',
'razor_pay' => 'nullable|string|max:255',
'stripe' => 'nullable|string|max:255',
'ssl_commerz' => 'nullable|string|max:255',
'pvit' => 'nullable|string|max:255',
'paypal' => 'nullable|string|max:255',
'paymob_accept' => 'nullable|string|max:255',
'flutterwave' => 'nullable|string|max:255',
'senang_pay' => 'nullable|string|max:255',
'paytm' => 'nullable|string|max:255',
'primary_color' => 'nullable|string|max:255',
'secondary_color' => 'nullable|string|max:255',
'primary_container_color' => 'nullable|string|max:255',
'dark_primary_color' => 'nullable|string|max:255',
'dark_secondary_color' => 'nullable|string|max:255',
'dark_container_color' => 'nullable|string|max:255',
'text_color' => 'nullable|string|max:255',
'dark_text_color' => 'nullable|string|max:255',
'sidebar_selected_bg_color' => 'nullable|string|max:255',
'sidebar_selected_text_color' => 'nullable|string|max:255',
'vercel_project_id' => 'nullable|string|max:255',
'vercel_token' => 'nullable|string|max:255',
];
}
public function message(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Modules\Authentication\Http\Requests\Settings;
use Illuminate\Foundation\Http\FormRequest;
class SettingUpdateRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'restaurant_name' => 'nullable|string|max:255',
'site_title' => 'nullable|string|max:255',
'phone' => 'nullable|string|max:255',
'email' => 'nullable|string|max:255',
'language' => 'nullable|string|max:255',
'google_map' => 'nullable|string|max:255',
'address' => 'nullable|string|max:255',
'on_google_map' => 'nullable|string|max:255',
'restaurant_code' => 'nullable|string|max:255',
'currency_symbol' => 'nullable|string|max:255',
'logo' => 'nullable|string|max:255',
'mail_type' => 'nullable|string|max:255',
'disabled_website' => 'nullable|string|max:255',
'copyright_text' => 'nullable|string|max:255',
'facebook_link' => 'nullable|string|max:255',
'google_plus_link' => 'nullable|string|max:255',
'youtube_link' => 'nullable|string|max:255',
'whats_app_link' => 'nullable|string|max:255',
'twitter_link' => 'nullable|string|max:255',
'eiin_code' => 'nullable|string|max:255',
'sms_gateway' => 'nullable|string|max:255',
'bulk_sms_api_key' => 'nullable|string|max:255',
'bulk_sms_sender_id' => 'nullable|string|max:255',
'twilio_sid' => 'nullable|string|max:255',
'twilio_token' => 'nullable|string|max:255',
'twilio_from_number' => 'nullable|string|max:255',
'header_notice' => 'nullable|string|max:255',
'app_version' => 'nullable|string|max:255',
'app_url' => 'nullable|string|max:255',
'tagline' => 'nullable|string|max:255',
'favicon' => 'nullable|string|max:255',
'theme_color' => 'nullable|string|max:255',
'background_image' => 'nullable|string|max:255',
'tax_type' => 'nullable|string|max:255',
'tax_percentage' => 'nullable|string|max:255',
'service_charge' => 'nullable|string|max:255',
'default_currency' => 'nullable|string|max:255',
'billing_prefix' => 'nullable|string|max:255',
'invoice_footer' => 'nullable|string|max:255',
'enable_kitchen_print' => 'nullable|string|max:255',
'enable_customer_copy' => 'nullable|string|max:255',
'enable_online_order' => 'nullable|string|max:255',
'delivery_charge' => 'nullable|string|max:255',
'minimum_order_amount' => 'nullable|string|max:255',
'auto_accept_order' => 'nullable|string|max:255',
'estimated_preparation_time' => 'nullable|string|max:255',
'slack_webhook_url' => 'nullable|string|max:255',
'telegram_bot_token' => 'nullable|string|max:255',
'telegram_chat_id' => 'nullable|string|max:255',
'twilio_sms_enabled' => 'nullable|string|max:255',
'email_notifications' => 'nullable|string|max:255',
'whatsapp_notifications' => 'nullable|string|max:255',
'auto_backup' => 'nullable|string|max:255',
'report_timezone' => 'nullable|string|max:255',
'data_retention_days' => 'nullable|string|max:255',
'sidebar_collapsed' => 'nullable|string|max:255',
'dark_mode' => 'nullable|string|max:255',
'default_dashboard' => 'nullable|string|max:255',
'razorpay_key' => 'nullable|string|max:255',
'razorpay_secret' => 'nullable|string|max:255',
'stripe_key' => 'nullable|string|max:255',
'stripe_secret' => 'nullable|string|max:255',
'cash_on_delivery' => 'nullable|string|max:255',
'max_table_capacity' => 'nullable|string|max:255',
'default_shift_start' => 'nullable|string|max:255',
'default_shift_end' => 'nullable|string|max:255',
'auto_logout_idle_minutes' => 'nullable|string|max:255',
'primary_color' => 'nullable|string|max:255',
'secondary_color' => 'nullable|string|max:255',
'primary_container_color' => 'nullable|string|max:255',
'dark_primary_color' => 'nullable|string|max:255',
'dark_secondary_color' => 'nullable|string|max:255',
'dark_container_color' => 'nullable|string|max:255',
'text_color' => 'nullable|string|max:255',
'dark_text_color' => 'nullable|string|max:255',
'sidebar_selected_bg_color' => 'nullable|string|max:255',
'sidebar_selected_text_color' => 'nullable|string|max:255',
'is_online' => 'nullable|string|max:255',
'latitude' => 'nullable|string|max:255',
'longitude' => 'nullable|string|max:255',
'delivery_radius_km' => 'nullable|string|max:255',
'delivery_fee' => 'nullable|string|max:255',
'delivery_partner_count' => 'nullable|string|max:255',
'delivery_time_avg' => 'nullable|string|max:255',
'pickup_enabled' => 'nullable|string|max:255',
'opening_time' => 'nullable|string|max:255',
'closing_time' => 'nullable|string|max:255',
'auto_accept_orders' => 'nullable|string|max:255',
'pre_order_enabled' => 'nullable|string|max:255',
'max_order_capacity' => 'nullable|string|max:255',
'avg_rating' => 'nullable|string|max:255',
'review_count' => 'nullable|string|max:255',
'total_orders' => 'nullable|string|max:255',
'last_order_time' => 'nullable|string|max:255',
'last_active_time' => 'nullable|string|max:255',
'loyalty_points_enabled' => 'nullable|string|max:255',
'offers_enabled' => 'nullable|string|max:255',
'social_media_links' => 'nullable',
'settings' => 'nullable',
'uuid' => 'nullable',
];
}
public function message(): array
{
return [
//
];
}
}