<?php
// ZaccShop PHP/MySQL API - shared-hosting compatible
require_once __DIR__ . '/config.php';

header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

function camelize($v){ if(is_array($v)){ $o=[]; foreach($v as $k=>$x){ $nk=is_string($k)?preg_replace_callback('/_([a-z])/',fn($m)=>strtoupper($m[1]),$k):$k; $o[$nk]=camelize($x); } return $o; } return $v; }
function out($data, int $status=200) { http_response_code($status); echo json_encode(camelize($data), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit; }
function body() { $raw=file_get_contents('php://input'); $j=json_decode($raw,true); return is_array($j)?$j:$_POST; }
function now_iso(){ return gmdate('c'); }
function db(){ static $pdo=null; if($pdo) return $pdo; $pdo=new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4',DB_USER,DB_PASS,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC]); return $pdo; }
function bearer(){ $h=$_SERVER['HTTP_AUTHORIZATION']??''; if(preg_match('/Bearer\s+(.+)/i',$h,$m)) return trim($m[1]); return ''; }
function user(){ $t=bearer(); if(!$t)return null; $q=db()->prepare('SELECT u.* FROM sessions s JOIN users u ON u.id=s.user_id WHERE s.token=? AND s.expires_at>NOW() AND u.is_active=1'); $q->execute([$t]); $u=$q->fetch(); return $u?:null; }
function safe_user($u){ unset($u['password_hash']); return $u; }
function auth(){ $u=user(); if(!$u) out(['error'=>'لطفاً ابتدا وارد حساب کاربری خود شوید'],401); return $u; }
function admin(){ $u=auth(); if($u['role']!=='admin') out(['error'=>'شما دسترسی لازم برای انجام این عملیات (مدیریت) را ندارید'],403); return $u; }
function idv($prefix){ return $prefix.'_'.date('YmdHis').'_'.bin2hex(random_bytes(3)); }
function row_json($row,$jsonCols=[]){ foreach($jsonCols as $c){ if(isset($row[$c])) $row[$c]=json_decode($row[$c]?:'[]',true); } return $row; }
function product_row($r){ return row_json($r,['features','tags','regions']); }
function order_row($r){ return row_json($r,['items','license_keys']); }
function product_payload($d){ return [
 'title'=>trim((string)($d['title']??'')), 'slug'=>trim((string)($d['slug']??'')), 'category_id'=>$d['categoryId']??$d['category_id']??'',
 'price'=>(int)($d['price']??0), 'description'=>(string)($d['description']??''), 'features'=>json_encode(array_values($d['features']??[]),JSON_UNESCAPED_UNICODE),
 'tags'=>json_encode(array_values($d['tags']??[]),JSON_UNESCAPED_UNICODE), 'image'=>(string)($d['image']??''), 'badge'=>(string)($d['badge']??''),
 'in_stock'=>!empty($d['inStock'])?1:0, 'stock_count'=>(int)($d['stockCount']??999), 'is_featured'=>!empty($d['isFeatured'])?1:0, 'is_best_seller'=>!empty($d['isBestSeller'])?1:0,
 'platform'=>(string)($d['platform']??'gaming_service'), 'delivery_type'=>(string)($d['deliveryType']??'manual_config'), 'delivery_note'=>(string)($d['deliveryNote']??''),
 'regions'=>json_encode(array_values($d['regions']??[]),JSON_UNESCAPED_UNICODE)
 ]; }

$path=parse_url($_SERVER['REQUEST_URI'],PHP_URL_PATH); $base=rtrim(dirname($_SERVER['SCRIPT_NAME']),'/'); if($base && str_starts_with($path,$base)) $path=substr($path,strlen($base)); $path=rtrim($path,'/');
$method=$_SERVER['REQUEST_METHOD'];

try {
 if($path==='/api/health' && $method==='GET') out(['status'=>'ok','brand'=>'ZaccShop','time'=>now_iso()]);
 if($path==='/api/auth/register' && $method==='POST'){ $d=body(); if(empty($d['name'])||empty($d['email'])||empty($d['password'])) out(['error'=>'نام، ایمیل و رمز عبور الزامی هستند'],400); if(strlen($d['password'])<6)out(['error'=>'رمز عبور باید حداقل ۶ کاراکتر باشد'],400); $pdo=db(); $q=$pdo->prepare('SELECT id FROM users WHERE email=?');$q->execute([$d['email']]);if($q->fetch())out(['error'=>'این ایمیل قبلاً ثبت شده است'],400); $id=idv('usr');$q=$pdo->prepare('INSERT INTO users(id,name,email,mobile,password_hash,role,is_active,created_at) VALUES(?,?,?,?,?,"user",1,NOW())');$q->execute([$id,trim($d['name']),strtolower(trim($d['email'])),trim($d['mobile']??''),password_hash($d['password'],PASSWORD_DEFAULT)]);$u=db()->query('SELECT * FROM users WHERE id='.$pdo->quote($id))->fetch(); $token=bin2hex(random_bytes(32));$q=$pdo->prepare('INSERT INTO sessions(token,user_id,expires_at) VALUES(?,?,DATE_ADD(NOW(),INTERVAL 7 DAY))');$q->execute([$token,$id]);out(['message'=>'ثبت‌نام با موفقیت انجام شد','user'=>safe_user($u),'token'=>$token]); }
 if($path==='/api/auth/login' && $method==='POST'){ $d=body();$q=db()->prepare('SELECT * FROM users WHERE email=?');$q->execute([strtolower(trim($d['email']??''))]);$u=$q->fetch();if(!$u||!password_verify($d['password']??'',$u['password_hash']))out(['error'=>'ایمیل یا رمز عبور اشتباه است'],400);if(!$u['is_active'])out(['error'=>'حساب کاربری شما غیرفعال شده است'],403);$token=bin2hex(random_bytes(32));$q=db()->prepare('INSERT INTO sessions(token,user_id,expires_at) VALUES(?,?,DATE_ADD(NOW(),INTERVAL 7 DAY))');$q->execute([$token,$u['id']]);out(['message'=>'ورود موفقیت‌آمیز بود','user'=>safe_user($u),'token'=>$token]); }
 if($path==='/api/auth/me' && $method==='GET'){ $u=auth(); out(['user'=>safe_user($u)]); }

 if($path==='/api/categories' && $method==='GET'){ $all=($_GET['all']??'false')==='true';$sql='SELECT * FROM categories '.($all?'':'WHERE is_active=1 ').'ORDER BY sort_order,id';$rows=db()->query($sql)->fetchAll();out($rows); }
 if($path==='/api/categories' && $method==='POST'){ admin();$d=body();if(empty($d['name']))out(['error'=>'نام دسته‌بندی الزامی است'],400);$id=idv('cat');$q=db()->prepare('INSERT INTO categories(id,name,slug,icon,description,is_active,sort_order,created_at) VALUES(?,?,?,?,?,?,?,NOW())');$q->execute([$id,$d['name'],$d['slug']??preg_replace('/\s+/','-',strtolower($d['name'])),$d['icon']??'Folder',$d['description']??'',isset($d['isActive'])?(int)$d['isActive']:1,(int)($d['sortOrder']??1)]);out(db()->query('SELECT * FROM categories WHERE id='.db()->quote($id))->fetch(),201); }
 if(preg_match('#^/api/categories/([^/]+)$#',$path,$m)){admin();$id=$m[1];if($method==='PUT'){ $d=body();$fields=['name'=>'name','slug'=>'slug','icon'=>'icon','description'=>'description','isActive'=>'is_active','sortOrder'=>'sort_order'];$sets=[];$vals=[];foreach($fields as $a=>$b)if(array_key_exists($a,$d)){ $sets[]="$b=?";$vals[]=$b==='is_active'?(int)$d[$a]:($b==='sort_order'?(int)$d[$a]:$d[$a]); }if(!$sets)out(db()->query('SELECT * FROM categories WHERE id='.db()->quote($id))->fetch());$vals[]=$id;$q=db()->prepare('UPDATE categories SET '.implode(',',$sets).' WHERE id=?');$q->execute($vals);out(db()->query('SELECT * FROM categories WHERE id='.db()->quote($id))->fetch());} if($method==='DELETE'){db()->prepare('DELETE FROM categories WHERE id=?')->execute([$id]);out(['message'=>'دسته‌بندی با موفقیت حذف شد']);}}

 if($path==='/api/products' && $method==='GET'){ $where=['1=1'];$vals=[];if(($_GET['all']??'')!=='true')$where[]='p.in_stock=1';if(!empty($_GET['category'])){$where[]='p.category_id=?';$vals[]=$_GET['category'];}if(!empty($_GET['search'])){$where[]='(p.title LIKE ? OR p.description LIKE ?)';$vals[]='%'.$_GET['search'].'%';$vals[]='%'.$_GET['search'].'%';}if(($_GET['isFeatured']??'')==='true')$where[]='p.is_featured=1';if(isset($_GET['minPrice'])){$where[]='p.price>=?';$vals[]=(int)$_GET['minPrice'];}if(isset($_GET['maxPrice'])){$where[]='p.price<=?';$vals[]=(int)$_GET['maxPrice'];}$sort='p.created_at DESC';if(($_GET['sort']??'')==='price_asc')$sort='p.price ASC';if(($_GET['sort']??'')==='price_desc')$sort='p.price DESC';$q=db()->prepare('SELECT p.*,c.name category_name FROM products p LEFT JOIN categories c ON c.id=p.category_id WHERE '.implode(' AND ',$where).' ORDER BY '.$sort);$q->execute($vals);$rows=$q->fetchAll();foreach($rows as &$r)$r=product_row($r);out($rows); }
 if(preg_match('#^/api/products/([^/]+)$#',$path,$m)&&$method==='GET'){ $q=db()->prepare('SELECT p.*,c.name category_name FROM products p LEFT JOIN categories c ON c.id=p.category_id WHERE p.id=? OR p.slug=?');$q->execute([$m[1],$m[1]]);$r=$q->fetch();if(!$r)out(['error'=>'محصول یافت نشد'],404);out(product_row($r)); }
 if($path==='/api/products' && $method==='POST'){admin();$d=body();$p=product_payload($d);if(!$p['title']||!$p['category_id'])out(['error'=>'عنوان و دسته‌بندی محصول الزامی است'],400);$id=idv('prod');$q=db()->prepare('INSERT INTO products(id,title,slug,category_id,price,description,features,tags,image,badge,in_stock,stock_count,is_featured,is_best_seller,platform,delivery_type,delivery_note,regions,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())');$q->execute(array_merge([$id],array_values($p)));$r=db()->query('SELECT p.*,c.name category_name FROM products p LEFT JOIN categories c ON c.id=p.category_id WHERE p.id='.db()->quote($id))->fetch();out(product_row($r),201);}
 if(preg_match('#^/api/products/([^/]+)$#',$path,$m)&&in_array($method,['PUT','DELETE'])){admin();$id=$m[1];if($method==='DELETE'){db()->prepare('DELETE FROM products WHERE id=?')->execute([$id]);out(['message'=>'محصول با موفقیت حذف شد']);}$d=body();$map=['title'=>'title','slug'=>'slug','categoryId'=>'category_id','price'=>'price','description'=>'description','features'=>'features','tags'=>'tags','image'=>'image','badge'=>'badge','inStock'=>'in_stock','stockCount'=>'stock_count','isFeatured'=>'is_featured','isBestSeller'=>'is_best_seller','platform'=>'platform','deliveryType'=>'delivery_type','deliveryNote'=>'delivery_note','regions'=>'regions'];$sets=[];$vals=[];foreach($map as $a=>$b)if(array_key_exists($a,$d)){ $sets[]="$b=?"; $vals[]=in_array($b,['features','tags','regions'])?json_encode($d[$a],JSON_UNESCAPED_UNICODE):($b==='price'||$b==='stock_count'||in_array($b,['in_stock','is_featured','is_best_seller'])?(int)$d[$a]:$d[$a]);}$vals[]=$id;$q=db()->prepare('UPDATE products SET '.implode(',',$sets).' WHERE id=?');$q->execute($vals);$r=db()->query('SELECT p.*,c.name category_name FROM products p LEFT JOIN categories c ON c.id=p.category_id WHERE p.id='.db()->quote($id))->fetch();out(product_row($r));}

 if($path==='/api/coupons/validate'&&$method==='POST'){ $d=body();$q=db()->prepare('SELECT * FROM coupons WHERE code=? AND is_active=1');$q->execute([strtoupper(trim($d['code']??''))]);$c=$q->fetch();$sub=(int)($d['subtotal']??0);if(!$c)out(['valid'=>false,'discount'=>0,'message'=>'کد تخفیف نامعتبر است']);$today=date('Y-m-d');if(($c['start_date']&&$today<$c['start_date'])||($c['end_date']&&$today>$c['end_date'])||($c['max_uses']&&$c['used_count']>=$c['max_uses'])||$sub<(int)$c['min_purchase'])out(['valid'=>false,'discount'=>0,'message'=>'شرایط استفاده از این کد تخفیف برقرار نیست']);$discount=$c['discount_type']==='percent'?floor($sub*$c['discount_value']/100):(int)$c['discount_value'];if($c['max_discount'])$discount=min($discount,(int)$c['max_discount']);out(['valid'=>true,'discount'=>$discount,'message'=>'کد تخفیف اعمال شد','coupon'=>$c]); }
 if($path==='/api/coupons'&&$method==='GET'){admin();out(db()->query('SELECT * FROM coupons ORDER BY created_at DESC')->fetchAll());}
 if($path==='/api/coupons'&&$method==='POST'){admin();$d=body();$id=idv('coup');$q=db()->prepare('INSERT INTO coupons(id,code,discount_type,discount_value,min_purchase,max_discount,max_uses,used_count,is_active,start_date,end_date,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,NOW())');$q->execute([$id,strtoupper($d['code']),$d['discountType']??'percent',(int)$d['discountValue'],(int)($d['minPurchase']??0),$d['maxDiscount']??null,$d['maxUses']??null,0,isset($d['isActive'])?(int)$d['isActive']:1,$d['startDate']??null,$d['endDate']??null]);out(db()->query('SELECT * FROM coupons WHERE id='.db()->quote($id))->fetch(),201);}
 if(preg_match('#^/api/coupons/([^/]+)$#',$path,$m)&&in_array($method,['PUT','DELETE'])){admin();$id=$m[1];if($method==='DELETE'){db()->prepare('DELETE FROM coupons WHERE id=?')->execute([$id]);out(['message'=>'کد تخفیف حذف شد']);}$d=body();$map=['code'=>'code','discountType'=>'discount_type','discountValue'=>'discount_value','minPurchase'=>'min_purchase','maxDiscount'=>'max_discount','maxUses'=>'max_uses','isActive'=>'is_active','startDate'=>'start_date','endDate'=>'end_date'];$sets=[];$vals=[];foreach($map as $a=>$b)if(array_key_exists($a,$d)){$sets[]="$b=?";$vals[]=in_array($b,['discount_value','min_purchase','max_discount','max_uses','is_active'])?($d[$a]===''?null:(int)$d[$a]):$d[$a];}$vals[]=$id;db()->prepare('UPDATE coupons SET '.implode(',',$sets).' WHERE id=?')->execute($vals);out(db()->query('SELECT * FROM coupons WHERE id='.db()->quote($id))->fetch());}

 if($path==='/api/orders'&&$method==='POST'){ $u=user();$d=body();if(empty($d['guestInfo']['name'])||empty($d['guestInfo']['mobile'])||empty($d['items']))out(['error'=>'اطلاعات سفارش ناقص است'],400);$items=[];$subtotal=0;$pdo=db();foreach($d['items'] as $it){$q=$pdo->prepare('SELECT * FROM products WHERE id=?');$q->execute([$it['productId']]);$p=$q->fetch();if(!$p)out(['error'=>'محصول سفارش یافت نشد'],400);$qty=max(1,(int)($it['quantity']??1));$subtotal+=(int)$p['price']*$qty;$items[]=['productId'=>$p['id'],'title'=>$p['title'],'quantity'=>$qty,'unitPrice'=>(int)$p['price'],'region'=>$it['region']??null];}$discount=0;$coupon=null;if(!empty($d['couponCode'])){$q=$pdo->prepare('SELECT * FROM coupons WHERE code=? AND is_active=1');$q->execute([strtoupper($d['couponCode'])]);$coupon=$q->fetch();if($coupon){$discount=$coupon['discount_type']==='percent'?floor($subtotal*$coupon['discount_value']/100):(int)$coupon['discount_value'];if($coupon['max_discount'])$discount=min($discount,(int)$coupon['max_discount']);}}$total=max(0,$subtotal-$discount);$id=idv('ord');$num='ZC-'.date('ymd').'-'.strtoupper(bin2hex(random_bytes(3)));$gi=$d['guestInfo'];$q=$pdo->prepare('INSERT INTO orders(id,order_number,user_id,guest_name,guest_mobile,guest_email,notes,items,subtotal,discount,total,payment_method,payment_status,order_status,license_keys,delivery_instructions,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW(),NOW())');$q->execute([$id,$num,$u['id']??null,$gi['name'],$gi['mobile'],$gi['email']??'',$gi['notes']??'',json_encode($items,JSON_UNESCAPED_UNICODE),$subtotal,$discount,$total,$d['paymentMethod']??'zarinpal','pending','pending',json_encode([],JSON_UNESCAPED_UNICODE),'']);$r=$pdo->query('SELECT * FROM orders WHERE id='.$pdo->quote($id))->fetch();out(order_row($r),201); }
 if($path==='/api/orders'&&$method==='GET'){ $u=auth();$status=$_GET['status']??'';$sql='SELECT * FROM orders';$vals=[];if($u['role']!=='admin'){$sql.=' WHERE user_id=?';$vals[]=$u['id'];}elseif($status){$sql.=' WHERE order_status=?';$vals[]=$status;}$sql.=' ORDER BY created_at DESC';$q=db()->prepare($sql);$q->execute($vals);$rows=$q->fetchAll();foreach($rows as &$r)$r=order_row($r);out($rows);}
 if(preg_match('#^/api/orders/([^/]+)$#',$path,$m)&&$method==='GET'){ $u=auth();$q=db()->prepare('SELECT * FROM orders WHERE id=? OR order_number=?');$q->execute([$m[1],$m[1]]);$r=$q->fetch();if(!$r)out(['error'=>'سفارش یافت نشد'],404);if($u['role']!=='admin'&&$r['user_id']!==$u['id'])out(['error'=>'دسترسی غیرمجاز'],403);out(order_row($r));}
 if(preg_match('#^/api/orders/([^/]+)/status$#',$path,$m)&&$method==='PATCH'){admin();$d=body();$sets=[];$vals=[];if(isset($d['orderStatus'])){$sets[]='order_status=?';$vals[]=$d['orderStatus'];}if(isset($d['paymentStatus'])){$sets[]='payment_status=?';$vals[]=$d['paymentStatus'];}if(isset($d['licenseKeys'])){$sets[]='license_keys=?';$vals[]=json_encode(array_values($d['licenseKeys']),JSON_UNESCAPED_UNICODE);}$if=isset($d['deliveryInstructions']);if($if){$sets[]='delivery_instructions=?';$vals[]=(string)$d['deliveryInstructions'];}$sets[]='updated_at=NOW()';$vals[]=$m[1];db()->prepare('UPDATE orders SET '.implode(',',$sets).' WHERE id=?')->execute($vals);$r=db()->query('SELECT * FROM orders WHERE id='.db()->quote($m[1]))->fetch();out(order_row($r));}

 if($path==='/api/tutorials'&&$method==='GET'){ $all=($_GET['all']??'false')==='true';$q=db()->prepare('SELECT * FROM tutorials '.($all?'':'WHERE active=1 ').'ORDER BY created_at DESC');$q->execute();out($q->fetchAll()); }
 if($path==='/api/tutorials'&&$method==='POST'){admin();$d=body();$id=idv('tutorial');$q=db()->prepare('INSERT INTO tutorials(id,title,platform,description,aparat_url,embed_url,thumbnail,active,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,NOW(),NOW())');$q->execute([$id,$d['title']??'',$d['platform']??'Windows',$d['description']??'',$d['aparatUrl']??'',$d['embedUrl']??'',$d['thumbnail']??'',isset($d['active'])?(int)$d['active']:1]);out(db()->query('SELECT * FROM tutorials WHERE id='.db()->quote($id))->fetch(),201);}
 if(preg_match('#^/api/tutorials/([^/]+)$#',$path,$m)&&in_array($method,['PUT','DELETE'])){admin();$id=$m[1];if($method==='DELETE'){db()->prepare('DELETE FROM tutorials WHERE id=?')->execute([$id]);out(['message'=>'آموزش حذف شد']);}$d=body();$map=['title'=>'title','platform'=>'platform','description'=>'description','aparatUrl'=>'aparat_url','embedUrl'=>'embed_url','thumbnail'=>'thumbnail','active'=>'active'];$sets=[];$vals=[];foreach($map as $a=>$b)if(array_key_exists($a,$d)){$sets[]="$b=?";$vals[]=$b==='active'?(int)$d[$a]:$d[$a];}$sets[]='updated_at=NOW()';$vals[]=$id;db()->prepare('UPDATE tutorials SET '.implode(',',$sets).' WHERE id=?')->execute($vals);out(db()->query('SELECT * FROM tutorials WHERE id='.db()->quote($id))->fetch());}

 if($path==='/api/admin/stats'&&$method==='GET'){admin();$pdo=db();$stats=['users'=>(int)$pdo->query('SELECT COUNT(*) FROM users WHERE role="user"')->fetchColumn(),'orders'=>(int)$pdo->query('SELECT COUNT(*) FROM orders')->fetchColumn(),'pendingOrders'=>(int)$pdo->query('SELECT COUNT(*) FROM orders WHERE order_status="pending"')->fetchColumn(),'revenue'=>(int)$pdo->query('SELECT COALESCE(SUM(total),0) FROM orders WHERE payment_status="paid"')->fetchColumn(),'products'=>(int)$pdo->query('SELECT COUNT(*) FROM products')->fetchColumn()];out($stats);}
 if($path==='/api/admin/users'&&$method==='GET'){admin();$rows=db()->query('SELECT id,name,email,mobile,role,is_active,created_at FROM users ORDER BY created_at DESC')->fetchAll();out($rows);}
 if(preg_match('#^/api/admin/users/([^/]+)$#',$path,$m)&&$method==='PATCH'){admin();$d=body();$sets=[];$vals=[];if(isset($d['name'])){$sets[]='name=?';$vals[]=$d['name'];}if(isset($d['mobile'])){$sets[]='mobile=?';$vals[]=$d['mobile'];}if(isset($d['isActive'])){$sets[]='is_active=?';$vals[]=(int)$d['isActive'];}if(isset($d['role'])&&in_array($d['role'],['user','admin'],true)){$sets[]='role=?';$vals[]=$d['role'];}$vals[]=$m[1];db()->prepare('UPDATE users SET '.implode(',',$sets).' WHERE id=?')->execute($vals);out(db()->query('SELECT id,name,email,mobile,role,is_active,created_at FROM users WHERE id='.db()->quote($m[1]))->fetch());}

 if($path==='/api/settings'&&$method==='GET'){ $q=db()->query('SELECT setting_key,setting_value FROM settings');$o=[];foreach($q as $r)$o[$r['setting_key']]=$r['setting_value'];out($o); }
 if($path==='/api/settings'&&$method==='PUT'){admin();$d=body();$q=db()->prepare('INSERT INTO settings(setting_key,setting_value) VALUES(?,?) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)');foreach($d as $k=>$v)$q->execute([$k,is_scalar($v)?(string)$v:json_encode($v,JSON_UNESCAPED_UNICODE)]);out(['message'=>'تنظیمات ذخیره شد']);}

 out(['error'=>'API endpoint not found'],404);
} catch(Throwable $e){ error_log($e->getMessage()); out(['error'=>'خطای داخلی سرور. تنظیمات دیتابیس و لاگ‌ها را بررسی کنید.'],500); }
