Files
admin/app/Http/Controllers/ApiController.php
Husanjonazamov e0f1989655 classify admin
2026-02-24 12:52:01 +05:00

4671 lines
206 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Http\Resources\ItemCollection;
use App\Models\Area;
use App\Models\BlockUser;
use App\Models\Blog;
use App\Models\Category;
use App\Models\Chat;
use App\Models\City;
use App\Models\ContactUs;
use App\Models\Country;
use App\Models\Currency;
use App\Models\CustomField;
use App\Models\Faq;
use App\Models\Favourite;
use App\Models\FeaturedItems;
use App\Models\FeatureSection;
use App\Models\Item;
use App\Models\ItemCustomFieldValue;
use App\Models\ItemImages;
use App\Models\ItemOffer;
use App\Models\JobApplication;
use App\Models\Language;
use App\Models\Notifications;
use App\Models\NumberOtp;
use App\Models\Package;
use App\Models\PaymentConfiguration;
use App\Models\PaymentTransaction;
use App\Models\ReportReason;
use App\Models\SellerRating;
use App\Models\SeoSetting;
use App\Models\Setting;
use App\Models\Slider;
use App\Models\SocialLogin;
use App\Models\State;
use App\Models\Tip;
use App\Models\User;
use App\Models\UserFcmToken;
use App\Models\UserPurchasedPackage;
use App\Models\UserReports;
use App\Models\VerificationField;
use App\Models\VerificationFieldValue;
use App\Models\VerificationRequest;
use App\Services\CachingService;
use App\Services\CurrencyFormatterService;
use App\Services\FileService;
use App\Services\HelperService;
use App\Services\NotificationService;
use App\Services\Payment\PaymentService;
use App\Services\ResponseService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Unique;
use JsonException;
use Stichoza\GoogleTranslate\GoogleTranslate;
use Throwable;
use Twilio\Rest\Client as TwilioRestClient;
class ApiController extends Controller
{
private string $uploadFolder;
public function __construct()
{
$this->uploadFolder = 'item_images';
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER) && ! empty($_SERVER['HTTP_AUTHORIZATION'])) {
$this->middleware('auth:sanctum');
}
}
public function getSystemSettings(Request $request)
{
try {
$query = Setting::select(['id', 'name', 'value', 'type']); // include 'id' to support translation loading
if (! empty($request->type)) {
$query->where('name', $request->type);
}
$settings = $query->with('translations')->get();
$tempRow = [];
foreach ($settings as $row) {
if (in_array($row->name, [
'account_holder_name',
'bank_name',
'account_number',
'ifsc_swift_code',
'bank_transfer_status',
'place_api_key',
'mail_password',
'mail_from_address',
'mail_username',
'twilio_account_sid',
'twilio_auth_token',
'twilio_my_phone_number',
'admin_user_email',
'admin_user_password',
'S3_aws_access_key_id',
's3_aws_secret_access_key',
's3_aws_default_region',
's3_aws_bucket',
's3_aws_url'
])) {
continue;
}
$tempRow[$row->name] = $row->translated_value ?? $row->value;
}
// --- determine current language ---
$languageCode = $request->header('Content-Language') ?? app()->getLocale();
$language = Language::where('code', $languageCode)->first();
if (! $language) {
$defaultLanguageCode = Setting::where('name', 'default_language')->value('value');
$language = Language::where('code', $defaultLanguageCode)->first();
}
$tempRow['demo_mode'] = config('app.demo_mode');
$tempRow['languages'] = CachingService::getLanguages();
$tempRow['admin'] = User::role('Super Admin')->select(['name', 'profile'])->first();
// 👇 add current language info
$tempRow['current_language'] = $language?->code ?? app()->getLocale();
ResponseService::successResponse(__('Data Fetched Successfully'), $tempRow);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getSystemSettings');
ResponseService::errorResponse();
}
}
public function userSignup(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'type' => 'required|in:email,google,phone,apple',
'firebase_id' => 'nullable|string',
'flag' => 'boolean',
'platform_type' => 'nullable|in:android,ios',
'region_code' => 'nullable|string',
'country_code' => 'nullable|string',
'mobile' => 'required_if:type,phone',
'password' => 'nullable|string|min:6',
'is_login' => 'boolean',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$type = $request->type;
$firebase_id = $request->firebase_id;
if ($type == 'phone' && ! empty($request->password) && $request->boolean('is_login')) {
$mobile = ltrim($request->mobile, '+');
$countryCode = ltrim($request->country_code, '+');
// 1⃣ First: try with mobile + country code
$mobileWithCode = $countryCode . $mobile;
// First try strict match
$user = User::where('type', 'phone')
->where('mobile', $mobile)
->withTrashed()
->first();
if (!$user) {
return ResponseService::errorResponse(
__('User not found. Please signup first.')
);
}
if ($user->deleted_at) {
return ResponseService::errorResponse(
__('User is deactivated. Please contact the administrator.')
);
}
// password exists check
if (empty($user->password)) {
return ResponseService::errorResponse(
__('Password is not set. Please set your password using the forgot password option.')
);
}
if (! Hash::check($request->password, $user->password)) {
return ResponseService::errorResponse(__('Invalid password.'));
}
}
$socialLogin = null;
if (! empty($firebase_id)) {
$socialLogin = SocialLogin::where('firebase_id', $firebase_id)
->where('type', $type)
->with('user', fn($q) => $q->withTrashed())
->whereHas('user', fn($q) => $q->role('User'))
->first();
}
if ($socialLogin && ! empty($socialLogin->user->deleted_at)) {
return ResponseService::errorResponse(__('User is deactivated. Please Contact the administrator'));
}
if (empty($socialLogin)) {
DB::beginTransaction();
if ($request->type == 'phone') {
$unique['mobile'] = $request->mobile;
} else {
$unique['email'] = $request->email;
}
$existingUser = User::withTrashed()->where($unique)->first();
if ($existingUser && $existingUser->trashed()) {
return ResponseService::errorResponse(__('Your account has been deactivated.'), null, config('constants.RESPONSE_CODE.DEACTIVATED_ACCOUNT'));
}
$dataToUpdate = [
'region_code' => $request->region_code ?? null,
'profile' => $request->hasFile('profile') ? $request->file('profile')->store('user_profile', 'public') : $request->profile,
];
if ($request->filled('password')) {
$dataToUpdate['password'] = Hash::make($request->password);
} else {
$dataToUpdate['password'] = '';
}
$user = User::updateOrCreate($unique, array_merge($request->all(), $dataToUpdate));
if (! empty($firebase_id)) {
SocialLogin::updateOrCreate(
['type' => $type, 'user_id' => $user->id],
['firebase_id' => $firebase_id]
);
}
$user->assignRole('User');
Auth::login($user);
$auth = User::find($user->id);
DB::commit();
} else {
if ($socialLogin->user && ! empty($countryCode)) {
$socialLogin->user->update([
'country_code' => ltrim($countryCode, '+'),
]);
}
// Social Login exists
Auth::login($socialLogin->user);
$auth = Auth::user();
}
if (! $auth->hasRole('User')) {
ResponseService::errorResponse(__('Invalid Login Credentials'), null, config('constants.RESPONSE_CODE.INVALID_LOGIN'));
}
if (! empty($request->fcm_id)) {
UserFcmToken::updateOrCreate(['fcm_token' => $request->fcm_id], ['user_id' => $auth->id, 'platform_type' => $request->platform_type, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now()]);
}
$auth->fcm_id = $request->fcm_id;
if (! empty($request->registration)) {
// If registration is passed then don't create token
$token = null;
} else {
$token = $auth->createToken($auth->name ?? '')->plainTextToken;
}
if ($auth) {
NotificationService::sendNewDeviceLoginEmail($auth, $request);
}
ResponseService::successResponse(__('User logged-in successfully'), $auth, ['token' => $token]);
} catch (Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th, 'API Controller -> Signup');
ResponseService::errorResponse();
}
}
public function resetPassword(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'number' => 'required|string',
'country_code' => 'required|string',
'new_password' => 'required|string|min:6',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$mobile = ltrim($request->number, '+');
$countryCode = ltrim($request->country_code, '+');
$mobileWithCode = $countryCode . $mobile;
$user = User::where('type', 'phone')
->where('mobile', $mobile)->first();
if (! $user) {
ResponseService::errorResponse(__('User not found.'));
}
$user->password = Hash::make($request->new_password);
$user->save();
ResponseService::successResponse(__('Password reset successfully.'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> resetPassword');
ResponseService::errorResponse();
}
}
public function getUser(Request $request)
{
try {
$auth = Auth::user();
if (! $auth) {
ResponseService::errorResponse(__('User not authenticated'));
}
if (! $auth->hasRole('User')) {
ResponseService::errorResponse(__('Invalid User Role'));
}
$user = User::find($auth->id);
ResponseService::successResponse(__('User fetched successfully'), $user);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> GetUser');
ResponseService::errorResponse();
}
}
public function updateProfile(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'name' => 'nullable|string',
'profile' => 'nullable|mimes:jpg,jpeg,png|max:7168',
'email' => 'nullable|email|unique:users,email,' . Auth::user()->id,
'mobile' => [
'nullable',
Rule::unique('users')->ignore(Auth::user()->id)->where(function ($query) use ($request) {
return $query->where('country_code', '+' . $request->country_code);
}),
],
'fcm_id' => 'nullable',
'address' => 'nullable',
'show_personal_details' => 'boolean',
'country_code' => 'nullable|string',
'region_code' => 'nullable|string',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$app_user = Auth::user();
// Email should not be updated when type is google.
$data = $app_user->type == 'google' ? $request->except('email') : $request->all();
if ($request->hasFile('profile')) {
$data['profile'] = FileService::compressAndReplace($request->file('profile'), 'profile', $app_user->getRawOriginal('profile'));
}
if (! empty($request->fcm_id)) {
UserFcmToken::updateOrCreate(['fcm_token' => $request->fcm_id], ['user_id' => $app_user->id, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now()]);
}
$data['show_personal_details'] = $request->show_personal_details;
$app_user->update($data);
ResponseService::successResponse(__('Profile Updated Successfully'), $app_user);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> updateProfile');
ResponseService::errorResponse();
}
}
public function getPackage(Request $request)
{
$validator = Validator::make($request->toArray(), [
'platform' => 'nullable|in:android,ios',
'type' => 'nullable|in:advertisement,item_listing',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$packages = Package::with(['translations', 'categories', 'package_categories'])->where('status', 1);
if (Auth::check()) {
$packages = $packages->with('user_purchased_packages', function ($q) {
$q->onlyActive();
});
}
if (isset($request->platform) && $request->platform == 'ios') {
$packages->whereNotNull('ios_product_id');
}
if (! empty($request->type)) {
$packages = $packages->where('type', $request->type);
}
$packages = $packages->orderBy('id', 'ASC')->get();
$formatter = app(CurrencyFormatterService::class);
$packages = $packages->map(function ($package) use ($formatter) {
if (Auth::check()) {
$package['is_active'] = count($package->user_purchased_packages) > 0;
} else {
$package['is_active'] = false;
}
// Only load package categories if package is not global
if ($package->is_global != 1) {
// Add selected category IDs
$package['selected_category_ids'] = $package->package_categories->pluck('category_id')->toArray();
// Add categories data
$package['categories'] = $package->categories->map(function ($category) {
return [
'id' => $category->id,
'name' => $category->name,
'slug' => $category->slug ?? null,
'parent_category_id' => $category->parent_category_id ?? null,
];
});
} else {
$package['selected_category_ids'] = [];
$package['categories'] = [];
}
// Decode key_points JSON if it exists
if (! empty($package->key_points)) {
$keyPoints = json_decode($package->key_points, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($keyPoints)) {
$package['key_points'] = $keyPoints;
} else {
// If it's already an array or invalid JSON, keep as is
$package['key_points'] = is_array($package->key_points) ? $package->key_points : [];
}
} else {
$package['key_points'] = [];
}
$iso_code = Setting::where('name', 'currency_iso_code')->value('value');
$symbol = Setting::where('name', 'currency_symbol')->value('value');
$position = Setting::where('name', 'currency_symbol_position')->value('value');
$currency = (object) [
'iso_code' => $iso_code,
'symbol' => $symbol,
'symbol_position' => $position,
];
// Formatted price fields
$package['formatted_final_price'] = $formatter->formatPrice($package->final_price ?? 0, $currency);
// If original/base price exists, format it too; fall back to final_price
$basePrice = $package->price ?? $package->final_price ?? 0;
$package['formatted_price'] = $formatter->formatPrice($basePrice, $currency);
// If listing_duration_type is null, set it to 'package' and use package duration
if (empty($package->listing_duration_type)) {
$package['listing_duration_type'] = 'package';
$package['listing_duration_days'] = $package->duration;
}
$package->user_purchased_packages = $package->user_purchased_packages->map(function ($purchased) use ($package) {
// 1. Calculate Duration/Days
if ($purchased->start_date && $purchased->end_date) {
$start = \Carbon\Carbon::parse($purchased->start_date);
$end = \Carbon\Carbon::parse($purchased->end_date);
$purchased['duration'] = $start->diffInDays($end);
} else {
$purchased['duration'] = "unlimited";
}
// 2. Map item_limit
$purchased['item_limit'] = $purchased->total_limit;
// 3. Fallback Logic for listing_duration_type & days
// Priority: Purchased Record -> Parent Package Record -> Default Setup
$purchased['listing_duration_type'] = $purchased->listing_duration_type
?? $package->listing_duration_type
?? 'package';
$days = $purchased->listing_duration_days ?? $package->listing_duration_days ?? $package->duration;
// If the final determined value is 0 or "0", return "unlimited"
$purchased['listing_duration_days'] = ($days == 0 && $days !== null) ? "unlimited" : $days;
return $purchased;
});
return $package;
});
ResponseService::successResponse(__('Data Fetched Successfully'), $packages);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getPackage');
ResponseService::errorResponse();
}
}
public function assignFreePackage(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'package_id' => 'required|exists:packages,id',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$user = Auth::user();
$package = Package::where(['final_price' => 0, 'id' => $request->package_id])->firstOrFail();
$activePackage = UserPurchasedPackage::where(['package_id' => $request->package_id, 'user_id' => Auth::user()->id])->first();
if (! empty($activePackage)) {
ResponseService::errorResponse(__('You already have purchased this package'));
}
UserPurchasedPackage::create([
'user_id' => $user->id,
'package_id' => $request->package_id,
'start_date' => Carbon::now(),
'total_limit' => $package->item_limit == 'unlimited' ? null : $package->item_limit,
'end_date' => $package->duration == 'unlimited' ? null : Carbon::now()->addDays($package->duration),
'listing_duration_type' => $package->listing_duration_type,
'listing_duration_days' => $package->listing_duration_days
]);
ResponseService::successResponse(__('Package Purchased Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> assignFreePackage');
ResponseService::errorResponse();
}
}
public function getLimits(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'package_type' => 'required|in:item_listing,advertisement',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$setting = Setting::where('name', 'free_ad_listing')->first()['value'];
if ($setting == 1 && $request->package_type != 'advertisement') {
return ResponseService::successResponse(__('User is allowed to create Advertisement'));
}
$user_package = UserPurchasedPackage::onlyActive()->whereHas('package', function ($q) use ($request) {
$q->where('type', $request->package_type);
})->count();
if ($user_package > 0) {
ResponseService::successResponse(__('User is allowed to create Advertisement'));
}
ResponseService::errorResponse(__('User is not allowed to create Advertisement'), $user_package);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getLimits');
ResponseService::errorResponse();
}
}
public function getCurrencies(Request $request)
{
try {
$countryName = $request->query('country');
$selectedCurrencyId = null;
// If country param is passed
if ($countryName) {
$country = Country::where('name', $countryName)
->select('id', 'currency_id')
->first();
$selectedCurrencyId = $country?->id;
}
$currencies = Currency::select(
'id',
'iso_code',
'symbol',
'symbol_position',
'country_id'
)
->orderBy('iso_code')
->get()
->map(function ($currency) use ($selectedCurrencyId) {
return [
'id' => $currency->id,
'iso_code' => $currency->iso_code,
'symbol' => $currency->symbol,
'position' => $currency->symbol_position,
'selected' => $selectedCurrencyId == $currency->country_id ? 1 : 0,
];
});
ResponseService::successResponse(__('Currency fetched Successfully'), $currencies);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCurrencies');
ResponseService::errorResponse();
}
}
public function addItem(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'name' => 'required',
'category_id' => 'required|integer',
'description' => 'required',
'latitude' => 'required',
'longitude' => 'required',
'address' => 'required',
'contact' => 'nullable|numeric',
'show_only_to_premium' => 'required|boolean',
'video_link' => 'nullable|url',
'gallery_images' => 'nullable|array|min:1',
'gallery_images.*' => 'nullable|mimes:jpeg,png,jpg|max:7168',
'image' => 'required|mimes:jpeg,png,jpg|max:7168',
'country' => 'required',
'state' => 'nullable',
'city' => 'required',
'custom_field_files' => 'nullable|array',
'custom_field_files.*' => 'nullable|mimes:jpeg,png,jpg,pdf,doc|max:7168',
'slug' => [
'nullable',
'regex:/^(?!-)(?!.*--)(?!.*-$)(?!-$)[a-z0-9-]+$/',
],
'region_code' => 'nullable|string',
'currency_id' => 'required|exists:currencies,id',
'all_category_ids' => 'nullable|array',
'all_category_ids.*' => 'integer|exists:categories,id',
]);
$translations = json_decode($request->input('translations', '{}'), true, 512, JSON_THROW_ON_ERROR);
if (! empty($translations)) {
foreach ($translations as $languageId => $translation) {
Validator::make($translation, [
'name' => 'required|string|max:255',
'slug' => 'nullable|regex:/^[a-z0-9-]+$/',
'description' => 'nullable|string',
'address' => 'nullable|string',
'video_link' => 'nullable|url',
'rejected_reason' => 'nullable|string',
'admin_edit_reason' => 'nullable|string',
])->validate();
}
}
$category = Category::findOrFail($request->category_id);
$isJobCategory = $category->is_job_category;
$isPriceOptional = $category->price_optional;
if ($isJobCategory || $isPriceOptional) {
$validator = Validator::make($request->all(), [
'min_salary' => 'nullable|numeric|min:0',
'max_salary' => 'nullable|numeric|gte:min_salary',
]);
} else {
$validator = Validator::make($request->all(), [
'price' => 'required|numeric|min:0',
]);
}
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
DB::beginTransaction();
$user = Auth::user();
$free_ad_listing = Setting::where('name', 'free_ad_listing')->value('value') ?? 0;
$auto_approve_item = Setting::where('name', 'auto_approve_item')->value('value') ?? 0;
if ($auto_approve_item == 1 || $user->auto_approve_item == 1) {
$status = 'approved';
} else {
$status = 'review';
}
// Get all active packages for the user
$user_packages = null;
$selectedPackageId = null;
$selectedUserPackage = null;
// Only check package if free_ad_listing is not enabled
if ($free_ad_listing != 1) {
// Get ALL active packages for the user
$user_packages = UserPurchasedPackage::onlyActive()
->whereHas('package', static function ($q) {
$q->where('type', 'item_listing');
})
->where('user_id', $user->id)
->with('package.package_categories')
->get();
if ($user_packages->isEmpty()) {
DB::rollBack();
ResponseService::errorResponse(__('No Active Package found for Advertisement Creation'));
}
// Get all_category_ids from request (should contain category and all parent categories)
$allCategoryIds = $request->input('all_category_ids', []);
// If it comes as comma-separated string, convert to array
if (is_string($allCategoryIds)) {
$allCategoryIds = array_filter(
array_map('intval', explode(',', $allCategoryIds))
);
}
// Ensure it's always an array
if (! is_array($allCategoryIds)) {
$allCategoryIds = [];
}
// If all_category_ids not provided, build it from category_id
if (empty($allCategoryIds) && $request->category_id) {
$allCategoryIds = [$request->category_id];
$currentCategoryId = $request->category_id;
// Traverse up the parent chain
while ($currentCategoryId) {
$parentCategory = Category::find($currentCategoryId);
if ($parentCategory && $parentCategory->parent_category_id) {
$allCategoryIds[] = $parentCategory->parent_category_id;
$currentCategoryId = $parentCategory->parent_category_id;
} else {
break;
}
}
$allCategoryIds = array_unique($allCategoryIds);
}
// Check for global package first
$globalPackage = $user_packages->firstWhere(function ($userPackage) {
return $userPackage->package && $userPackage->package->is_global == 1;
});
if ($globalPackage) {
// Use global package
$selectedPackageId = $globalPackage->package_id;
$selectedUserPackage = $globalPackage;
} else {
// No global package, check if any package contains any category from all_category_ids
$matchingPackage = null;
foreach ($user_packages as $user_package) {
if ($user_package->package && $user_package->package->is_global != 1) {
$packageCategoryIds = $user_package->package->package_categories
->pluck('category_id')
->toArray();
// Check if any category from all_category_ids is in this package
$hasMatchingCategory = ! empty(array_intersect($allCategoryIds, $packageCategoryIds));
if ($hasMatchingCategory) {
$matchingPackage = $user_package;
break;
}
}
}
if ($matchingPackage) {
$selectedPackageId = $matchingPackage->package_id;
$selectedUserPackage = $matchingPackage;
} else {
DB::rollBack();
ResponseService::errorResponse(__('Selected category is not available in your package'));
}
}
}
// Increment used_limit for the selected package
if ($selectedUserPackage) {
$selectedUserPackage->used_limit++;
$selectedUserPackage->save();
}
$slug = trim($request->input('slug') ?? '');
$slug = preg_replace('/[^a-z0-9]+/i', '-', strtolower($slug));
$slug = trim($slug, '-');
if (empty($slug)) {
$slug = HelperService::generateRandomSlug();
}
$uniqueSlug = HelperService::generateUniqueSlug(new Item, $slug);
// Calculate expiry date based on package listing duration
$package = $selectedPackageId ? Package::find($selectedPackageId) : null;
$expiryDate = HelperService::calculateItemExpiryDate($package, $selectedUserPackage);
$data = [
...$request->all(),
'name' => $request->name,
'slug' => $uniqueSlug,
'status' => $status,
'active' => 'deactive',
'user_id' => $user->id,
'package_id' => $selectedPackageId ?? null,
'expiry_date' => $expiryDate,
];
if ($request->hasFile('image')) {
$data['image'] = FileService::compressAndUpload($request->file('image'), $this->uploadFolder, true);
}
$item = Item::create($data);
if (! empty($translations)) {
foreach ($translations as $languageId => $translationData) {
// Optional: Check if language ID exists
if (Language::where('id', $languageId)->exists()) {
$item->translations()->create([
'language_id' => $languageId,
'name' => $translationData['name'],
'description' => $translationData['description'] ?? '',
'address' => $translationData['address'] ?? '',
'rejected_reason' => $translationData['rejected_reason'] ?? null,
'admin_edit_reason' => $translationData['admin_edit_reason'] ?? null,
]);
}
}
}
if ($request->hasFile('gallery_images')) {
$galleryImages = [];
foreach ($request->file('gallery_images') as $file) {
$galleryImages[] = [
'image' => FileService::compressAndUpload($file, $this->uploadFolder, true),
'item_id' => $item->id,
'created_at' => time(),
'updated_at' => time(),
];
}
if (count($galleryImages) > 0) {
ItemImages::insert($galleryImages);
}
}
if ($request->custom_fields) {
$itemCustomFieldValues = [];
foreach (json_decode($request->custom_fields, true, 512, JSON_THROW_ON_ERROR) as $key => $custom_field) {
$itemCustomFieldValues[] = [
'item_id' => $item->id,
'language_id' => 1,
'custom_field_id' => $key,
'value' => json_encode($custom_field, JSON_THROW_ON_ERROR),
'created_at' => time(),
'updated_at' => time(),
];
}
if (count($itemCustomFieldValues) > 0) {
ItemCustomFieldValue::insert($itemCustomFieldValues);
}
}
if ($request->custom_field_files) {
$itemCustomFieldValues = [];
foreach ($request->custom_field_files as $key => $file) {
$itemCustomFieldValues[] = [
'item_id' => $item->id,
'language_id' => 1,
'custom_field_id' => $key,
'value' => ! empty($file) ? FileService::upload($file, 'custom_fields_files') : '',
'created_at' => time(),
'updated_at' => time(),
];
}
if (count($itemCustomFieldValues) > 0) {
ItemCustomFieldValue::insert($itemCustomFieldValues);
}
}
// Handle Translated Custom Field Values
if ($request->has('custom_field_translations')) {
$customFieldTranslations = $request->input('custom_field_translations');
if (! is_array($customFieldTranslations)) {
$customFieldTranslations = html_entity_decode($customFieldTranslations);
$customFieldTranslations = json_decode($customFieldTranslations, true, 512, JSON_THROW_ON_ERROR);
}
$translatedEntries = [];
foreach ($customFieldTranslations as $languageId => $fieldsByCustomField) {
foreach ($fieldsByCustomField as $customFieldId => $translatedValue) {
$translatedEntries[] = [
'item_id' => $item->id,
'custom_field_id' => $customFieldId,
'language_id' => $languageId,
'value' => json_encode($translatedValue, JSON_THROW_ON_ERROR),
'created_at' => now(),
'updated_at' => now(),
];
}
}
if (! empty($translatedEntries)) {
ItemCustomFieldValue::insert($translatedEntries);
}
}
// Optimize: Only load essential relationships to prevent memory timeout
$result = Item::with([
'user:id,name,email,mobile,profile,country_code',
'category:id,name,image,is_job_category,price_optional',
'gallery_images:id,image,item_id',
'area:id,name',
'translations',
])
->where('items.id', $item->id)
->first();
// Load expensive relationships only if needed (lazy loading) to prevent memory timeout
if ($result) {
$result->loadMissing(['featured_items', 'favourites']);
// Only load custom fields if they exist (avoid loading all nested translations)
if ($result->item_custom_field_values()->exists()) {
$result->load('item_custom_field_values.custom_field:id,name,type');
}
}
$result = new ItemCollection(collect([$result]));
DB::commit();
ResponseService::successResponse(__('Advertisement Added Successfully'), $result);
} catch (Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th, 'API Controller -> addItem');
ResponseService::errorResponse();
}
}
public function getItem(Request $request)
{
$validator = Validator::make($request->all(), [
'limit' => 'nullable|integer',
'offset' => 'nullable|integer',
'id' => 'nullable',
'custom_fields' => 'nullable',
'slug' => 'nullable|string',
'category_id' => 'nullable',
'user_id' => 'nullable',
'min_price' => 'nullable',
'max_price' => 'nullable',
'sort_by' => 'nullable|in:new-to-old,old-to-new,price-high-to-low,price-low-to-high,popular_items',
'posted_since' => 'nullable|in:all-time,today,within-1-week,within-2-week,within-1-month,within-3-month',
'current_page' => 'nullable|string',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
// TODO : need to simplify this whole module
$maxPrice = Item::max('price') ?? 0;
$sql = Item::with(
'user:id,name,email,mobile,profile,created_at,is_verified,show_personal_details,country_code',
'category:id,name,image,is_job_category,price_optional',
'gallery_images:id,image,item_id',
'featured_items',
'favourites',
'item_custom_field_values.custom_field.translations',
'area:id,name',
'job_applications',
'translations',
'currency',
)
->withCount('featured_items')
->withCount('job_applications')
->select('items.*')
->whereHas('category', function ($q) {
$q->where('status', '!=', 0)
->where(function ($query) {
// Either no parent or parent status != 0
$query->whereDoesntHave('parent') // no parent category
->orWhereHas('parent', function ($q2) {
$q2->where('status', '!=', 0);
});
});
})
->when($request->id, function ($sql) use ($request) {
$sql->where('items.id', $request->id);
})->when(($request->category_id), function ($sql) use ($request) {
$category = Category::where('id', $request->category_id)->with('children')->first();
$categoryIDS = HelperService::findAllCategoryIds(collect([$category]));
return $sql->whereIn('category_id', $categoryIDS);
})->when(($request->category_slug), function ($sql) use ($request) {
$category = Category::where('slug', $request->category_slug)->with('children')->first();
$categoryIDS = HelperService::findAllCategoryIds(collect([$category]));
return $sql->whereIn('category_id', $categoryIDS);
})->when((isset($request->min_price) || isset($request->max_price)), function ($sql) use ($request, $maxPrice) {
$min_price = $request->min_price ?? 0;
$max_price = $request->max_price ?? $maxPrice;
return $sql->whereBetween('price', [$min_price, $max_price]);
})->when($request->posted_since, function ($sql) use ($request) {
return match ($request->posted_since) {
// Qualify column to avoid ambiguity once joins are applied (e.g. featured_items)
'today' => $sql->whereDate('items.created_at', '>=', now()),
'within-1-week' => $sql->whereDate('items.created_at', '>=', now()->subDays(7)),
'within-2-week' => $sql->whereDate('items.created_at', '>=', now()->subDays(14)),
'within-1-month' => $sql->whereDate('items.created_at', '>=', now()->subMonths()),
'within-3-month' => $sql->whereDate('items.created_at', '>=', now()->subMonths(3)),
default => $sql
};
})->when($request->area_id, function ($sql) use ($request) {
return $sql->where('area_id', $request->area_id);
})->when($request->user_id, function ($sql) use ($request) {
return $sql->where('user_id', $request->user_id);
})->when($request->slug, function ($sql) use ($request) {
return $sql->where('slug', $request->slug);
});
if ($request->sort_by == 'new-to-old') {
$sql->orderBy('items.id', 'DESC');
} elseif ($request->sort_by == 'old-to-new') {
$sql->orderBy('items.id', 'ASC');
} elseif ($request->sort_by == 'price-high-to-low') {
$sql->orderByRaw('
COALESCE(price, max_salary, min_salary, 0) DESC
');
} elseif ($request->sort_by == 'price-low-to-high') {
$sql->orderByRaw('
COALESCE(price, min_salary, max_salary, 0) ASC
');
} elseif ($request->sort_by == 'popular_items') {
$sql->orderBy('clicks', 'DESC');
} else {
$sql->orderBy('items.id', 'DESC');
}
// Status
if (! empty($request->status)) {
if (in_array($request->status, ['review', 'approved', 'rejected', 'sold out', 'soft rejected', 'permanent rejected', 'resubmitted'])) {
$sql->where('status', $request->status)->getNonExpiredItems()->whereNull('deleted_at');
} elseif ($request->status == 'inactive') {
// If status is inactive then display only trashed items
$sql->onlyTrashed()->getNonExpiredItems();
} elseif ($request->status == 'featured') {
// If status is featured then display only featured items
$sql->where('status', 'approved')->has('featured_items')->getNonExpiredItems();
} elseif ($request->status == 'expired') {
$sql->whereNotNull('expiry_date')
->where('expiry_date', '<', Carbon::now())->whereNull('deleted_at');
}
}
// Feature Section Filtration
// Only apply feature section filters if user hasn't provided conflicting filters
// User filters should override feature section defaults
if (! empty($request->featured_section_id) || ! empty($request->featured_section_slug)) {
if (! empty($request->featured_section_id)) {
$featuredSection = FeatureSection::findOrFail($request->featured_section_id);
} else {
$featuredSection = FeatureSection::where('slug', $request->featured_section_slug)->firstOrFail();
}
// Check if user has provided filters that should override feature section filters
$hasUserPriceFilter = isset($request->min_price) || isset($request->max_price);
$hasUserSortFilter = ! empty($request->sort_by);
$hasUserCategoryFilter = ! empty($request->category_id) || ! empty($request->category_slug);
// Apply feature section filters only if user hasn't provided conflicting filters
$sql = match ($featuredSection->filter) {
'price_criteria' => $hasUserPriceFilter
? $sql // User price filter already applied, skip feature section price filter
// : $sql->whereBetween('price', [$featuredSection->min_price, $featuredSection->max_price]),
: $sql->where(function ($query) use ($featuredSection) {
$query->whereBetween('price', [$featuredSection->min_price, $featuredSection->max_price])
->orWhere(function ($q) use ($featuredSection) {
$q->whereBetween('min_salary', [$featuredSection->min_price, $featuredSection->max_price])
->whereBetween('max_salary', [$featuredSection->min_price, $featuredSection->max_price]);
});
}),
'most_viewed' => $hasUserSortFilter
? $sql // User sort already applied, skip feature section sort
: $sql->reorder()->orderBy('clicks', 'DESC'),
'category_criteria' => $hasUserCategoryFilter
? $sql // User category filter already applied, skip feature section category filter
: (static function () use ($featuredSection, $sql) {
$category = Category::whereIn('id', explode(',', $featuredSection->value))->with('children')->get();
$categoryIDS = HelperService::findAllCategoryIds($category);
return $sql->whereIn('category_id', $categoryIDS);
})(),
'most_liked' => $hasUserSortFilter
? $sql // User sort already applied, skip feature section sort
: $sql->reorder()->withCount('favourites'), // ->orderBy('favourites_count', 'DESC'),
'featured_ads' => $sql->where('status', 'approved')->has('featured_items')->getNonExpiredItems(),
};
}
if (! empty($request->search)) {
$sql->search($request->search);
}
function removeBackslashesRecursive($data)
{
$cleaned = [];
foreach ($data as $key => $value) {
$cleanKey = stripslashes($key);
if (is_array($value)) {
$cleaned[$cleanKey] = removeBackslashesRecursive($value);
} else {
$cleaned[$cleanKey] = stripslashes($value);
}
}
return $cleaned;
}
// Optimize custom fields filtering - use whereHas instead of joins for better performance
$cleanedParameters = removeBackslashesRecursive($request->all());
if (! empty($cleanedParameters['custom_fields'])) {
$customFields = $cleanedParameters['custom_fields'];
$sql->whereHas('item_custom_field_values', function ($query) use ($customFields) {
$query->whereIn('custom_field_id', array_keys($customFields));
foreach ($customFields as $customFieldId => $value) {
$query->where(function ($q) use ($customFieldId, $value) {
if (is_array($value)) {
foreach ($value as $arrayValue) {
$q->orWhere(function ($subQ) use ($customFieldId, $arrayValue) {
$subQ->where('custom_field_id', $customFieldId)
->where('value', 'LIKE', '%' . trim($arrayValue) . '%');
});
}
} else {
$q->orWhere(function ($subQ) use ($customFieldId, $value) {
$subQ->where('custom_field_id', $customFieldId)
->where('value', 'LIKE', '%' . trim($value) . '%');
});
}
});
}
}, '>=', count($customFields));
}
if (Auth::check()) {
$sql->with(['item_offers' => function ($q) {
$q->where('buyer_id', Auth::user()->id);
}, 'user_reports' => function ($q) {
$q->where('user_id', Auth::user()->id);
}]);
$currentURI = explode('?', $request->getRequestUri(), 2);
if ($currentURI[0] == '/api/my-items') { // TODO: This if condition is temporary fix. Need something better
$sql->where(['user_id' => Auth::user()->id])->withTrashed();
} else {
$sql->where('status', 'approved')->has('user')->onlyNonBlockedUsers()->getNonExpiredItems();
}
} else {
// Other users should only get approved items
$sql->where('status', 'approved')->getNonExpiredItems();
}
// Helper function to apply auth filters
$applyAuthFilters = function ($query) use ($request) {
if (Auth::check()) {
$query->with(['item_offers' => function ($q) {
$q->where('buyer_id', Auth::user()->id);
}, 'user_reports' => function ($q) {
$q->where('user_id', Auth::user()->id);
}]);
$currentURI = explode('?', $request->getRequestUri(), 2);
if ($currentURI[0] == '/api/my-items') {
$query->where(['user_id' => Auth::user()->id])->withTrashed();
} else {
$query->where('status', 'approved')->has('user')->onlyNonBlockedUsers()->getNonExpiredItems();
}
} else {
$query->where('status', 'approved')->getNonExpiredItems();
}
return $query;
};
// Apply location filters using shared function
$locationResult = HelperService::applyLocationFilters($sql, $request, $applyAuthFilters);
$sql = $locationResult['query'];
$locationMessage = $locationResult['message'];
$baseQuery = clone $sql;
// FEATURED QUERY
$featuredQuery = clone $baseQuery;
$featuredQuery
->whereHas('featured_items', function ($q) {
$q->whereDate('start_date', '<=', now())
->where(function ($q) {
$q->whereNull('end_date')
->orWhereDate('end_date', '>=', now());
});
})
->join('featured_items as fi', 'fi.item_id', '=', 'items.id')
->orderBy('fi.created_at', 'DESC')
// IMPORTANT: do not override select() here because location filters may have added
// `distance` via selectRaw(... AS distance). Overriding select would remove it and
// break ORDER BY distance with "Unknown column 'distance'".
->addSelect('items.*');
// NORMAL QUERY
$normalQuery = clone $baseQuery;
$normalQuery
->whereDoesntHave('featured_items', function ($q) {
$q->whereDate('start_date', '<=', now())
->where(function ($q) {
$q->whereNull('end_date')
->orWhereDate('end_date', '>=', now());
});
})
->orderBy('items.created_at', 'DESC');
// Cast to integers to prevent "string - int" error
$limit = (int) ($request->limit ?? 10);
$page = (int) ($request->page ?? 1);
$totalFeatured = (clone $featuredQuery)->distinct('items.id')->count('items.id');
$totalNormal = (clone $normalQuery)->count();
$totalItems = $totalFeatured + $totalNormal;
// Ensure totalPages is at least 1 to avoid division by zero
$totalPages = (int) max(1, ceil($totalItems / $limit));
// Distribute featured items across pages
$featuredPerPage = (int) ceil($totalFeatured / $totalPages);
// Now subtraction will work because both are integers
$normalPerPage = $limit - $featuredPerPage;
if ($normalPerPage < 0) {
$normalPerPage = 0;
}
$featuredOffset = ($page - 1) * $featuredPerPage;
$normalOffset = ($page - 1) * $normalPerPage;
if ($normalPerPage < 0) {
$normalPerPage = 0;
}
$featuredItems = $featuredQuery
->skip($featuredOffset)
->take($featuredPerPage)
->get();
$normalItems = $normalQuery
->skip($normalOffset)
->take($normalPerPage)
->get();
$items = $featuredItems->merge($normalItems);
$paginator = new LengthAwarePaginator(
$items,
$totalItems,
$limit,
$page,
[
'path' => request()->url(),
'query' => request()->query(),
]
);
// Execute query and get results
if (! empty($request->id)) {
/*
* Collection does not support first OR find method's result as of now. It's a part of R&D
* So currently using this shortcut method get() to fetch the first data
*/
$result = $sql->get();
// dd($result);
if (count($result) == 0) {
ResponseService::errorResponse(__('No item Found'));
}
} else {
if (! empty($request->limit)) {
$result = $sql->paginate($request->limit);
} else {
$result = $sql->paginate();
}
}
// Prepare response with location message if applicable
$responseData = new ItemCollection($paginator);
// Use location message if available, otherwise use default success message
$responseMessage = ! empty($locationMessage) ? $locationMessage : __('Advertisement Fetched Successfully');
// return response()->json($responseData);
ResponseService::successResponse($responseMessage, $responseData);
// if (!empty($request->id)) {
// /*
// * Collection does not support first OR find method's result as of now. It's a part of R&D
// * So currently using this shortcut method get() to fetch the first data
// */
// $result = $sql->get();
// if (count($result) == 0) {
// ResponseService::errorResponse(__('No item Found'));
// }
// } else {
// if (!empty($request->limit)) {
// $result = $sql->paginate($request->limit);
// } else {
// $result = $sql->paginate();
// }
// }
// // Add three regular items
// for ($i = 0; $i < 3 && $regularIndex < $regularItemCount; $i++) {
// $items->push($regularItems[$regularIndex]);
// $regularIndex++;
// }
//
// // Add one featured item if available
// if ($featuredIndex < $featuredItemCount) {
// $items->push($featuredItems[$featuredIndex]);
// $featuredIndex++;
// }
// }
// Return success response with the fetched items
// ResponseService::successResponse(__('Advertisement Fetched Successfully'), new ItemCollection($result));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getItem');
ResponseService::errorResponse();
}
}
public function updateItem(Request $request)
{
$validator = Validator::make($request->all(), [
'id' => 'required',
'name' => 'nullable',
'slug' => [
'nullable',
'regex:/^(?!-)(?!.*--)(?!.*-$)(?!-$)[a-z0-9-]+$/',
],
'price' => 'nullable',
'description' => 'nullable',
'latitude' => 'nullable',
'longitude' => 'nullable',
'address' => 'nullable',
'contact' => 'nullable',
'image' => 'nullable|mimes:jpeg,jpg,png|max:7168',
'custom_fields' => 'nullable',
'custom_field_files' => 'nullable|array',
'custom_field_files.*' => 'nullable|mimes:jpeg,png,jpg,pdf,doc|max:7168',
'gallery_images' => 'nullable|array',
'currency_id' => 'nullable|exists:currencies,id',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
DB::beginTransaction();
try {
$item = Item::owner()->findOrFail($request->id);
$auto_approve_item = Setting::where('name', 'auto_approve_edited_item')->value('value') ?? 0;
if ($auto_approve_item == 1) {
$status = 'approved';
} else {
$status = 'review';
}
$slugInput = $request->input('slug') ?? '';
$slug = preg_replace('/[^a-z0-9]+/i', '-', strtolower(trim($slugInput)));
$slug = trim($slug, '-');
// If slug is empty after cleaning, use existing item slug
if (empty($slug)) {
$slug = $item->slug;
}
// Generate unique slug
$uniqueSlug = HelperService::generateUniqueSlug(new Item, $slug, $request->id);
$data = $request->all();
$data['slug'] = $uniqueSlug;
$data['status'] = $status;
if ($request->hasFile('image')) {
$data['image'] = FileService::compressAndReplace($request->file('image'), $this->uploadFolder, $item->getRawOriginal('image'), true);
}
$item->update($data);
// Update or create item translations
$translations = json_decode($request->input('translations', '{}'), true, 512, JSON_THROW_ON_ERROR);
if (! empty($translations)) {
foreach ($translations as $languageId => $translationData) {
if (Language::where('id', $languageId)->exists()) {
$item->translations()->updateOrCreate(
['language_id' => $languageId],
[
'name' => $translationData['name'],
'description' => $translationData['description'] ?? '',
'address' => $translationData['address'] ?? '',
'rejected_reason' => $translationData['rejected_reason'] ?? null,
'admin_edit_reason' => $translationData['admin_edit_reason'] ?? null,
]
);
}
}
}
// Update Custom Field values for item
if ($request->custom_fields) {
$itemCustomFieldValues = [];
foreach (json_decode($request->custom_fields, true, 512, JSON_THROW_ON_ERROR) as $key => $custom_field) {
$itemCustomFieldValues[] = [
'item_id' => $item->id,
'custom_field_id' => $key,
'value' => json_encode($custom_field, JSON_THROW_ON_ERROR),
'updated_at' => time(),
];
}
if (count($itemCustomFieldValues) > 0) {
ItemCustomFieldValue::upsert($itemCustomFieldValues, ['item_id', 'custom_field_id'], ['value', 'updated_at']);
}
}
// Add new gallery images
if ($request->hasFile('gallery_images')) {
$galleryImages = [];
foreach ($request->file('gallery_images') as $file) {
$galleryImages[] = [
'image' => FileService::compressAndUpload($file, $this->uploadFolder, true),
'item_id' => $item->id,
'created_at' => time(),
'updated_at' => time(),
];
}
if (count($galleryImages) > 0) {
ItemImages::insert($galleryImages);
}
}
if ($request->custom_field_files) {
$itemCustomFieldValues = [];
foreach ($request->custom_field_files as $key => $file) {
$value = ItemCustomFieldValue::where(['item_id' => $item->id, 'custom_field_id' => $key])->first();
if (! empty($value)) {
$file = FileService::replace($file, 'custom_fields_files', $value->getRawOriginal('value'));
} else {
$file = '';
}
$itemCustomFieldValues[] = [
'item_id' => $item->id,
'language_id' => 1,
'custom_field_id' => $key,
'value' => $file,
'updated_at' => time(),
];
}
if (count($itemCustomFieldValues) > 0) {
ItemCustomFieldValue::updateOrCreate(
['item_id' => $item->id, 'custom_field_id' => $key],
['value' => $file, 'language_id' => 1, 'updated_at' => time()]
);
}
}
// Update or insert custom field translations
if ($request->has('custom_field_translations')) {
$customFieldTranslations = $request->input('custom_field_translations');
if (! is_array($customFieldTranslations)) {
$customFieldTranslations = html_entity_decode($customFieldTranslations);
$customFieldTranslations = json_decode($customFieldTranslations, true, 512, JSON_THROW_ON_ERROR);
}
$translatedEntries = [];
foreach ($customFieldTranslations as $languageId => $fieldsByCustomField) {
foreach ($fieldsByCustomField as $customFieldId => $translatedValue) {
$translatedEntries[] = [
'item_id' => $item->id,
'custom_field_id' => $customFieldId,
'language_id' => $languageId,
'value' => json_encode($translatedValue, JSON_THROW_ON_ERROR),
'updated_at' => now(),
'created_at' => now(),
];
}
}
if (! empty($translatedEntries)) {
// Ensure combination is unique
ItemCustomFieldValue::upsert(
$translatedEntries,
['item_id', 'custom_field_id', 'language_id'], // unique keys
['value', 'updated_at']
);
}
}
// Delete gallery images
if (! empty($request->delete_item_image_id)) {
$item_ids = explode(',', $request->delete_item_image_id);
foreach (ItemImages::whereIn('id', $item_ids)->get() as $itemImage) {
FileService::delete($itemImage->getRawOriginal('image'));
$itemImage->delete();
}
}
$result = Item::with('user:id,name,email,mobile,profile,country_code', 'category:id,name,image,is_job_category,price_optional', 'gallery_images:id,image,item_id', 'featured_items', 'favourites', 'item_custom_field_values.custom_field.translations', 'area', 'translations')->where('items.id', $item->id)->get();
/*
* Collection does not support first OR find method's result as of now. It's a part of R&D
* So currently using this shortcut method
*/
$result = new ItemCollection($result);
DB::commit();
ResponseService::successResponse(__('Advertisement Fetched Successfully'), $result);
} catch (Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th, 'API Controller -> updateItem');
ResponseService::errorResponse();
}
}
public function deleteItem(Request $request)
{
try {
// Validation rules
$rules = [
'item_id' => 'nullable|exists:items,id',
'item_ids' => 'nullable|string', // comma-separated IDs
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
// Normalize IDs
$itemIds = [];
if ($request->filled('item_id')) {
$itemIds[] = $request->item_id;
}
if ($request->filled('item_ids')) {
$ids = explode(',', $request->item_ids);
$ids = array_map('trim', $ids);
$ids = array_filter($ids, 'strlen');
$itemIds = array_merge($itemIds, $ids);
}
if (empty($itemIds)) {
return ResponseService::validationError(__('Please provide item_id or item_ids'));
}
$results = [];
foreach ($itemIds as $id) {
try {
$item = Item::owner()->with('gallery_images')->withTrashed()->findOrFail($id);
// Delete main image
FileService::delete($item->getRawOriginal('image'));
// Delete gallery images
if ($item->gallery_images->count() > 0) {
foreach ($item->gallery_images as $gallery) {
FileService::delete($gallery->getRawOriginal('image'));
}
}
// Delete item
$item->forceDelete();
$results[] = [
'status' => 'success',
'message' => __('Advertisement Deleted Successfully'),
'item_id' => $id,
];
} catch (Throwable $e) {
$results[] = [
'status' => 'failed',
'message' => __('Failed to delete item'),
'item_id' => $id,
];
}
}
// Single item response
if (count($results) === 1) {
if ($results[0]['status'] === 'success') {
return ResponseService::successResponse(
__('Advertisement Deleted Successfully'),
['id' => $results[0]['item_id']]
);
} else {
return ResponseService::errorResponse($results[0]['message']);
}
}
// Multiple items response
return ResponseService::successResponse(__('Items processed successfully'), $results);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> deleteItem');
return ResponseService::errorResponse();
}
}
public function updateItemStatus(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'required|integer',
'status' => 'required|in:sold out,inactive,active,resubmitted',
// 'sold_to' => 'required_if:status,==,sold out|integer'
'sold_to' => 'nullable|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$item = Item::owner()->whereNotIn('status', ['review', 'permanent rejected'])->withTrashed()->findOrFail($request->item_id);
if ($item->status == 'permanent rejected' && $request->status == 'resubmitted') {
ResponseService::errorResponse(__('This Advertisement is permanently rejected and cannot be resubmitted'));
}
if ($request->status == 'inactive') {
$item->delete();
} elseif ($request->status == 'active') {
$item->restore();
$item->update(['status' => 'review']);
} elseif ($request->status == 'sold out') {
$item->update([
'status' => 'sold out',
'sold_to' => $request->sold_to,
]);
} else {
$item->update(['status' => $request->status]);
}
ResponseService::successResponse(__('Advertisement Status Updated Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'ItemController -> updateItemStatus');
ResponseService::errorResponse(__('Something Went Wrong'));
}
}
public function getItemBuyerList(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'required|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$buyer_ids = ItemOffer::where('item_id', $request->item_id)->select('buyer_id')->pluck('buyer_id');
$users = User::select(['id', 'name', 'profile'])->whereIn('id', $buyer_ids)->get();
ResponseService::successResponse(__('Buyer List fetched Successfully'), $users);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'ItemController -> updateItemStatus');
ResponseService::errorResponse(__('Something Went Wrong'));
}
}
public function getSubCategories(Request $request)
{
$validator = Validator::make($request->all(), [
'category_id' => 'nullable|integer',
'listing' => 'nullable|in:0,1',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$subcategoryLimit = 15; // Limit subcategories per level for display
// Get package category IDs for validation when category_id is passed with listing=1
$packageCategoryIds = [];
$hasGlobalPackage = false;
$freeAdListing = Setting::where('name', 'free_ad_listing')->value('value') ?? 0;
// Validate access when category_id is passed and listing=1
if (! empty($request->category_id) && ! empty($request->listing) && $request->listing == 1 && $freeAdListing != 1 && Auth::check()) {
// Get ALL active packages for the user (user can have multiple packages)
$userPackages = UserPurchasedPackage::onlyActive()
->whereHas('package', static function ($q) {
$q->where('type', 'item_listing');
})
->where('user_id', Auth::id())
->with('package.package_categories')
->get();
// Check if user has any global package
foreach ($userPackages as $userPackage) {
if ($userPackage->package && $userPackage->package->is_global == 1) {
$hasGlobalPackage = true;
break;
}
}
// If no global package, collect category IDs from non-global packages
if (! $hasGlobalPackage) {
foreach ($userPackages as $userPackage) {
if ($userPackage->package && $userPackage->package->is_global != 1 && $userPackage->package->package_categories) {
$packageCatIds = $userPackage->package->package_categories->pluck('category_id')->toArray();
$packageCategoryIds = array_merge($packageCategoryIds, $packageCatIds);
}
}
$packageCategoryIds = array_unique($packageCategoryIds);
}
// dd($packageCategoryIds);
// Get the requested category
$requestedCategory = Category::find($request->category_id);
$requestedCategory = Category::find($request->category_id);
if ($requestedCategory) {
// Check if user has access to the category or any of its subcategories
if (! $hasGlobalPackage) {
$hasAccess = false;
// 1⃣ Direct category access
if (in_array($requestedCategory->id, $packageCategoryIds)) {
$hasAccess = true;
}
// 2⃣ Check ancestors (parent → root)
if (! $hasAccess) {
$ancestorIds = $requestedCategory->ancestors()
->where('status', 1)
->pluck('id')
->toArray();
if (! empty(array_intersect($ancestorIds, $packageCategoryIds))) {
$hasAccess = true;
}
}
// 3⃣ Check descendants (children → deep)
if (! $hasAccess) {
$descendantIds = $requestedCategory->descendants()
->where('status', 1)
->pluck('id')
->toArray();
if (! empty(array_intersect($descendantIds, $packageCategoryIds))) {
$hasAccess = true;
}
}
// ❌ No access
if (! $hasAccess) {
ResponseService::errorResponse(
__('You need to purchase a package for this category to access it.')
);
}
}
// If hasGlobalPackage is true, user has access to all categories
} else {
ResponseService::errorResponse(__('Category not found.'));
}
}
// Recursive function to load subcategories recursively
$loadSubcategoriesRecursively = function ($query, $depth = 0, $maxDepth = 5) use (&$loadSubcategoriesRecursively) {
if ($depth >= $maxDepth) {
return;
}
$query->where('status', 1)
->orderBy('sequence', 'ASC')
->with('translations')
->withCount(['approved_items', 'subcategories' => function ($q) {
$q->where('status', 1);
}]);
// Recursively load nested subcategories
$query->with(['subcategories' => function ($subQuery) use (&$loadSubcategoriesRecursively, $depth, $maxDepth) {
$loadSubcategoriesRecursively($subQuery, $depth + 1, $maxDepth);
}]);
};
// Helper to limit subcategories after loading (for performance - limits displayed subcategories)
// Note: Item counts are calculated from ALL descendants, not just loaded ones
$limitSubcategoriesAfterLoad = function ($categories, $limit, $depth = 0, $maxDepth = 5) use (&$limitSubcategoriesAfterLoad) {
if ($depth >= $maxDepth || empty($categories)) {
return;
}
foreach ($categories as $category) {
if ($category->relationLoaded('subcategories') && $category->subcategories->isNotEmpty()) {
// Limit to first N subcategories for display
$limitedSubcategories = $category->subcategories->take($limit)->values();
$category->setRelation('subcategories', $limitedSubcategories);
// Recursively limit nested subcategories
$limitSubcategoriesAfterLoad($category->subcategories, $limit, $depth + 1, $maxDepth);
}
}
};
$sql = Category::withCount(['subcategories' => function ($q) {
$q->where('status', 1);
}])->with('translations')->where(['status' => 1])->orderBy('sequence', 'ASC')
->with(['subcategories' => function ($query) use (&$loadSubcategoriesRecursively) {
$loadSubcategoriesRecursively($query, 0);
}]);
$parentCategory = null;
// Return all categories regardless of listing parameter
if (! empty($request->category_id)) {
$sql = $sql->where('parent_category_id', $request->category_id);
$parentCategory = Category::find($request->category_id);
} elseif (! empty($request->slug)) {
$parentCategory = Category::where('slug', $request->slug)->firstOrFail();
$sql = $sql->where('parent_category_id', $parentCategory->id);
} else {
$sql = $sql->whereNull('parent_category_id');
}
$sql = $sql->paginate();
// Limit subcategories to 15 per level (for performance)
// Note: getAllItemsCountAttribute counts from ALL descendants, so count is accurate
$limitSubcategoriesAfterLoad($sql->items(), $subcategoryLimit);
// Calculate all_items_count for each category (includes ALL descendants, not just loaded ones)
$sql->map(function ($category) {
$category->all_items_count = $category->all_items_count;
return $category;
});
ResponseService::successResponse(null, $sql, ['self_category' => $parentCategory ?? null]);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategories');
ResponseService::errorResponse();
}
}
public function getParentCategoryTree(Request $request)
{
$validator = Validator::make($request->all(), [
'child_category_id' => 'nullable|integer',
'tree' => 'nullable|boolean',
'slug' => 'nullable|string',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$sql = Category::with('translations')->when($request->child_category_id, function ($sql) use ($request) {
$sql->where('categories.id', $request->child_category_id);
})
->when($request->slug, function ($sql) use ($request) {
$sql->where('slug', $request->slug);
})
->firstOrFail()
->ancestorsAndSelf()->breadthFirst()->get();
if ($request->tree) {
$sql = $sql->toTree();
}
ResponseService::successResponse(null, $sql);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategories');
ResponseService::errorResponse();
}
}
public function getNotificationList(Request $request)
{
try {
$user = Auth::user();
$authId = $user->id;
$userCreatedAt = $user->created_at; // Get user's registration date
$id = $request->id;
// 1. Build Base Query
$query = Notifications::with(['item.area', 'item.translations'])
->where(function ($q) use ($authId, $userCreatedAt) {
// Specific notifications for this user
$q->whereRaw('FIND_IN_SET(?, user_id)', [$authId])
// Global notifications only if they were sent AFTER the user registered
->orWhere(function ($sq) use ($userCreatedAt) {
$sq->where('send_to', 'all')
->where('created_at', '>=', $userCreatedAt);
});
});
// 2. Filter by ID if passed, otherwise Paginate
if (! empty($id)) {
$notifications = $query->where('id', $id)->first();
if (! $notifications) {
return ResponseService::successResponse(__('Notification not found'), null);
}
$notificationCollection = collect([$notifications]);
} else {
$notifications = $query->orderBy('id', 'DESC')->paginate();
$notificationCollection = $notifications->getCollection();
}
$currentLanguage = app()->getLocale();
$currentLangId = Language::where('code', $currentLanguage)->value('id');
// 3. Process Logic
foreach ($notificationCollection as $notification) {
$item = $notification->item;
if ($item) {
$city = City::with(['translations', 'state', 'country'])
->where('name', $item->city)
->whereHas('state', fn($q) => $q->where('name', $item->state))
->first();
$translatedArea = $item->area->translated_name ?? '';
$translatedCity = $city?->translated_name ?? $item->city;
$translatedState = $city?->state?->translated_name ?? $item->state;
$translatedCountry = $city?->country?->translated_name ?? $item->country;
$item->translated_address = (! empty($translatedArea) ? $translatedArea . ', ' : '') .
$translatedCity . ', ' . $translatedState . ', ' . $translatedCountry;
if ($currentLanguage && $item->relationLoaded('translations')) {
$translation = $item->translations->firstWhere('language_id', $currentLangId);
if ($translation) {
$item->name = $translation->name;
$item->description = $translation->description;
$item->address = $translation->address;
}
}
$item->translated_area = $translatedArea;
$item->translated_city = $translatedCity;
$item->translated_state = $translatedState;
$item->translated_country = $translatedCountry;
}
}
return ResponseService::successResponse(__('Notification fetched successfully'), $notifications);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getNotificationList');
return ResponseService::errorResponse();
}
}
public function getLanguages(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'language_code' => 'required',
'type' => 'nullable|in:app,web',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$language = Language::where('code', $request->language_code)->firstOrFail();
// Determine requested file path
$type = $request->type ?? 'app';
$languageCode = $request->language_code;
if ($type === 'web') {
$json_file_path = base_path("resources/lang/{$language->web_file}");
$default_file_path = base_path('resources/lang/en_web.json');
} else {
$json_file_path = base_path("resources/lang/{$language->app_file}");
$default_file_path = base_path('resources/lang/en_app.json');
}
// If requested file doesnt exist, fallback to default English file
if (! is_file($json_file_path)) {
if (is_file($default_file_path)) {
$json_file_path = $default_file_path;
} else {
ResponseService::errorResponse(__('Default language file not found'));
}
}
// Read file content safely
$json_string = file_get_contents($json_file_path);
try {
$json_data = json_decode($json_string, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
ResponseService::errorResponse(__('Invalid JSON format in the language file'));
}
$language->file_name = $json_data;
ResponseService::successResponse(__('Data Fetched Successfully'), $language);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getLanguages');
ResponseService::errorResponse();
}
}
public function appPaymentStatus(Request $request)
{
try {
$paypalInfo = $request->all();
if (! empty($paypalInfo) && isset($_GET['st']) && strtolower($_GET['st']) == 'completed') {
ResponseService::successResponse(__('Your Package will be activated within 10 Minutes'), $paypalInfo['txn_id']);
} elseif (! empty($paypalInfo) && isset($_GET['st']) && strtolower($_GET['st']) == 'authorized') {
ResponseService::successResponse(__('Your Transaction is Completed. Ads wil be credited to your account within 30 minutes.'), $paypalInfo);
} else {
ResponseService::errorResponse(__('Payment Cancelled / Declined'), (isset($_GET)) ? $paypalInfo : '');
}
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> appPaymentStatus');
ResponseService::errorResponse();
}
}
public function getPaymentSettings()
{
try {
$result = PaymentConfiguration::select(['currency_code', 'payment_method', 'api_key', 'status'])->where('status', 1)->get();
$response = [];
foreach ($result as $payment) {
$response[$payment->payment_method] = $payment->toArray();
}
$settings = Setting::whereIn('name', [
'account_holder_name',
'bank_name',
'account_number',
'ifsc_swift_code',
'bank_transfer_status',
])->get();
$bankDetails = [];
foreach ($settings as $row) {
$key = ($row->name === 'bank_transfer_status') ? 'status' : $row->name;
$bankDetails[$key] = $row->value;
}
$response['bankTransfer'] = $bankDetails;
ResponseService::successResponse(__('Data Fetched Successfully'), $response);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getPaymentSettings');
ResponseService::errorResponse();
}
}
public function getCustomFields(Request $request)
{
try {
$filter = filter_var($request->input('filter', false), FILTER_VALIDATE_BOOLEAN);
$categoryIds = explode(',', $request->input('category_ids'));
// Load custom fields
$customFields = CustomField::with('translations')
->whereHas('custom_field_category', function ($q) use ($categoryIds) {
$q->whereIn('category_id', $categoryIds);
})
->where('status', 1)
->get();
// Apply filtering logic
if ($filter === true) {
// Modify the collection with filtering
$customFields = $customFields->filter(function ($field) use ($categoryIds) {
// Only filter for dropdown/checkbox/radio
if (! in_array($field->type, ['dropdown', 'checkbox', 'radio'])) {
return true; // keep text, number etc.
}
// Get used values for this field (pluck only value column)
$values = ItemCustomFieldValue::where('custom_field_id', $field->id)
->whereHas('item', function ($q) use ($categoryIds) {
$q->getNonExpiredItems()
->whereNull('deleted_at')
->where('status', 'approved')
->whereIn('category_id', $categoryIds);
})
->pluck('value')
->toArray();
$used = [];
// Decode values properly
foreach ($values as $raw) {
$decoded = is_string($raw) ? json_decode($raw, true) : $raw;
if (is_array($decoded)) {
$used = array_merge($used, $decoded);
} else {
$used[] = $decoded;
}
}
$used = array_unique(array_filter($used));
// ❌ Remove the entire field if no used values exist
if (empty($used)) {
return false;
}
// Get original main language values before filtering
$originalMainValues = $field->values ?? [];
// Filter original field values
$field->values = array_values(array_intersect($originalMainValues, $used));
// Find indices of used values in original main language array
$usedIndices = [];
foreach ($field->values as $usedValue) {
$index = array_search($usedValue, $originalMainValues);
if ($index !== false) {
$usedIndices[] = $index;
}
}
// Filter translations by same indices to maintain alignment
foreach ($field->translations as $t) {
$translationValues = $t->value ?? [];
if (is_array($translationValues) && count($translationValues) > 0) {
$filteredTranslationValues = [];
foreach ($usedIndices as $idx) {
if (isset($translationValues[$idx])) {
$filteredTranslationValues[] = $translationValues[$idx];
}
}
$t->value = array_values($filteredTranslationValues);
}
}
return true; // KEEP field
})->values(); // re-index collection
}
// Load translated attributes
$customFields->each(function ($field) {
$field->translated_name = $field->translated_name;
$field->translated_value = $field->translated_value;
});
ResponseService::successResponse(__('Data Fetched successfully'), $customFields);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCustomFields');
ResponseService::errorResponse();
}
}
public function makeFeaturedItem(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'required|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::commit();
$user = Auth::user();
Item::where('status', 'approved')->findOrFail($request->item_id);
$user_package = UserPurchasedPackage::onlyActive()
->where(['user_id' => $user->id])
->with('package')
->whereHas('package', function ($q) {
$q->where(['type' => 'advertisement']);
})
->first();
if (! $user_package) {
return ResponseService::errorResponse(__('You need to purchase a Featured Ad plan first.'));
}
$featuredItems = FeaturedItems::where(['item_id' => $request->item_id, 'package_id' => $user_package->package_id])->first();
if (! empty($featuredItems)) {
ResponseService::errorResponse(__('Advertisement is already featured'));
}
$user_package->used_limit++;
$user_package->save();
FeaturedItems::create([
'item_id' => $request->item_id,
'package_id' => $user_package->package_id,
'user_purchased_package_id' => $user_package->id,
'start_date' => date('Y-m-d'),
'end_date' => $user_package->end_date,
]);
DB::commit();
ResponseService::successResponse(__('Featured Advertisement Created Successfully'));
} catch (Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th, 'API Controller -> createAdvertisement');
ResponseService::errorResponse();
}
}
public function manageFavourite(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'item_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$favouriteItem = Favourite::where('user_id', Auth::user()->id)->where('item_id', $request->item_id)->first();
if (empty($favouriteItem)) {
$favouriteItem = new Favourite;
$favouriteItem->user_id = Auth::user()->id;
$favouriteItem->item_id = $request->item_id;
$favouriteItem->save();
ResponseService::successResponse(__('Advertisement added to Favourite'));
} else {
$favouriteItem->delete();
ResponseService::successResponse(__('Advertisement remove from Favourite'));
}
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> manageFavourite');
ResponseService::errorResponse();
}
}
public function getFavouriteItem(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'page' => 'nullable|integer',
'limit' => 'nullable|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$favouriteItemIDS = Favourite::where('user_id', Auth::user()->id)->select('item_id')->pluck('item_id');
$items = Item::whereIn('id', $favouriteItemIDS)
->with('user:id,name,email,mobile,profile,country_code', 'category:id,name,image,is_job_category', 'gallery_images:id,image,item_id', 'featured_items', 'favourites', 'item_custom_field_values.custom_field')->where('status', 'approved')->onlyNonBlockedUsers()->getNonExpiredItems()->paginate();
ResponseService::successResponse(__('Data Fetched Successfully'), new ItemCollection($items));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getFavouriteItem');
ResponseService::errorResponse();
}
}
public function getSlider(Request $request)
{
$validator = Validator::make($request->all(), [
'country' => 'nullable|string',
'state' => 'nullable|string',
'city' => 'nullable|string',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
// Fetch IDs (single responsibility)
$countryId = $request->country
? Country::where('name', $request->country)->value('id')
: null;
$stateId = $request->state
? State::where('name', $request->state)->value('id')
: null;
$cityId = $request->city
? City::where('name', $request->city)->value('id')
: null;
$rows = Slider::with([
'model' => function (MorphTo $morphTo) {
$morphTo->constrain([
Category::class => fn($q) => $q->withCount('subcategories'),
]);
},
])->where(function ($query) {
$query->whereNull('model_type')
->orWhereHasMorph(
'model',
[Category::class, Item::class],
fn($q) => $q->whereNotNull('id')
);
})
->where(function ($q) use ($countryId, $stateId, $cityId) {
if ($countryId || $stateId || $cityId) {
$q->where(function ($sub) use ($countryId, $stateId, $cityId) {
if ($countryId) {
$sub->orWhere('country_id', $countryId);
}
if ($stateId) {
$sub->orWhere('state_id', $stateId);
}
if ($cityId) {
$sub->orWhere('city_id', $cityId);
}
})
->orWhere(function ($global) {
$global->whereNull('country_id')
->whereNull('state_id')
->whereNull('city_id');
});
} else {
$q->whereNull('country_id')
->whereNull('state_id')
->whereNull('city_id');
}
})->get();
ResponseService::successResponse(null, $rows);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getSlider');
ResponseService::errorResponse();
}
}
public function getReportReasons(Request $request)
{
try {
$report_reason = new ReportReason;
if (! empty($request->id)) {
$id = $request->id;
$report_reason->where('id', '=', $id);
}
$result = $report_reason->paginate();
$total = $report_reason->count();
ResponseService::successResponse(__('Data Fetched Successfully'), $result, ['total' => $total]);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getReportReasons');
ResponseService::errorResponse();
}
}
public function addReports(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'item_id' => 'required',
'report_reason_id' => 'required_without:other_message',
'other_message' => 'required_without:report_reason_id',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$user = Auth::user();
$report_count = UserReports::where('item_id', $request->item_id)->where('user_id', $user->id)->first();
if ($report_count) {
ResponseService::errorResponse(__('Already Reported'));
}
UserReports::create([
...$request->all(),
'user_id' => $user->id,
'other_message' => $request->other_message ?? '',
]);
ResponseService::successResponse(__('Report Submitted Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> addReports');
ResponseService::errorResponse();
}
}
public function setItemTotalClick(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'item_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
Item::findOrFail($request->item_id)->increment('clicks');
ResponseService::successResponse(null, 'Update Successfully');
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> setItemTotalClick');
ResponseService::errorResponse();
}
}
public function getFeaturedSection(Request $request)
{
try {
$featureSection = FeatureSection::with('translations')->orderBy('sequence', 'ASC');
if (isset($request->slug)) {
$featureSection->where('slug', $request->slug);
}
$featureSection = $featureSection->get();
$tempRow = [];
$rows = [];
// Helper function to build base query
// $buildBaseQuery = function () {
// return Item::where('status', 'approved')
// ->with('user:id,name,email,mobile,profile,is_verified,show_personal_details,country_code',
// 'category:id,name,image,is_job_category,price_optional',
// 'gallery_images:id,image,item_id',
// 'featured_items',
// 'favourites',
// 'item_custom_field_values.custom_field.translations',
// 'job_applications',
// 'translations',
// 'countryRelation:id,name',
// 'countryRelation.translations:id,country_id,language_id,name',
// 'countryRelation.currency:id,country_id,iso_code,symbol,symbol_position'
// )
// ->has('user')
// ->getNonExpiredItems();
// };
$buildBaseQuery = function () {
return Item::query()
->where('status', 'approved')
->has('user')
->with([
'user:id,name,mobile,profile,is_verified,show_personal_details,country_code',
'category:id,name,image,is_job_category,price_optional',
'gallery_images:id,image,item_id',
'featured_items',
'favourites',
'item_custom_field_values.custom_field.translations',
'job_applications',
'translations',
'countryRelation:id,name',
'countryRelation.translations:id,country_id,language_id,name',
'countryRelation.currency:id,country_id,iso_code,symbol,symbol_position',
])
->getNonExpiredItems();
};
// Helper function to apply auth filters
$applyAuthFilters = function ($query) {
if (Auth::check()) {
$query->with(['item_offers' => function ($q) {
$q->where('buyer_id', Auth::user()->id);
}, 'user_reports' => function ($q) {
$q->where('user_id', Auth::user()->id);
}]);
}
return $query;
};
$locationMessage = null;
$baseItems = $buildBaseQuery();
$locationResult = HelperService::applyLocationFilters($baseItems, $request, $applyAuthFilters);
$filteredBaseQuery = clone $locationResult['query'];
$sectionLocationMessage = $locationResult['message'];
foreach ($featureSection as $row) {
// Build base query with all eager loading
// $baseItems = $buildBaseQuery();
// Apply location filters using shared function
// $locationResult = HelperService::applyLocationFilters($baseItems, $request, $applyAuthFilters);
// $baseItems = $locationResult['query'];
// $sectionLocationMessage = $locationResult['message'];
$baseItems = clone $filteredBaseQuery;
// Apply filter criteria
$items = match ($row->filter) {
// 'price_criteria' => $baseItems->whereBetween('price', [$row->min_price, $row->max_price]),
'price_criteria' => $baseItems->where(function ($query) use ($row) {
$query->whereBetween('price', [$row->min_price, $row->max_price])
->orWhere(function ($q) use ($row) {
$q->whereBetween('min_salary', [$row->min_price, $row->max_price])
->whereBetween('max_salary', [$row->min_price, $row->max_price]);
});
}),
'most_viewed' => $baseItems->orderBy('clicks', 'DESC'),
'category_criteria' => (static function () use ($row, $baseItems) {
$category = Category::whereIn('id', explode(',', $row->value))->with('children')->get();
$categoryIDS = HelperService::findAllCategoryIds($category);
return $baseItems->whereIn('category_id', $categoryIDS)->orderBy('id', 'DESC');
})(),
'most_liked' => $baseItems->withCount('favourites')->orderBy('favourites_count', 'DESC'),
'featured_ads' => $baseItems->has('featured_items')->orderBy('id', 'DESC'),
};
// Add auth-specific relationships
if (Auth::check()) {
$items->with(['item_offers' => function ($q) {
$q->where('buyer_id', Auth::user()->id);
}, 'user_reports' => function ($q) {
$q->where('user_id', Auth::user()->id);
}]);
}
// Limit results early and get items
$items = $items->limit(5)->get();
// $items = $items->with('user:id,name,email,mobile,profile,is_verified,show_personal_details,country_code',
// 'category:id,name,image,is_job_category,price_optional',
// 'gallery_images:id,image,item_id',
// 'featured_items',
// 'favourites',
// 'item_custom_field_values.custom_field.translations',
// 'job_applications',
// 'translations',
// 'countryRelation:id,name',
// 'countryRelation.translations:id,country_id,language_id,name',
// 'countryRelation.currency:id,country_id,iso_code,symbol,symbol_position')->limit(5)->get();
$tempRow[$row->id] = $row;
$tempRow[$row->id]['total_data'] = count($items);
if (count($items) > 0) {
$tempRow[$row->id]['section_data'] = new ItemCollection($items);
} else {
$tempRow[$row->id]['section_data'] = [];
}
// Track location message for response (use first non-empty one)
if (! empty($sectionLocationMessage) && empty($locationMessage)) {
$locationMessage = $sectionLocationMessage;
}
$rows[] = $tempRow[$row->id];
}
// Use location message if available, otherwise use default success message
// dd([
// 'queries' => \DB::getQueryLog(),
// 'memory' => memory_get_peak_usage(true) / 1024 / 1024 .' MB',
// ]);
$responseMessage = ! empty($locationMessage) ? $locationMessage : __('Data Fetched Successfully');
ResponseService::successResponse($responseMessage, $rows);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getFeaturedSection');
ResponseService::errorResponse();
}
}
public function getPaymentIntent(Request $request)
{
$validator = Validator::make($request->all(), [
'package_id' => 'required',
'payment_method' => 'required|in:Stripe,Razorpay,Paystack,PhonePe,FlutterWave,bankTransfer,PayPal',
'platform_type' => 'required_if:payment_method,==,Paystack|string',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
if ($request->payment_method !== 'bankTransfer') {
$paymentConfigurations = PaymentConfiguration::where(['status' => 1, 'payment_method' => $request->payment_method])->first();
if (empty($paymentConfigurations)) {
ResponseService::errorResponse(__('Payment is not Enabled'));
}
} else {
$bankTransferEnabled = Setting::where('name', 'bank_transfer_status')->value('value');
if ($bankTransferEnabled != 1) {
ResponseService::errorResponse(__('Bank Transfer is not enabled.'));
}
}
$package = Package::whereNot('final_price', 0)->findOrFail($request->package_id);
$purchasedPackage = UserPurchasedPackage::onlyActive()->where(['user_id' => Auth::user()->id, 'package_id' => $request->package_id])->first();
if (! empty($purchasedPackage)) {
ResponseService::errorResponse(__('You already have purchased this package'));
}
if ($request->payment_method === 'bankTransfer') {
$existingTransaction = PaymentTransaction::where('user_id', Auth::user()->id)
->where('package_id', $request->package_id)
->where('payment_gateway', $request->payment_method)
->whereIn('payment_status', ['pending', 'under review'])
->exists();
$methodName = $paymentMethodNames[$request->payment_method] ?? ucfirst($request->payment_method);
if ($existingTransaction) {
return ResponseService::errorResponse("A $methodName transaction for this package already exists.");
}
}
$orderId = ($request->payment_method === 'bankTransfer') ? uniqid() . '-' . 'p' . '-' . $package->id : null;
// Add Payment Data to Payment Transactions Table
$paymentTransactionData = PaymentTransaction::create([
'user_id' => Auth::user()->id,
'package_id' => $request->package_id,
'amount' => $package->final_price,
'payment_gateway' => ucfirst($request->payment_method),
'payment_status' => 'Pending',
'order_id' => $orderId,
]);
if ($request->payment_method === 'bankTransfer') {
DB::commit();
ResponseService::successResponse(__('Bank transfer initiated. Please complete the transfer and update the transaction.'), [
'payment_transaction_id' => $paymentTransactionData->id,
'payment_transaction' => $paymentTransactionData,
]);
}
$paymentIntent = PaymentService::create($request->payment_method)->createAndFormatPaymentIntent(round($package->final_price, 2), [
'payment_transaction_id' => $paymentTransactionData->id,
'package_id' => $package->id,
'user_id' => Auth::user()->id,
'email' => Auth::user()->email,
'platform_type' => $request->platform_type,
]);
$paymentTransactionData->update(['order_id' => $paymentIntent['id']]);
$paymentTransactionData = PaymentTransaction::findOrFail($paymentTransactionData->id);
// Custom Array to Show as response
$paymentGatewayDetails = [
...$paymentIntent,
'payment_transaction_id' => $paymentTransactionData->id,
];
DB::commit();
ResponseService::successResponse('', ['payment_intent' => $paymentGatewayDetails, 'payment_transaction' => $paymentTransactionData]);
} catch (Throwable $e) {
DB::rollBack();
ResponseService::logErrorResponse($e);
ResponseService::errorResponse();
}
}
public function getPaymentTransactions(Request $request)
{
$validator = Validator::make($request->all(), [
'latest_only' => 'nullable|boolean',
'page' => 'nullable',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$paymentTransactions = PaymentTransaction::where('user_id', Auth::user()->id)->orderBy('id', 'DESC');
if ($request->latest_only) {
$paymentTransactions->where('created_at', '>', Carbon::now()->subMinutes(30)->toDateTimeString());
}
$paymentTransactions = $paymentTransactions->paginate();
$formatter = app(CurrencyFormatterService::class);
$paymentTransactions->getCollection()->transform(function ($data) use ($formatter) {
if ($data->payment_status == 'pending') {
try {
$paymentIntent = PaymentService::create($data->payment_gateway)->retrievePaymentIntent($data->order_id);
// dd($paymentIntent);
} catch (Throwable) {
// PaymentTransaction::find($data->id)->update(['payment_status' => "failed"]);
}
if (! empty($paymentIntent) && $paymentIntent['status'] != 'pending') {
PaymentTransaction::find($data->id)->update(['payment_status' => $paymentIntent['status'] ?? 'failed']);
}
}
$data->formatted_amount = $formatter->formatPrice($data->amount);
$data->payment_reciept = $data->payment_reciept;
return $data;
});
ResponseService::successResponse(__('Payment Transactions Fetched'), $paymentTransactions);
} catch (Throwable $e) {
ResponseService::logErrorResponse($e);
ResponseService::errorResponse();
}
}
public function createItemOffer(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'required|integer',
'amount' => 'nullable|numeric',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$item = Item::approved()->notOwner()->findOrFail($request->item_id);
$itemOffer = ItemOffer::updateOrCreate([
'item_id' => $request->item_id,
'buyer_id' => Auth::user()->id,
'seller_id' => $item->user_id,
], ['amount' => $request->amount]);
$itemOffer = $itemOffer->load('seller:id,name,profile', 'buyer:id,name,profile', 'item:id,name,description,price,image');
$fcmMsg = [
'user_id' => $itemOffer->buyer->id,
'user_name' => $itemOffer->buyer->name,
'user_profile' => $itemOffer->buyer->profile,
'user_type' => 'Buyer',
'item_id' => $itemOffer->item->id,
'item_name' => $itemOffer->item->name,
'item_image' => $itemOffer->item->image,
'item_price' => $itemOffer->item->price,
'item_offer_id' => $itemOffer->id,
'item_offer_amount' => $itemOffer->amount,
// 'type' => $notificationPayload['message_type'],
// 'message_type_temp' => $notificationPayload['message_type']
];
/* message_type is reserved keyword in FCM so removed here */
unset($fcmMsg['message_type']);
if ($request->has('amount') && $request->amount != 0) {
$user_token = UserFcmToken::where('user_id', $item->user->id)->pluck('fcm_token')->toArray();
$message = 'new offer is created by buyer';
NotificationService::sendFcmNotification($user_token, 'New Offer', $message, 'offer', $fcmMsg);
}
ResponseService::successResponse(__('Advertisement Offer Created Successfully'), $itemOffer);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> createItemOffer');
ResponseService::errorResponse();
}
}
public function getChatList(Request $request)
{
$validator = Validator::make($request->all(), [
'type' => 'required|in:seller,buyer',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$authId = Auth::user()->id;
// 1. Get Blocked Users Lists
$authUserBlockList = BlockUser::where('user_id', $authId)->pluck('blocked_user_id');
$otherUserBlockList = BlockUser::where('blocked_user_id', $authId)->pluck('user_id');
// 2. Prepare the Base Query
$query = ItemOffer::with([
'seller:id,name,profile',
'buyer:id,name,profile',
'item' => function ($q) {
$q->with(['currency:id,iso_code,symbol,symbol_position', 'category:id,name,image,is_job_category,price_optional']);
},
'chat' => function ($q) {
$q->latest('updated_at')->select('updated_at', 'item_offer_id', 'is_read', 'sender_id');
},
'item.review' => function ($q) use ($authId) {
$q->where('buyer_id', $authId);
}
])
->whereHas('buyer', function ($query) {
$query->whereNull('deleted_at');
})
->withCount([
'chat as unread_chat_count' => function ($query) use ($authId) {
$query->where('is_read', 0)
->where('sender_id', '!=', $authId);
},
]);
// Filter by type
if ($request->type == 'seller') {
$query->where('seller_id', $authId);
} else {
$query->where('buyer_id', $authId);
}
// 3. CALCULATE GLOBAL UNREAD COUNT (For all pages)
// We clone the query to get the total sum without pagination limits
$totalUnreadChatCount = (int) (clone $query)->get()->sum('unread_chat_count');
// 4. APPLY STABLE ORDERING AND PAGINATE
$itemOffer = $query->orderByDesc('unread_chat_count')
->orderByDesc(function ($q) {
$q->select('updated_at')
->from('chats')
->whereColumn('item_offer_id', 'item_offers.id')
->orderByDesc('updated_at')
->limit(1);
})
->orderBy('id', 'DESC') // Tie-breaker ensures Page 1 items don't show on Page 2
->paginate();
// 5. DATA FORMATTING (Single Transform)
$formatter = app(currencyFormatterService::class);
$itemOffer->getCollection()->transform(function ($offer) use ($request, $authId, $authUserBlockList, $otherUserBlockList, $formatter) {
// Block logic
if ($request->type === 'seller') {
$userBlocked = $authUserBlockList->contains($offer->buyer_id) || $otherUserBlockList->contains($offer->seller_id);
} else {
$userBlocked = $authUserBlockList->contains($offer->seller_id) || $otherUserBlockList->contains($offer->buyer_id);
}
$offer->user_blocked = $userBlocked;
// Chat timing logic
$latestChat = $offer->chat->first();
$offer->last_message_time = $latestChat ? $latestChat->updated_at : $offer->updated_at;
// Item and Currency Formatting
if ($offer->item) {
$item = $offer->item;
$item->is_purchased = ($item->sold_to == $authId) ? 1 : 0;
// Handle Review Relation (Convert collection to single object)
$item->setRelation('review', optional($item->review)->first());
$currency = $item?->currency ?? null;
$item->formatted_price = $formatter->formatPrice($item->price, $currency);
$item->formatted_min_salary = $formatter->formatPrice($item->min_salary, $currency);
$item->formatted_max_salary = $formatter->formatPrice($item->max_salary, $currency);
$item->formatted_salary_range = $formatter->formatSalaryRange($item->min_salary, $item->max_salary, $currency);
}
// Offer amount formatting
$offerCurrency = $offer->item ? ($offer->item->currency ?? null) : null;
$offer->formatted_amount = $formatter->formatPrice($offer->amount, $offerCurrency);
return $offer;
});
return ResponseService::successResponse(
__('Chat List Fetched Successfully'),
$itemOffer,
['total_unread_chat_count' => $totalUnreadChatCount]
);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getChatList');
return ResponseService::errorResponse('Something went wrong');
}
}
public function sendMessage(Request $request)
{
$validator = Validator::make($request->all(), [
'item_offer_id' => 'required|integer',
'message' => (! $request->file('file') && ! $request->file('audio')) ? 'required' : 'nullable',
'file' => 'nullable|mimes:jpg,jpeg,png|max:7168',
'audio' => 'nullable|mimetypes:audio/mpeg,video/webm,audio/ogg,video/mp4,audio/x-wav,text/plain|max:7168',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
DB::beginTransaction();
$user = Auth::user();
// List of users that Auth user has blocked
$authUserBlockList = BlockUser::where('user_id', $user->id)->get();
// List of Other users that have blocked the Auth user
$otherUserBlockList = BlockUser::where('blocked_user_id', $user->id)->get();
$itemOffer = ItemOffer::with('item')->findOrFail($request->item_offer_id);
if ($itemOffer->seller_id == $user->id) {
// If Auth user is seller then check if buyer has blocked the user
$blockStatus = $authUserBlockList->filter(function ($data) use ($itemOffer) {
return $data->user_id == $itemOffer->seller_id && $data->blocked_user_id == $itemOffer->buyer_id;
});
if (count($blockStatus) !== 0) {
ResponseService::errorResponse(__('You Cannot send message because You have blocked this user'));
}
$blockStatus = $otherUserBlockList->filter(function ($data) use ($itemOffer) {
return $data->user_id == $itemOffer->buyer_id && $data->blocked_user_id == $itemOffer->seller_id;
});
if (count($blockStatus) !== 0) {
ResponseService::errorResponse(__('You Cannot send message because other user has blocked you.'));
}
} else {
// If Auth user is seller then check if buyer has blocked the user
$blockStatus = $authUserBlockList->filter(function ($data) use ($itemOffer) {
return $data->user_id == $itemOffer->buyer_id && $data->blocked_user_id == $itemOffer->seller_id;
});
if (count($blockStatus) !== 0) {
ResponseService::errorResponse(__('You Cannot send message because You have blocked this user'));
}
$blockStatus = $otherUserBlockList->filter(function ($data) use ($itemOffer) {
return $data->user_id == $itemOffer->seller_id && $data->blocked_user_id == $itemOffer->buyer_id;
});
if (count($blockStatus) !== 0) {
ResponseService::errorResponse(__('You Cannot send message because other user has blocked you.'));
}
}
$chat = Chat::create([
'sender_id' => Auth::user()->id,
'item_offer_id' => $request->item_offer_id,
'message' => $request->message,
'file' => $request->hasFile('file') ? FileService::compressAndUpload($request->file('file'), 'chat') : '',
'audio' => $request->hasFile('audio') ? FileService::compressAndUpload($request->file('audio'), 'chat') : '',
'is_read' => 0,
]);
if ($itemOffer->seller_id == $user->id) {
$receiver_id = $itemOffer->buyer_id;
$userType = 'Seller';
} else {
$receiver_id = $itemOffer->seller_id;
$userType = 'Buyer';
}
$notificationPayload = $chat->toArray();
$unreadMessagesCount = Chat::where('item_offer_id', $itemOffer->id)
->where('is_read', 0)
->count();
$fcmMsg = [
...$notificationPayload,
'user_id' => $user->id,
'user_name' => $user->name,
'user_profile' => $user->profile,
'user_type' => $userType,
'item_id' => $itemOffer->item->id,
'item_name' => $itemOffer->item->name,
'item_image' => $itemOffer->item->image,
'item_price' => $itemOffer->item->price,
'item_offer_id' => $itemOffer->id,
'item_offer_amount' => $itemOffer->amount,
'type' => $notificationPayload['message_type'],
'message_type_temp' => $notificationPayload['message_type'],
'unread_count' => $unreadMessagesCount,
];
/* message_type is reserved keyword in FCM so removed here */
unset($fcmMsg['message_type']);
$displayMessage = $request->message;
if (empty($displayMessage)) {
if ($request->hasFile('file')) {
$mime = $request->file('file')->getMimeType();
if (str_contains($mime, 'image')) {
$displayMessage = '📷 Sent you an image';
} elseif (str_contains($mime, 'pdf')) {
$displayMessage = '📄 Sent you a PDF file';
} elseif (str_contains($mime, 'word')) {
$displayMessage = '📘 Sent you a document';
} elseif (str_contains($mime, 'text')) {
$displayMessage = '📄 Sent you a text file';
} else {
$displayMessage = '📎 Sent you a file';
}
} elseif ($request->hasFile('audio')) {
$displayMessage = '🎤 Sent you an audio message';
} else {
$displayMessage = '💬 Sent you a message';
}
}
$receiverFCMTokens = UserFcmToken::where('user_id', $receiver_id)->pluck('fcm_token')->toArray();
DB::commit();
$notification = NotificationService::sendFcmNotification($receiverFCMTokens, 'Message', $displayMessage, 'chat', $fcmMsg);
ResponseService::successResponse(__('Message Fetched Successfully'), $chat, ['debug' => $notification]);
} catch (Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th, 'API Controller -> sendMessage');
ResponseService::errorResponse();
}
}
public function getChatMessages(Request $request)
{
$validator = Validator::make($request->all(), [
'item_offer_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$itemOffer = ItemOffer::owner()->findOrFail($request->item_offer_id);
$chat = Chat::where('item_offer_id', $itemOffer->id)->orderBy('created_at', 'DESC')->paginate();
$authUserId = auth::user()->id;
Chat::where('item_offer_id', $itemOffer->id)
->where('sender_id', '!=', $authUserId)
->whereIn('id', $chat->pluck('id'))
->update(['is_read' => '1']);
ResponseService::successResponse(__('Messages Fetched Successfully'), $chat);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getChatMessages');
ResponseService::errorResponse();
}
}
public function deleteUser()
{
try {
User::findOrFail(Auth::user()->id)->forceDelete();
ResponseService::successResponse(__('User Deleted Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> deleteUser');
ResponseService::errorResponse();
}
}
public function inAppPurchase(Request $request)
{
$validator = Validator::make($request->all(), [
'purchase_token' => 'required',
'payment_method' => 'required|in:google,apple',
'package_id' => 'required|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$package = Package::findOrFail($request->package_id);
$purchasedPackage = UserPurchasedPackage::where(['user_id' => Auth::user()->id, 'package_id' => $request->package_id])->first();
if (! empty($purchasedPackage)) {
ResponseService::errorResponse(__('You already have purchased this package'));
}
PaymentTransaction::create([
'user_id' => Auth::user()->id,
'amount' => $package->final_price,
'payment_gateway' => $request->payment_method,
'order_id' => $request->purchase_token,
'payment_status' => 'success',
]);
UserPurchasedPackage::create([
'user_id' => Auth::user()->id,
'package_id' => $request->package_id,
'start_date' => Carbon::now(),
'total_limit' => $package->item_limit == 'unlimited' ? null : $package->item_limit,
'end_date' => $package->duration == 'unlimited' ? null : Carbon::now()->addDays($package->duration),
]);
ResponseService::successResponse(__('Package Purchased Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> inAppPurchase');
ResponseService::errorResponse();
}
}
public function blockUser(Request $request)
{
$validator = Validator::make($request->all(), [
'blocked_user_id' => 'required|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
BlockUser::create([
'user_id' => Auth::user()->id,
'blocked_user_id' => $request->blocked_user_id,
]);
ResponseService::successResponse(__('User Blocked Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> blockUser');
ResponseService::errorResponse();
}
}
public function unblockUser(Request $request)
{
$validator = Validator::make($request->all(), [
'blocked_user_id' => 'required|integer',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
BlockUser::where([
'user_id' => Auth::user()->id,
'blocked_user_id' => $request->blocked_user_id,
])->delete();
ResponseService::successResponse(__('User Unblocked Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> unblockUser');
ResponseService::errorResponse();
}
}
public function getBlockedUsers()
{
try {
$blockedUsers = BlockUser::where('user_id', Auth::user()->id)->pluck('blocked_user_id');
$users = User::whereIn('id', $blockedUsers)->select(['id', 'name', 'profile'])->get();
ResponseService::successResponse(__('User Unblocked Successfully'), $users);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> unblockUser');
ResponseService::errorResponse();
}
}
public function getTips()
{
try {
$tips = Tip::select(['id', 'description'])->orderBy('sequence', 'ASC')->with('translations')->get();
ResponseService::successResponse(__('Tips Fetched Successfully'), $tips);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getTips');
ResponseService::errorResponse();
}
}
public function getBlog(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'category_id' => 'nullable|integer|exists:categories,id',
'blog_id' => 'nullable|integer|exists:blogs,id',
'sort_by' => 'nullable|in:new-to-old,old-to-new,popular',
'views' => 'nullable|boolean',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
if ($request->views == 1) {
if (! empty($request->id)) {
Blog::where('blogs.id', $request->id)->increment('views');
} elseif (! empty($request->slug)) {
Blog::where('slug', $request->slug)->increment('views');
} else {
return ResponseService::errorResponse(__('ID or Slug is required to increment views'));
}
}
$blogs = Blog::with('translations')->when(! empty($request->id), static function ($q) use ($request) {
$q->where('blogs.id', $request->id);
Blog::where('blogs.id', $request->id);
})
->when(! empty($request->slug), function ($q) use ($request) {
$q->where('slug', $request->slug);
Blog::where('slug', $request->slug);
})
->when(! empty($request->sort_by), function ($q) use ($request) {
if ($request->sort_by === 'new-to-old') {
$q->orderByDesc('created_at');
} elseif ($request->sort_by === 'old-to-new') {
$q->orderBy('created_at');
} elseif ($request->sort_by === 'popular') {
$q->orderByDesc('views');
}
})
->when(! empty($request->tag), function ($q) use ($request) {
$q->where(function ($query) use ($request) {
$query->where('tags', 'like', '%' . $request->tag . '%')
->orWhereHas('translations', function ($translationQuery) use ($request) {
$translationQuery->where('tags', 'like', '%' . $request->tag . '%');
});
});
})
->paginate();
$otherBlogs = [];
if (! empty($request->id) || ! empty($request->slug)) {
$otherBlogs = Blog::with('translations')
->when(! empty($request->id), function ($q) use ($request) {
$q->where('blogs.id', '!=', $request->id);
})
->when(! empty($request->slug), function ($q) use ($request) {
$q->where('slug', '!=', $request->slug);
})
->orderByDesc('id')
->limit(3)
->get();
}
ResponseService::successResponse(__('Blogs fetched successfully'), $blogs, ['other_blogs' => $otherBlogs]);
} catch (Throwable $th) {
// Log and handle exceptions
ResponseService::logErrorResponse($th, 'API Controller -> getBlog');
ResponseService::errorResponse(__('Failed to fetch blogs'));
}
}
public function getCountries(Request $request)
{
try {
$searchQuery = $request->search ?? '';
$countries = Country::withCount('states')
->where(function ($query) use ($searchQuery) {
$query->where('name', 'LIKE', "%{$searchQuery}%")
->orWhereHas('nameTranslations', function ($q) use ($searchQuery) {
$q->where('name', 'LIKE', "%{$searchQuery}%");
});
})
->with(['nameTranslations.language:id,code'])
->orderBy('name', 'ASC')
->paginate();
// Map translations to include `language_code`
$countries->getCollection()->transform(function ($country) {
if ($country->translations instanceof \Illuminate\Support\Collection) {
$country->translations = $country->translations->map(function ($translation) {
return [
'id' => $translation->id,
'country_id' => $translation->country_id,
'language_id' => $translation->language_id,
'name' => $translation->name,
'language_code' => optional($translation->language)->code,
];
});
} else {
// if somehow it's not a collection, fallback
$country->translations = [];
}
return $country;
});
ResponseService::successResponse(__('Countries Fetched Successfully'), $countries);
} catch (Throwable $th) {
// Log and handle any exceptions
ResponseService::logErrorResponse($th, 'API Controller -> getCountries');
ResponseService::errorResponse(__('Failed to fetch countries'));
}
}
public function getStates(Request $request)
{
$validator = Validator::make($request->all(), [
'country_id' => 'nullable|integer',
'search' => 'nullable|string',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$searchQuery = $request->search ?? '';
$statesQuery = State::withCount('cities')
->where('name', 'LIKE', "%{$searchQuery}%")
->orderBy('name', 'ASC');
if (isset($request->country_id)) {
$statesQuery->where('country_id', $request->country_id);
}
$states = $statesQuery->paginate();
ResponseService::successResponse(__('States Fetched Successfully'), $states);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller->getStates');
ResponseService::errorResponse(__('Failed to fetch states'));
}
}
public function getCities(Request $request)
{
try {
// Validate
$validator = Validator::make($request->all(), [
'state_id' => 'nullable|integer',
'search' => 'nullable|string',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
$searchQuery = $request->search ?? '';
// Base query
$citiesQuery = City::with('translations')
->withCount('areas')
->orderBy('cities.name', 'ASC'); // force main table for sorting
// Search filter: main name OR translated name
if ($searchQuery !== '') {
$citiesQuery->where(function ($q) use ($searchQuery) {
$q->where('cities.name', 'LIKE', "%{$searchQuery}%")
->orWhereHas('translations', function ($t) use ($searchQuery) {
$t->where('name', 'LIKE', "%{$searchQuery}%");
});
});
}
// State filter
if ($request->filled('state_id')) {
$citiesQuery->where('cities.state_id', $request->state_id);
}
// Pagination
$cities = $citiesQuery->paginate();
return ResponseService::successResponse(__('Cities Fetched Successfully'), $cities);
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller->getCities');
return ResponseService::errorResponse(__('Failed to fetch cities'));
}
}
public function getAreas(Request $request)
{
$validator = Validator::make($request->all(), [
'city_id' => 'nullable|integer',
'search' => 'nullable',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$searchQuery = $request->search ?? '';
$data = Area::with('translations')->search($searchQuery)->orderBy('name', 'ASC');
if (isset($request->city_id)) {
$data->where('city_id', $request->city_id);
}
$data = $data->paginate();
ResponseService::successResponse(__('Area fetched Successfully'), $data);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getAreas');
ResponseService::errorResponse();
}
}
public function getFaqs()
{
try {
$faqs = Faq::with('translations')->get();
ResponseService::successResponse(__('FAQ Data fetched Successfully'), $faqs);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getFaqs');
ResponseService::errorResponse(__('Failed to fetch Faqs'));
}
}
public function getAllBlogTags()
{
try {
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
$language = Language::select(['id', 'code', 'name'])
->where('code', $languageCode)
->first();
if (! $language) {
return ResponseService::errorResponse('Invalid language code');
}
$tagsMap = [];
Blog::with(['translations' => function ($q) use ($language) {
$q->where('language_id', $language->id);
}])->chunk(100, function ($blogs) use (&$tagsMap) {
foreach ($blogs as $blog) {
$defaultTagsRaw = $blog->tags;
$defaultTags = [];
if (! empty($defaultTagsRaw)) {
if (is_string($defaultTagsRaw)) {
$decoded = json_decode($defaultTagsRaw, true);
if (json_last_error() === JSON_ERROR_NONE && ! empty($decoded)) {
$defaultTags = is_array($decoded) ? $decoded : [$decoded];
} else {
$defaultTags = array_map('trim', explode(',', $defaultTagsRaw));
}
} elseif (is_array($defaultTagsRaw)) {
$defaultTags = $defaultTagsRaw;
}
}
$translatedTagsRaw = $blog->translations->first()?->tags;
$translatedTags = [];
if (! empty($translatedTagsRaw)) {
if (is_string($translatedTagsRaw)) {
$decoded = json_decode($translatedTagsRaw, true);
if (json_last_error() === JSON_ERROR_NONE && ! empty($decoded)) {
$translatedTags = is_array($decoded) ? $decoded : [$decoded];
} else {
$translatedTags = array_map('trim', explode(',', $translatedTagsRaw));
}
} elseif (is_array($translatedTagsRaw)) {
$translatedTags = $translatedTagsRaw;
}
}
foreach ($defaultTags as $index => $defaultTag) {
$translated = $translatedTags[$index] ?? $defaultTag;
$tagsMap[$defaultTag] = $translated;
}
}
});
$result = [];
foreach ($tagsMap as $defaultTag => $translatedTag) {
$result[] = [
'label' => $translatedTag,
'value' => $defaultTag,
];
}
ResponseService::successResponse('Blog Tags Retrieved Successfully', array_values($result));
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getAllBlogTags');
return ResponseService::errorResponse('Failed to fetch Tags');
}
}
public function storeContactUs(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'subject' => 'required',
'message' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
ContactUs::create($request->all());
ResponseService::successResponse(__('Contact Us Stored Successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> storeContactUs');
ResponseService::errorResponse();
}
}
public function addItemReview(Request $request)
{
$validator = Validator::make($request->all(), [
'review' => 'nullable|string',
'ratings' => 'required|numeric|between:0,5',
'item_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$item = Item::with('user')->notOwner()->findOrFail($request->item_id);
if ($item->sold_to !== Auth::id()) {
ResponseService::errorResponse(__('You can only review items that you have purchased.'));
}
if ($item->status !== 'sold out') {
ResponseService::errorResponse(__("The item must be marked as 'sold out' before you can review it."));
}
$existingReview = SellerRating::where('item_id', $request->item_id)->where('buyer_id', Auth::id())->first();
if ($existingReview) {
ResponseService::errorResponse(__('You have already reviewed this item.'));
}
$review = SellerRating::create([
'item_id' => $request->item_id,
'buyer_id' => Auth::user()->id,
'seller_id' => $item->user_id,
'ratings' => $request->ratings,
'review' => $request->review ?? '',
]);
$user_token = UserFcmToken::where('user_id', $item->user_id)->pluck('fcm_token')->toArray();
if (! empty($user_token)) {
NotificationService::sendFcmNotification(
$user_token,
'New Review',
'A new review has been added to your advertisement: ' . $item->name,
'item-review',
['item_id' => $item->id]
);
}
ResponseService::successResponse(__('Your review has been submitted successfully.'), $review);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> storeContactUs');
ResponseService::errorResponse();
}
}
public function getSeller(Request $request)
{
$request->validate([
'id' => 'required|integer',
]);
try {
// Fetch seller by ID
$seller = User::findOrFail($request->id);
// Fetch seller ratings
$ratings = SellerRating::where('seller_id', $seller->id)->with('buyer:id,name,profile')->paginate(10);
$averageRating = $ratings->avg('ratings');
// Response structure
$response = [
'seller' => [
...$seller->toArray(),
'average_rating' => $averageRating,
],
'ratings' => $ratings,
];
// Send success response
ResponseService::successResponse(__('Seller Details Fetched Successfully'), $response);
} catch (Throwable $th) {
// Log and handle error response
ResponseService::logErrorResponse($th, 'API Controller -> getSeller');
ResponseService::errorResponse();
}
}
public function renewItem(Request $request)
{
try {
$free_ad_listing = Setting::where('name', 'free_ad_listing')->value('value') ?? 0;
// Validation rules
$rules = [
'item_id' => 'nullable|exists:items,id',
'item_ids' => 'nullable|string', // accept comma-separated string
];
if ($free_ad_listing == 0) {
$rules['package_id'] = 'required|exists:packages,id';
} else {
$rules['package_id'] = 'nullable|exists:packages,id';
}
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
// Normalize input to array
$itemIds = [];
if ($request->filled('item_id')) {
$itemIds[] = $request->item_id;
}
if ($request->filled('item_ids')) {
// Convert comma-separated string into array
$ids = explode(',', $request->item_ids);
$ids = array_map('trim', $ids); // remove spaces
$ids = array_filter($ids, 'strlen'); // remove empty values
$itemIds = array_merge($itemIds, $ids);
}
if (empty($itemIds)) {
return ResponseService::validationError(__('Please provide item_id or item_ids'));
}
$user = Auth::user();
$package = null;
$userPackage = null;
// Fetch package if provided
if ($request->filled('package_id')) {
$package = Package::where('id', $request->package_id)->firstOrFail();
$userPackage = UserPurchasedPackage::onlyActive()
->where([
'user_id' => $user->id,
'package_id' => $package->id,
])->first();
if (! $userPackage) {
return ResponseService::errorResponse(__('You have not purchased this package'));
}
}
$currentDate = Carbon::now();
$results = [];
foreach ($itemIds as $itemId) {
$item = Item::findOrFail($itemId);
$rawStatus = $item->getAttributes()['status'];
if (Carbon::parse($item->expiry_date)->gt($currentDate)) {
$results[$itemId] = [
'status' => 'failed',
'message' => __('Advertisement has not expired yet, so it cannot be renewed'),
];
continue;
}
if ($package) {
// Calculate expiry date based on package listing duration
$expiryDate = HelperService::calculateItemExpiryDate($package, $userPackage);
$item->expiry_date = $expiryDate;
$userPackage->used_limit++;
$userPackage->save();
} else {
// No package - use standard 30 days
$item->expiry_date = $currentDate->copy()->addDays(30);
}
$item->status = $rawStatus;
$item->save();
$results[$itemId] = [
'status' => 'success',
'item' => $item,
];
}
// Return single item response if only one item was renewed
if (count($itemIds) === 1) {
$itemId = $itemIds[0];
if ($results[$itemId]['status'] === 'success') {
return ResponseService::successResponse(
__('Advertisement renewed successfully'),
$results[$itemId]['item']
);
} else {
return ResponseService::errorResponse($results[$itemId]['message']);
}
}
// Return multiple items response
return ResponseService::successResponse(__('Items processed successfully'), $results);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> renewItem');
return ResponseService::errorResponse();
}
}
public function getMyReview(Request $request)
{
try {
$ratings = SellerRating::where('seller_id', Auth::user()->id)->with('seller:id,name,profile', 'buyer:id,name,profile', 'item:id,name,price,image,description')->paginate(10);
$averageRating = $ratings->avg('ratings');
$response = [
'average_rating' => $averageRating,
'ratings' => $ratings,
];
ResponseService::successResponse(__('Seller Details Fetched Successfully'), $response);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getSeller');
ResponseService::errorResponse();
}
}
public function addReviewReport(Request $request)
{
$validator = Validator::make($request->all(), [
'report_reason' => 'required|string',
'seller_review_id' => 'required',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$ratings = SellerRating::where('seller_id', Auth::user()->id)->findOrFail($request->seller_review_id);
$ratings->update([
'report_status' => 'reported',
'report_reason' => $request->report_reason,
]);
ResponseService::successResponse(__('Your report has been submitted successfully.'), $ratings);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> addReviewReport');
ResponseService::errorResponse();
}
}
public function getVerificationFields()
{
try {
$fields = VerificationField::all();
ResponseService::successResponse(__('Verification Field Fetched Successfully'), $fields);
} catch (Throwable $th) {
DB::rollBack();
ResponseService::logErrorResponse($th, 'API Controller -> addVerificationFieldValues');
ResponseService::errorResponse();
}
}
public function sendVerificationRequest(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'verification_field' => 'sometimes|array',
'verification_field.*' => 'sometimes',
'verification_field_files' => 'nullable|array',
'verification_field_files.*' => 'nullable|mimes:jpeg,png,jpg,pdf,doc|max:7168',
'verification_field_translations' => 'nullable|json',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
DB::beginTransaction();
$user = Auth::user();
$verificationRequest = VerificationRequest::updateOrCreate([
'user_id' => $user->id,
], ['status' => 'pending']);
$user = auth()->user();
if ($request->verification_field) {
$itemCustomFieldValues = [];
foreach ($request->verification_field as $id => $value) {
$itemCustomFieldValues[] = [
'user_id' => $user->id,
'verification_field_id' => $id,
'verification_request_id' => $verificationRequest->id,
'value' => $value,
'created_at' => now(),
'updated_at' => now(),
];
}
if (count($itemCustomFieldValues) > 0) {
VerificationFieldValue::upsert($itemCustomFieldValues, ['user_id', 'verification_fields_id'], ['value', 'updated_at']);
}
}
if ($request->verification_field_files) {
$itemCustomFieldValues = [];
foreach ($request->verification_field_files as $fieldId => $file) {
$itemCustomFieldValues[] = [
'user_id' => $user->id,
'verification_field_id' => $fieldId,
'verification_request_id' => $verificationRequest->id,
'value' => ! empty($file) ? FileService::upload($file, 'verification_field_files') : '',
'created_at' => now(),
'updated_at' => now(),
];
}
if (count($itemCustomFieldValues) > 0) {
VerificationFieldValue::upsert($itemCustomFieldValues, ['user_id', 'verification_field_id'], ['value', 'updated_at']);
}
}
if ($request->has('verification_field_translations')) {
$fieldTranslations = json_decode($request->input('verification_field_translations'), true, 512, JSON_THROW_ON_ERROR);
$translatedEntries = [];
foreach ($fieldTranslations as $languageId => $fieldsById) {
foreach ($fieldsById as $fieldId => $translatedValue) {
$translatedEntries[] = [
'user_id' => $user->id,
'verification_field_id' => $fieldId,
'verification_request_id' => $verificationRequest->id,
'language_id' => $languageId,
'value' => is_array($translatedValue) ? implode(',', $translatedValue) : $translatedValue,
'created_at' => now(),
'updated_at' => now(),
];
}
}
if (! empty($translatedEntries)) {
// upsert to avoid duplicates — if necessary
VerificationFieldValue::upsert(
$translatedEntries,
['user_id', 'verification_field_id'],
['value', 'updated_at', 'language_id']
);
}
}
DB::commit();
ResponseService::successResponse(__('Verification request submitted successfully.'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> SendVerificationRequest');
ResponseService::errorResponse();
}
}
public function getVerificationRequest(Request $request)
{
try {
$verificationRequest = VerificationRequest::with([
'verification_field_values.verification_field.translations',
])->owner()->first();
if (empty($verificationRequest)) {
ResponseService::errorResponse('No Request found');
}
$response = $verificationRequest->toArray();
$response['verification_fields'] = [];
// Get current language for translation
$contentLangCode = $request->header('Content-Language') ?? app()->getLocale();
$currentLanguage = Language::where('code', $contentLangCode)->first();
$currentLangId = $currentLanguage->id ?? 1;
foreach ($verificationRequest->verification_field_values as $verificationFieldValue) {
if (
$verificationFieldValue->relationLoaded('verification_field') &&
! empty($verificationFieldValue->verification_field)
) {
// if (empty($verificationFieldValue->language_id) || $verificationFieldValue->language_id = null) {
// $verificationFieldValue->language_id = $currentLangId;
// }
$field = $verificationFieldValue->verification_field;
$tempRow = $field->toArray();
$rawValue = $verificationFieldValue->value;
// Normalize value to array
$normalizedValue = [];
if ($field->type === 'fileinput') {
$normalizedValue = ! empty($rawValue) ? [url(Storage::url($rawValue))] : [];
} elseif (is_array($rawValue)) {
$normalizedValue = $rawValue;
} elseif (is_string($rawValue)) {
$decoded = json_decode($rawValue, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
$normalizedValue = $decoded;
} else {
$normalizedValue = [$rawValue];
}
} elseif (! empty($rawValue)) {
$normalizedValue = [$rawValue];
}
// Set normalized value
$tempRow['value'] = array_map('trim', explode(',', $normalizedValue[0]));
// Set verification_field_value with normalized value
$tempRow['verification_field_value'] = $verificationFieldValue->toArray();
unset($tempRow['verification_field_value']['verification_field']);
$tempRow['verification_field_value']['value'] = $normalizedValue;
$tempRow['verification_field_value']['language_id'] = $verificationFieldValue->language_id;
// Handle translated_selected_values
$selected = [];
$type = $field->type ?? null;
$allPossibleValues = $field->values ?? [];
// Fetch translated values (if available)
$translatedValues = [];
if (! empty($field->translations)) {
$translation = collect($field->translations)->firstWhere('language_id', $currentLangId);
$translatedValues = $translation['value'] ?? [];
}
if (empty($translatedValues)) {
$translatedValues = $allPossibleValues;
}
if (in_array($type, ['checkbox', 'radio', 'dropdown'])) {
foreach ($normalizedValue as $val) {
$index = array_search($val, $allPossibleValues);
$translatedVal = ($index !== false && isset($translatedValues[$index]))
? $translatedValues[$index]
: $val;
$selected[] = $translatedVal;
}
} elseif (in_array($type, ['textbox', 'number'])) {
$selected = $normalizedValue;
}
$tempRow['language_id'] = $verificationFieldValue->language_id;
$tempRow['translated_selected_values'] = $selected;
$response['verification_fields'][] = $tempRow;
}
}
ResponseService::successResponse(__('Verification request fetched successfully.'), $response);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> SendVerificationRequest');
ResponseService::errorResponse();
}
}
public function seoSettings(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'page' => 'nullable',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
$settings = new SeoSetting;
if (! empty($request->page)) {
$settings = $settings->where('page', $request->page);
}
$settings = $settings->get();
ResponseService::successResponse(__('SEO settings fetched successfully.'), $settings);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> seoSettings');
ResponseService::errorResponse();
}
}
public function getCategories(Request $request)
{
$validator = Validator::make($request->all(), [
'language_code' => 'nullable',
]);
if ($validator->fails()) {
ResponseService::validationError($validator->errors()->first());
}
try {
$categories = Category::all();
$languageCode = $request->get('language_code', 'en');
$translator = new GoogleTranslate($languageCode);
$categoriesJson = $categories->toJson();
$translatedJson = $translator->translate($categoriesJson);
$translatedCategories = json_decode($translatedJson, true);
return ResponseService::successResponse(null, $translatedCategories);
ResponseService::successResponse(null, $sql);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategories');
ResponseService::errorResponse();
}
}
public function bankTransferUpdate(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'payment_transection_id' => 'required|integer',
'payment_receipt' => 'required|file|mimes:jpg,jpeg,png|max:7048',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
$transaction = PaymentTransaction::where('user_id', Auth::user()->id)->findOrFail($request->payment_transection_id);
if (! $transaction) {
return ResponseService::errorResponse(__('Transaction not found.'));
}
$receiptPath = ! empty($request->file('payment_receipt'))
? FileService::upload($request->file('payment_receipt'), 'bank-transfer')
: '';
$transaction->update([
'payment_receipt' => $receiptPath,
'payment_status' => 'under review',
]);
return ResponseService::successResponse(__('Payment transaction updated successfully.'), $transaction);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> bankTransferUpdate');
return ResponseService::errorResponse();
}
}
public function getOtp(Request $request)
{
try {
$validator = Validator::make($request->query(), [
'country_code' => 'required|string|max:5',
'number' => 'required|string|max:15',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
// Normalize inputs
$countryCode = ltrim(trim($request->query('country_code')), '+');
$number = preg_replace('/\D/', '', $request->query('number'));
$toNumber = '+' . $countryCode . $number;
$twilioSettings = Setting::whereIn('name', [
'twilio_account_sid',
'twilio_auth_token',
'twilio_my_phone_number',
])->pluck('value', 'name');
if ($twilioSettings->count() < 3) {
return ResponseService::errorResponse(__('Twilio settings are missing.'));
}
$client = new TwilioRestClient(
$twilioSettings['twilio_account_sid'],
$twilioSettings['twilio_auth_token']
);
try {
$client->lookups->v1->phoneNumbers($toNumber)->fetch();
} catch (\Throwable $e) {
return ResponseService::errorResponse(__('Invalid phone number.'));
}
$otp = rand(100000, 999999);
$expireAt = now()->addMinutes(10);
NumberOtp::updateOrCreate(
[
'number' => $number,
],
[
'otp' => $otp,
'expire_at' => $expireAt,
]
);
// Send OTP
$client->messages->create($toNumber, [
'from' => $twilioSettings['twilio_my_phone_number'],
'body' => "Your OTP is: $otp. It expires in 10 minutes.",
]);
return ResponseService::successResponse(__('OTP sent successfully.'));
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th, 'OTP Controller -> getOtp');
return ResponseService::errorResponse();
}
}
public function verifyOtp(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'number' => 'required|string',
'country_code' => 'required|string',
'otp' => 'required|numeric|digits:6',
'password' => 'nullable|string|min:6',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
$number = $request->query('number');
$countryCode = $request->query('country_code');
// Format full phone number
// $requestNumber = $countryCode.$number;
// $trimmedNumber = ltrim($requestNumber, '+');
// $toNumber = '+'.$trimmedNumber;
$otpRecord = NumberOtp::where('number', $number)->first();
if (! $otpRecord) {
return ResponseService::errorResponse(__('OTP not found.'));
}
if (now()->isAfter($otpRecord->expire_at)) {
return ResponseService::validationError(__('OTP has expired.'));
}
if ($otpRecord->attempts >= 3) {
$otpRecord->delete();
return ResponseService::validationError(__('OTP expired after 3 failed attempts.'));
}
if ($otpRecord->otp != $request->otp) {
$otpRecord->increment('attempts');
return ResponseService::validationError(__('Invalid OTP.'));
}
$otpRecord->delete();
$user = User::where('mobile', $request->number)->where('type', 'phone')->first();
if (! $user) {
$user = User::create([
'mobile' => $request->number,
'type' => 'phone',
'country_code' => $countryCode,
'password' => ! empty($request->password) ? Hash::make($request->password) : '',
]);
$user->assignRole('User');
}else{
if (! empty($countryCode)) {
$user->country_code = ltrim($countryCode, '+');
}
$user->save();
}
Auth::login($user);
$auth = User::find(Auth::id());
$token = $auth->createToken($auth->name ?? '')->plainTextToken;
return ResponseService::successResponse(__('User logged-in successfully'), $auth, ['token' => $token]);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'OTP Controller -> verifyOtp');
return ResponseService::errorResponse();
}
}
public function userExists(Request $request)
{
$validator = Validator::make($request->all(), [
'mobile' => 'required|string',
'country_code' => 'required|string',
'forgot_password' => 'nullable|boolean',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$mobile = ltrim($request->mobile, '+');
$countryCode = ltrim($request->country_code, '+');
$mobileWithCode = $countryCode . $mobile;
$userExists = User::where('type', 'phone')
->where('mobile', $mobile)
->withTrashed()
->first();
if (! $userExists) {
return ResponseService::errorResponse(
__('User does not exist'),
['user_exists' => false]
);
}
// ❌ user found but deactivated
if ($userExists->deleted_at) {
return ResponseService::errorResponse(
__('User is deactivated. Please contact the administrator.'),
['user_exists' => false]
);
}
if ($userExists) {
return ResponseService::successResponse(
__('User already exists'),
['user_exists' => true]
);
}
return ResponseService::errorResponse(
__('User does not exist'),
['user_exists' => false]
);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> userExists');
return ResponseService::errorResponse();
}
}
public function applyJob(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'required',
'full_name' => 'required|string|max:255',
'email' => 'required|email|max:255',
'mobile' => 'required|string|max:20',
'resume' => 'nullable|file|mimes:pdf,doc,docx|max:7168',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$userId = Auth::id();
$post = Item::approved()->notOwner()->findOrFail($request->item_id);
$alreadyApplied = JobApplication::where('item_id', $request->item_id)
->where('user_id', $userId)
->exists();
if ($alreadyApplied) {
return ResponseService::validationError(__('You have already applied for this job.'));
}
$resumePath = null;
if ($request->hasFile('resume')) {
$resumePath = FileService::upload($request->resume, 'job_resume');
}
$application = JobApplication::create([
'item_id' => $post->id,
'user_id' => Auth::user()->id,
'recruiter_id' => $post->user_id,
'full_name' => $request->full_name,
'email' => $request->email,
'mobile' => $request->mobile,
'resume' => $resumePath,
]);
$user_token = UserFcmToken::where('user_id', $post->user_id)->pluck('fcm_token')->toArray();
if (! empty($user_token)) {
NotificationService::sendFcmNotification(
$user_token,
'New Job Application',
$request->full_name . ' applied for your job post: ' . $post->name,
'job-application',
['item_id' => $post->id]
);
}
return ResponseService::successResponse(__('Application submitted successfully.'), $application);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> applyJob');
return ResponseService::errorResponse();
}
}
public function recruiterApplications(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'nullable|integer',
'page' => 'nullable|integer',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$user = Auth::user();
$applications = JobApplication::where('recruiter_id', $user->id)
->with('user:id,name,email', 'item:id,name');
if (! empty($request->item_id)) {
$applications->where('item_id', $request->item_id);
}
$applications = $applications->latest()->paginate();
return ResponseService::successResponse(__('Recruiter applications fetched'), $applications);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> recruiterApplications');
return ResponseService::errorResponse();
}
}
public function myJobApplications(Request $request)
{
$validator = Validator::make($request->all(), [
'item_id' => 'nullable|integer',
'page' => 'nullable|integer',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$user = Auth::user();
$applications = JobApplication::where('user_id', $user->id);
if (! empty($request->item_id)) {
$applications->where('item_id', $request->item_id);
}
$applications = $applications->with([
'item:id,name,user_id',
'recruiter:id,name,email',
])
->latest()
->paginate();
return ResponseService::successResponse(__('Your job applications fetched'), $applications);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> myJobApplications');
return ResponseService::errorResponse();
}
}
public function updateJobStatus(Request $request)
{
$validator = Validator::make($request->all(), [
'job_id' => 'required|exists:job_applications,id',
'status' => 'required|in:accepted,rejected',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$user = Auth::user();
$application = JobApplication::with('item')->findOrFail($request->job_id);
if ($application->recruiter_id !== $user->id) {
return ResponseService::errorResponse(__('Unauthorized to update this job status.'), 403);
}
$application->update(['status' => $request->status]);
// Optional: Notify the applicant
$user_token = UserFcmToken::where('user_id', $application->user_id)->pluck('fcm_token')->toArray();
if (! empty($user_token)) {
NotificationService::sendFcmNotification(
$user_token,
'Application ' . ucfirst($request->status),
'Your application for job post has been ' . $request->status,
'application-status',
['job_id' => $application->id]
);
}
return ResponseService::successResponse(__('Application status updated.'), $application);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> updateJobStatus');
return ResponseService::errorResponse();
}
}
public function getLocationFromCoordinates(Request $request)
{
$validator = Validator::make($request->all(), [
'lat' => 'nullable|numeric',
'lng' => 'nullable|numeric',
'lang' => 'nullable|string',
'search' => 'nullable|string',
'place_id' => 'nullable|string',
'session_id' => 'nullable|string',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
try {
$lat = $request->lat;
$lng = $request->lng;
$lang = $request->lang ?? 'en';
$search = $request->search;
$placeId = $request->place_id;
$mapProvider = Setting::where('name', 'map_provider')->value('value') ?? 'free_api';
// Determine current language ID
$contentLangCode = $request->header('Content-Language') ?? app()->getLocale();
$currentLanguage = Language::where('code', $contentLangCode)->first();
$currentLangId = $currentLanguage->id ?? 1;
/**
* 🔍 Handle search query
*/
if ($search) {
if ($mapProvider === 'google_places') {
$apiKey = Setting::where('name', 'place_api_key')->value('value');
if (! $apiKey) {
return ResponseService::errorResponse(__('Google Maps API key not set'));
}
$response = Http::get('https://maps.googleapis.com/maps/api/place/autocomplete/json', [
'key' => $apiKey,
'input' => $search,
'language' => $lang,
'sessiontoken' => $request->session_id, // ✅ added
]);
return $response->successful()
? ResponseService::successResponse(__('Location fetched from Google API'), $response->json())
: ResponseService::errorResponse(__('Failed to fetch from Google Maps API'));
} else {
// Search Areas with translations
$areas = Area::with([
'translations' => fn($q) => $q->where('language_id', $currentLangId),
'city.translations' => fn($q) => $q->where('language_id', $currentLangId),
'city.state.translations' => fn($q) => $q->where('language_id', $currentLangId),
'city.state.country.nametranslations' => fn($q) => $q->where('language_id', $currentLangId),
])
->where('name', 'like', "%{$search}%")
->limit(10)
->get();
if ($areas->isNotEmpty()) {
return ResponseService::successResponse(__('Matching areas found'), $areas->map(function ($area) {
return [
'area_id' => $area->id,
'area' => $area->name,
'area_translation' => optional($area->translations->first())->name ?? $area->name,
'city_id' => optional($area->city)->id,
'city' => optional($area->city)->name,
'city_translation' => optional($area->city->translations->first())->name ?? optional($area->city)->name,
'state' => optional($area->city->state)->name,
'state_translation' => optional($area->city->state->translations->first())->name ?? optional($area->city->state)->name,
'country' => optional($area->city->state->country)->name,
'country_translation' => optional($area->city->state->country->nametranslations->first())->name ?? optional($area->city->state->country)->name,
'latitude' => $area->latitude,
'longitude' => $area->longitude,
];
}));
}
// Search Cities with translations
$cities = City::with([
'translations' => fn($q) => $q->where('language_id', $currentLangId),
'state.translations' => fn($q) => $q->where('language_id', $currentLangId),
'state.country.nametranslations' => fn($q) => $q->where('language_id', $currentLangId),
])
->where('name', 'like', "%{$search}%")
->orWhereHas('state', fn($q) => $q->where('name', 'like', "%{$search}%"))
->orWhereHas('state.country', fn($q) => $q->where('name', 'like', "%{$search}%"))
->limit(10)
->get();
if ($cities->isEmpty()) {
return ResponseService::errorResponse(__('No matching location found'));
}
return ResponseService::successResponse(__('Matching cities found'), $cities->map(function ($city) {
return [
'city_id' => $city->id,
'city' => $city->name,
'city_translation' => optional($city->translations->first())->name ?? $city->name,
'state' => optional($city->state)->name,
'state_translation' => optional($city->state->translations->first())->name ?? optional($city->state)->name,
'country' => optional($city->state->country)->name,
'country_translation' => optional($city->state->country->nametranslations->first())->name ?? optional($city->state->country)->name,
'latitude' => $city->latitude,
'longitude' => $city->longitude,
];
}));
}
}
/**
* 📍 Get location by coordinates
*/
if (! empty($lat) && ! empty($lng)) {
if ($mapProvider === 'google_places') {
$apiKey = Setting::where('name', 'place_api_key')->value('value');
if (! $apiKey) {
return ResponseService::errorResponse(__('Google Maps API key not set'));
}
$response = Http::get('https://maps.googleapis.com/maps/api/geocode/json', [
'latlng' => "{$lat},{$lng}",
'key' => $apiKey,
'language' => $lang,
'sessiontoken' => $request->session_id, // ✅ added
]);
return $response->successful()
? ResponseService::successResponse(__('Location fetched from Google API'), $response->json())
: ResponseService::errorResponse(__('Failed to fetch from Google Maps API'));
} else {
$closestCity = City::with([
'translations' => fn($q) => $q->where('language_id', $currentLangId),
'state.translations' => fn($q) => $q->where('language_id', $currentLangId),
'state.country.nametranslations' => fn($q) => $q->where('language_id', $currentLangId),
])
->whereNotNull('latitude')
->whereNotNull('longitude')
->selectRaw('
id, name, latitude, longitude, state_id,
(6371 * acos(cos(radians(?))
* cos(radians(latitude))
* cos(radians(longitude) - radians(?))
+ sin(radians(?))
* sin(radians(latitude)))) AS distance
', [$lat, $lng, $lat])
->orderBy('distance', 'asc')
->first();
if (! $closestCity) {
return ResponseService::errorResponse(__('No nearby city found'));
}
$closestArea = Area::with([
'translations' => fn($q) => $q->where('language_id', $currentLangId),
])
->where('city_id', $closestCity->id)
->whereNotNull('latitude')
->whereNotNull('longitude')
->selectRaw('
id, name, latitude, longitude, city_id,
(6371 * acos(cos(radians(?))
* cos(radians(latitude))
* cos(radians(longitude) - radians(?))
+ sin(radians(?))
* sin(radians(latitude)))) AS distance
', [$lat, $lng, $lat])
->orderBy('distance', 'asc')
->first();
return ResponseService::successResponse(__('Location fetched from local database'), [
'city_id' => $closestCity->id,
'city' => $closestCity->name,
'city_translation' => optional($closestCity->translations->first())->name ?? $closestCity->name,
'state' => optional($closestCity->state)->name,
'state_translation' => optional($closestCity->state->translations->first())->name ?? optional($closestCity->state)->name,
'country' => optional($closestCity->state->country)->name,
'country_translation' => optional($closestCity->state->country->nametranslations->first())->name ?? optional($closestCity->state->country)->name,
'area_id' => optional($closestArea)->id,
'area' => optional($closestArea)->name,
'area_translation' => optional($closestArea?->translations?->first())->name ?? $closestArea?->name,
'latitude' => $closestCity->latitude,
'longitude' => $closestCity->longitude,
]);
}
}
/**
* 🏷️ Handle place_id
*/
if ($placeId) {
if ($mapProvider === 'google_places') {
$apiKey = Setting::where('name', 'place_api_key')->value('value');
if (! $apiKey) {
return ResponseService::errorResponse(__('Google Maps API key not set'));
}
$sessionParam = $request->session_id ? "&sessiontoken={$request->session_id}" : '';
$url = "https://maps.googleapis.com/maps/api/geocode/json?place_id={$placeId}&key={$apiKey}&language={$lang}{$sessionParam}";
// $url = "https://maps.googleapis.com/maps/api/geocode/json?place_id={$placeId}&key={$apiKey}&language={$lang}";
$response = Http::get($url);
return $response->successful()
? ResponseService::successResponse(__('Location fetched from place_id'), $response->json())
: ResponseService::errorResponse(__('Failed to fetch from Google Maps API using place_id'));
} else {
return ResponseService::errorResponse(__('place_id is only supported with Google Maps provider'));
}
}
} catch (\Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getLocationFromCoordinates');
return ResponseService::errorResponse(__('Failed to fetch location'));
}
}
public function subscribeFCMTopic(Request $request)
{
$request->validate([
'fcm_token' => 'required|string',
'topic' => 'required|string',
]);
$serverKey = env('FIREBASE_SERVER_KEY'); // legacy server key
$url = "https://iid.googleapis.com/iid/v1/{$request->fcm_token}/rel/topics/{$request->topic}";
$response = \Illuminate\Support\Facades\Http::withHeaders([
'Authorization' => 'key=' . $serverKey,
'access_token_auth' => true,
'Content-Type' => 'application/json',
])->post($url);
if ($response->successful()) {
return response()->json(['error' => false, 'message' => 'Subscribed successfully']);
}
return response()->json(['error' => true, 'details' => $response->body()], $response->status());
}
public function getItemSlugs(Request $request)
{
try {
$items = Item::without('translations')
->select('id', 'slug', 'updated_at')
->where('status', 'approved')
->whereNull('deleted_at')
->getNonExpiredItems()
->get()
->each->setAppends([]);
if ($items->isEmpty()) {
return ResponseService::errorResponse(__('No active items found.'));
}
return ResponseService::successResponse(__('Active item slugs fetched successfully.'), $items);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getItemSlugs');
return ResponseService::errorResponse();
}
}
public function getCategoriesSlug(Request $request)
{
try {
$categories = Category::without('translations')
->select('id', 'slug', 'updated_at')
->where('status', 1)
->get()
->each->setAppends([]);
if ($categories->isEmpty()) {
return ResponseService::errorResponse(__('No active Categories found.'));
}
return ResponseService::successResponse(__('Active Categories slugs fetched successfully.'), $categories);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategoriesSlug');
ResponseService::errorResponse();
}
}
public function getBlogsSlug(Request $request)
{
try {
$blogs = Blog::without('translations')
->select('id', 'slug', 'updated_at')
->get()
->each->setAppends([]);
if ($blogs->isEmpty()) {
return ResponseService::errorResponse(__('No active Blogs found.'));
}
return ResponseService::successResponse(__('Active Blogs slugs fetched successfully.'), $blogs);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategoriesSlug');
ResponseService::errorResponse();
}
}
public function getFeatureSectionSlug(Request $request)
{
try {
$FeatureSection = FeatureSection::without('translations')
->select('id', 'slug', 'updated_at')
->get()
->each->setAppends([]);
if ($FeatureSection->isEmpty()) {
return ResponseService::errorResponse(__('No active Feature Sections found.'));
}
return ResponseService::successResponse(__('Active Feature Sections slugs fetched successfully.'), $FeatureSection);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategoriesSlug');
ResponseService::errorResponse();
}
}
public function logout(Request $request)
{
try {
$user = Auth::user();
$validator = Validator::make($request->all(), [
'fcm_token' => 'nullable|string',
]);
if ($validator->fails()) {
return ResponseService::validationError($validator->errors()->first());
}
if ($request->fcm_token) {
UserFcmToken::where('user_id', $user->id)
->where('fcm_token', $request->fcm_token)
->delete();
}
return ResponseService::successResponse(__('User logged out successfully'));
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> Logout');
return ResponseService::errorResponse();
}
}
public function getSellerSlug(Request $request)
{
try {
$sellers = user::select('id', 'updated_at')
->whereNull('deleted_at')
->get();
if ($sellers->isEmpty()) {
return ResponseService::errorResponse(__('No active seller found.'));
}
return ResponseService::successResponse(__('Active Seller fetched successfully.'), $sellers);
} catch (Throwable $th) {
ResponseService::logErrorResponse($th, 'API Controller -> getCategoriesSlug');
ResponseService::errorResponse();
}
}
}