This commit is contained in:
2026-08-11 22:08:42 +03:30
2 changed files with 174 additions and 52 deletions
@@ -9,7 +9,6 @@ use App\Models\Order;
use App\Models\Price;
use App\Models\Product;
use App\Models\ProfitConversion;
use App\Models\TransactionsAdmin;
use App\Models\Withdraw;
use App\Services\BalanceReportService;
use App\Services\ConfigService;
@@ -28,7 +27,6 @@ class DashboardController extends Controller
public function dashboardMaster(Request $request)
{
$tallaPrice = Price::latest()->first();
// $talla->update_date = Verta::instance(Carbon::parse($talla->updated_at))->format('Y/n/j-H:i');
$new_customer_major = Customer::where('type',1)->whereDate('created_at','<=',now())->whereDate('created_at','>=',now()->subMonths(3))
->count();
$new_customer_single = Customer::where('type',2)->whereDate('created_at','<=',now())->whereDate('created_at','>=',now()->subMonths(3))
@@ -37,33 +35,39 @@ class DashboardController extends Controller
$all_customer_single = Customer::where('type',2)->count();
$archive_customer_major = Customer::where('type',1)->where('is_archive',true)->count();
$archive_customer_single = Customer::where('type',2)->where('is_archive',true)->count();
$all_gold = Order::where('direction',2)->where('type',1)->sum('amount');// جمع طلاهایی که از مشتری خریدیم
$all_gold_single = Order::where('direction',2)->where('type',2)->sum('amount');// جمع طلاهایی که از مشتری خریدیم
$await_list = Order::where('direction',1)->where('sts',1)->get();
$best_customer_major=Order::with('customer')->where('type',1)->where('sts',2)->select('customer_id',DB::raw('sum(amount) as amount'))
->groupBy('customer_id')->limit(10)->get();
$best_customer_single=Wallet::with('customer')->whereNull('fund_id')->where('owner_type',2)->orderBy('gold','desc')->limit(10)->get();
$all_gold = Order::where('direction',2)->where('type',1)->sum('amount');
$all_gold_single = Order::where('direction',2)->where('type',2)->sum('amount');
$await_list = Order::query()
->where('direction', 1)
->where('sts', 1)
->select(['id', 'customer_id', 'amount', 'total_price', 'sts', 'direction', 'type', 'created_at'])
->with(['customer:id,name,mobile'])
->orderByDesc('id')
->limit(50)
->get();
$best_customer_major = Order::with('customer:id,name,mobile')
->where('type', 1)
->where('sts', 2)
->select('customer_id', DB::raw('sum(amount) as amount'))
->groupBy('customer_id')
->orderByDesc('amount')
->limit(10)
->get();
$best_customer_single = Wallet::with('customer:id,name,mobile')
->whereNull('fund_id')
->where('owner_type', 2)
->orderByDesc('gold')
->limit(10)
->get(['id', 'owner', 'owner_type', 'gold', 'fund_id']);
// GT
$GT_all_customers = Customer::where('type',2)->where('is_archive',false)->count();
$GT_no_login_customers = Customer::where('type',2)->where('is_archive',false)->where('last_seen_at',null)->count();
$GT_no_login_customers = Customer::where('type',2)->where('is_archive',false)->whereNull('last_seen_at')->count();
$GT_no_valid_customers = Customer::where('type',2)->where('is_archive',false)->where('is_legal',0)->count();
$GT_no_valid_customers_data = Customer::where('type',2)->where('is_archive',false)->where('is_legal',0)->get(['id','name','mobile']);
$GT_active_customers = Customer::where('type', 2)
->where('is_archive', false)
->whereIn('id', function($query) {
$query->select('owner')
->from('wallets');
->whereIn('id', function ($query) {
$query->select('owner')->from('wallets');
})->count();
$GT_active_customers_data = Customer::where('type', 2)
->where('is_archive', false)
->whereIn('id', function($query) {
$query->select('owner')
->from('wallets');
})->get(['id','name','mobile']);
$transactions_admins = TransactionsAdmin::with(['withdraw','order',"user",'customerTo','customer'])->get();
$startDate = $request->startDateProduct
? Carbon::parse($request->startDateProduct)->startOfDay()
@@ -72,23 +76,32 @@ class DashboardController extends Controller
? Carbon::parse($request->endDateProduct)->endOfDay()
: now()->endOfDay();
$products_report = Product::with(['limitedTransactions' => function ($query) use ($startDate, $endDate) {
$query->whereBetween('created_at', [$startDate, $endDate]);
}])
->whereHas('limitedTransactions', function ($query) use ($startDate, $endDate) {
$query->whereBetween('created_at', [$startDate, $endDate]);
})
// Aggregation در SQL — لود هزاران ردیف transaction باعث OOM می‌شد
$productAgg = DB::table('transactions')
->select(
'product_id',
DB::raw('SUM(CASE WHEN gold > 0 THEN gold ELSE 0 END) as transaction_sum_gold_buy'),
DB::raw('SUM(CASE WHEN gold < 0 THEN gold ELSE 0 END) as total_negative_gold_sell'),
DB::raw('SUM(gold) as total_gold')
)
->whereNotNull('product_id')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('product_id')
->get();
$products_report->each(function ($product) {
$product->transaction_sum_gold_buy = $product->limitedTransactions
->where('gold', '>', 0)
->sum('gold');
$product->total_negative_gold_sell = $product->limitedTransactions
->where('gold', '<', 0)
->sum('gold');
$product->total_gold = $product->limitedTransactions->sum('gold');
});
$productTitles = Product::query()
->whereIn('id', $productAgg->pluck('product_id')->filter()->all())
->pluck('title', 'id');
$products_report = $productAgg->map(function ($row) use ($productTitles) {
return [
'id' => (int) $row->product_id,
'title' => (string) ($productTitles[$row->product_id] ?? ''),
'transaction_sum_gold_buy' => (float) $row->transaction_sum_gold_buy,
'total_negative_gold_sell' => (float) $row->total_negative_gold_sell,
'total_gold' => (float) $row->total_gold,
];
})->values();
$gramPrice = (int) ($tallaPrice->price ?? 0);
$FPrices = [];
@@ -113,11 +126,12 @@ class DashboardController extends Controller
'GT_all_customers'=>$GT_all_customers,
'GT_no_login_customers'=>$GT_no_login_customers,
'GT_no_valid_customers'=>$GT_no_valid_customers,
'GT_no_valid_customers_data'=>$GT_no_valid_customers_data,
// لیست کامل مشتریان/تراکنش‌ها دیگر برنمی‌گردد (OOM روی JsonResponse)
'GT_no_valid_customers_data'=>[],
'GT_active_customers'=>$GT_active_customers,
'GT_active_customers_data'=>$GT_active_customers_data,
'GT_active_customers_data'=>[],
'GT_deActive_customers'=>$GT_all_customers-$GT_active_customers,
'GT_transactions_admins'=>$transactions_admins,
'GT_transactions_admins'=>[],
'GT_products_report' => $products_report,
'update_date' => now()->timezone('Asia/Tehran')->format('Y-m-d H:i:s'),
'pricem' => round($gramPrice * 4.331802 / 10000) * 10000,
@@ -135,7 +149,6 @@ class DashboardController extends Controller
'await_order'=>$await_list,
'best_customer'=>$best_customer_major,
'best_customer_single'=>$best_customer_single,
]);
}
@@ -6,29 +6,138 @@ use Closure;
use Illuminate\Http\Request;
/**
* Bearer token must match SUPERADMIN_REMOTE_TOKEN (same as SuperAdmin Tenant.remoteApiToken).
* Auth for SuperAdmin → product (kill-switch / super settings):
* 1) Authorization Bearer matching SUPERADMIN_REMOTE_TOKEN
* OR SUPERADMIN_SMS_SHARED_SECRET (CDN-safe; same secret as SMS)
* 2) Optional: shared HMAC headers X-Abshode-* (may be stripped by CDN)
*/
class EnsureSuperAdminRemoteToken
{
private const MAX_SKEW_SECONDS = 300;
private const CANONICAL_PATH = '/api/super/settings';
public function handle(Request $request, Closure $next)
{
$expected = trim((string) config('services.superadmin.remote_token', ''));
if ($expected === '') {
if ($this->authByBearer($request)) {
return $next($request);
}
if ($this->authByHmac($request)) {
return $next($request);
}
$hasSecret = $this->sharedSecret() !== '';
$hasToken = $this->bearerToken() !== '';
if (! $hasSecret && ! $hasToken) {
return response()->json([
'message' => 'SUPERADMIN_REMOTE_TOKEN is not configured',
'message' => 'SUPERADMIN_SMS_SHARED_SECRET or SUPERADMIN_REMOTE_TOKEN is not configured',
], 503);
}
return response()->json(['message' => 'Unauthorized'], 401);
}
private function authByHmac(Request $request): bool
{
$secret = $this->sharedSecret();
if ($secret === '') {
return false;
}
$domainHeader = trim((string) $request->header('X-Abshode-Domain', ''));
$tsRaw = trim((string) $request->header('X-Abshode-Timestamp', ''));
$sigRaw = trim((string) $request->header('X-Abshode-Signature', ''));
if ($domainHeader === '' || $tsRaw === '' || $sigRaw === '') {
return false;
}
if (! ctype_digit($tsRaw)) {
return false;
}
$timestamp = (int) $tsRaw;
if (abs(time() - $timestamp) > self::MAX_SKEW_SECONDS) {
return false;
}
$domain = $this->normalizeHost($domainHeader);
if ($domain === '' || ! $this->domainAllowed($domain, $request)) {
return false;
}
$method = strtoupper($request->getMethod());
$payload = $timestamp.'.'.$method.'.'.self::CANONICAL_PATH.'.'.$domain;
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals(strtolower($expected), strtolower($sigRaw));
}
private function authByBearer(Request $request): bool
{
$auth = (string) $request->header('Authorization', '');
$token = '';
if (preg_match('/^\s*Bearer\s+(.+)\s*$/i', $auth, $m)) {
$token = trim($m[1]);
if (! preg_match('/^\s*Bearer\s+(.+)\s*$/i', $auth, $m)) {
return false;
}
if ($token === '' || ! hash_equals($expected, $token)) {
return response()->json(['message' => 'Unauthorized'], 401);
$token = trim($m[1]);
if ($token === '') {
return false;
}
return $next($request);
foreach ([$this->bearerToken(), $this->sharedSecret()] as $expected) {
if ($expected !== '' && hash_equals($expected, $token)) {
return true;
}
}
return false;
}
private function domainAllowed(string $domain, Request $request): bool
{
$candidates = [
$this->configuredDomain(),
$this->normalizeHost((string) $request->getHost()),
];
foreach ($candidates as $candidate) {
if ($candidate !== '' && hash_equals($candidate, $domain)) {
return true;
}
}
return false;
}
private function sharedSecret(): string
{
return trim((string) config('services.superadmin.sms_shared_secret', ''));
}
private function bearerToken(): string
{
return trim((string) config('services.superadmin.remote_token', ''));
}
private function configuredDomain(): string
{
$override = trim((string) config('services.superadmin.sms_domain', ''));
if ($override !== '') {
return $this->normalizeHost($override);
}
$fromApp = parse_url((string) config('app.url'), PHP_URL_HOST);
return $this->normalizeHost(is_string($fromApp) ? $fromApp : '');
}
private function normalizeHost(string $value): string
{
$host = strtolower(trim($value));
$host = preg_replace('#^https?://#', '', $host) ?? $host;
$host = explode('/', $host)[0] ?? $host;
$host = explode(':', $host)[0] ?? $host;
return trim($host);
}
}