Files
approagency/app/Http/Controllers/AdminController.php
T
2026-07-03 17:21:45 +03:30

380 lines
13 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers;
use App\Models\OldPurchase;
use App\Models\PackageName;
use App\Models\Product;
use App\Models\Transaction;
use App\Models\User;
use App\Rules\MobileNumber;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class AdminController extends Controller
{
public function users(Request $request)
{
$data = $request->validate([
'per_page' => 'integer',
'mobile' => 'string',
'email' => 'string',
'package_name' => 'string',
]);
$users = User::when(isset($data['package_name']), fn ($q) => $q->whereHas('packageNames', fn ($q2) => $q2->where('name', $data['package_name'])))
->when(isset($data['mobile']), fn ($q) => $q->where('mobile', $data['mobile']))
->when(isset($data['email']), fn ($q) => $q->where('email', $data['email']))
->paginate($data['per_page'] ?? 30);
return $users;
}
/**
* List all subscription purchases (transactions) across users, with
* filters for email, mobile (phone), package name and payment source
* (gateway). Returns paginated results with the related user, product
* and package eager-loaded for display in the admin panel.
*/
public function purchases(Request $request)
{
$data = $request->validate([
'per_page' => 'integer',
'email' => 'string|nullable',
'mobile' => 'string|nullable',
'package_name' => 'string|nullable',
// payment source: accepts a gateway name (asanpardakht, zarinpal,
// digipay, cafe, myket) or its numeric code
'gateway' => 'string|nullable',
'status' => 'integer|nullable',
]);
// Resolve the payment source to its stored integer code
$gateway = null;
if (!empty($data['gateway'])) {
$gateway = Transaction::GATEWAYS[$data['gateway']]
?? (is_numeric($data['gateway']) ? (int) $data['gateway'] : null);
}
$purchases = Transaction::with(['user', 'product.packageName'])
->when(!empty($data['email']), fn ($q) => $q->whereHas('user', fn ($u) => $u->where('email', 'like', '%' . $data['email'] . '%')))
->when(!empty($data['mobile']), fn ($q) => $q->whereHas('user', fn ($u) => $u->where('mobile', 'like', '%' . $data['mobile'] . '%')))
->when(!empty($data['package_name']), fn ($q) => $q->whereHas('product.packageName', fn ($p) => $p->where('name', $data['package_name'])))
->when(!is_null($gateway), fn ($q) => $q->where('gateway', $gateway))
->when(isset($data['status']), fn ($q) => $q->where('status', $data['status']))
->latest()
->paginate($data['per_page'] ?? 30)
->withQueryString();
return $purchases;
}
public function getUserTransactions(Request $request, $identifier)
{
$data = $request->validate([
'per_page' => 'integer'
]);
if (!$user = $this->findUserByIdentifier($identifier)) {
return response()->json([
'message' => 'user not found'
], 404);
}
$transactions = $user->transactions()->paginate($data['per_page'] ?? 30);
return $transactions;
}
public function getUserStatus(Request $request, $identifier)
{
$data = $request->validate([
'package_name' => 'string'
]);
if (isset($data['package_name']) && !$packageName = PackageName::with('products')->where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
if (!$user = User::with([
'products' => fn ($q) => $q->when(isset($data['package_name']), fn ($q1) => $q1->where('package_name_id', $packageName->id)),
'products.packageName'
])->where(function($query) use ($identifier) {
$this->applyIdentifierCondition($query, $identifier);
})->first()) {
return response()->json([
'message' => 'user not found'
], 404);
}
return $user;
}
public function getUserStatusV1(Request $request, $identifier)
{
$data = $request->validate([
'package_name' => 'string'
]);
$userIdentifier = $identifier;
if (isset($data['package_name']) && !$packageName = PackageName::with('products')
->where('name', $data['package_name'])->first()) {
return response()->json([
'message' => 'package name not found'
], 404);
}
// For OldPurchase, we need to determine if identifier is mobile or email
if ($this->isMobileNumber($identifier)) {
$mobile = preg_replace('/09/', '989', $identifier, 1);
$query = OldPurchase::when(isset($data['package_name']), fn ($q) => $q->where('package_name', $data['package_name']))
->where('mobile', $mobile);
} else {
// Assuming OldPurchase might have email field or we need to find user first
if (!$user = $this->findUserByIdentifier($identifier)) {
return response()->json([
'message' => 'user not found'
], 404);
}
$query = OldPurchase::when(isset($data['package_name']), fn ($q) => $q->where('package_name', $data['package_name']))
->where('mobile', preg_replace('/09/', '989', $user->mobile, 1));
}
if (!$query->first()) {
return response()->json([
'message' => 'user is not paid'
], 400);
}
return response()->json([
'message' => 'user is paid'
]);
}
public function subscribeUser(Request $request, $identifier)
{
$data = $request->validate([
'product_id' => 'integer|required'
]);
if (!$user = $this->findUserByIdentifier($identifier)) {
return response()->json([
'message' => 'user not found'
], 404);
}
if (!$product = Product::where('id', $data['product_id'])->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$product->buy($user);
return response()->json([
'message' => 'subscribed successfuly'
]);
}
/**
* List users who reached the referral invite target (کد معرف) and have
* not yet been granted their free-month subscription.
*/
public function pendingReferralRewards(Request $request)
{
$target = User::REFERRAL_SUBSCRIPTION_TARGET;
// Use has() (a correlated subquery in WHERE) rather than having() on the
// withCount alias — Postgres does not allow select aliases in HAVING.
$users = User::withCount('referrals')
->whereNull('referral_subscription_granted_at')
->has('referrals', '>=', $target)
->orderByDesc('referrals_count')
->get(['id', 'uuid', 'first_name', 'last_name', 'email', 'mobile', 'referral_code']);
return response()->json([
'subscription_target' => $target,
'data' => $users->map(fn ($u) => [
'id' => $u->id,
'uuid' => $u->uuid,
'name' => trim($u->first_name . ' ' . $u->last_name),
'email' => $u->email,
'mobile' => $u->mobile,
'referral_code' => $u->referral_code,
'successful_invites' => $u->referrals_count,
]),
]);
}
/**
* Grant the free-month subscription to a user who reached the referral
* invite target. Defaults to the meditation package's monthly product,
* overridable via product_id. Marks the grant so it happens only once.
*/
public function fulfillReferralReward(Request $request, $userId)
{
$data = $request->validate([
'product_id' => 'integer|nullable',
]);
if (!$user = User::find($userId)) {
return response()->json(['message' => 'user not found'], 404);
}
if ($user->referral_subscription_granted_at) {
return response()->json(['message' => 'referral subscription already granted'], 400);
}
if ($user->referrals()->count() < User::REFERRAL_SUBSCRIPTION_TARGET) {
return response()->json(['message' => 'user has not reached the referral target yet'], 400);
}
// Resolve which monthly product to grant
if (!empty($data['product_id'])) {
$product = Product::where('id', $data['product_id'])->first();
} else {
$package = PackageName::where('name', User::REFERRAL_PACKAGE_NAME)->first();
$product = $package
? Product::where('package_name_id', $package->id)
->where('type', Product::TYPES['monthly'])
->first()
: null;
}
if (!$product) {
return response()->json(['message' => 'monthly product not found'], 404);
}
$product->buy($user);
$user->referral_subscription_granted_at = now();
$user->save();
return response()->json([
'message' => 'referral subscription granted',
'user_id' => $user->id,
'product_id' => $product->id,
]);
}
public function unsubscribeUser(Request $request, $identifier)
{
$data = $request->validate([
'product_id' => 'integer|required'
]);
if (!$user = $this->findUserByIdentifier($identifier)) {
return response()->json([
'message' => 'user not found'
], 404);
}
if (!$product = Product::where('id', $data['product_id'])->first()) {
return response()->json([
'message' => 'product not found'
], 404);
}
$user->products()->updateExistingPivot($product, ['expire_at' => now()->format('Y-m-d H:i:s')]);
return response()->json([
'message' => 'unsubscribed successfuly'
]);
}
public function deleteUser(Request $request, $identifier)
{
if (!$user = $this->findUserByIdentifier($identifier)) {
return response()->json([
'message' => 'user not found'
], 404);
}
$user->delete();
return response()->json([
'message' => 'user deleted'
]);
}
public function updateUserProfile(Request $request, $identifier)
{
$data = $request->validate([
'first_name' => 'string',
'last_name' => 'string',
'email' => 'email',
'mobile' => [new MobileNumber, 'string'],
'avatar' => 'image'
]);
if (!$subjectUser = $this->findUserByIdentifier($identifier)) {
return response()->json([
'message' => 'user not found'
], 404);
}
if (isset($data['email'])) {
$user = User::where('email', $data['email'])->first();
if ($user && $data['email'] != $subjectUser->email) {
return response()->json([
'message' => 'user with given email exists'
], 400);
}
}
if (isset($data['mobile'])) {
$user = User::where('mobile', $data['mobile'])->first();
if ($user && $data['mobile'] != $subjectUser->mobile) {
return response()->json([
'message' => 'user with given mobile exists'
], 400);
}
}
if (isset($data['avatar'])) {
Storage::disk('public')->put("avatars/$subjectUser->uuid.png", file_get_contents($data['avatar']->path()));
$data['avatar'] = "storage/avatars/$subjectUser->uuid.png";
}
$subjectUser->update($data);
return response()->json([
'message' => 'user updated'
]);
}
/**
* Helper method to find user by mobile or email
*/
private function findUserByIdentifier($identifier)
{
return User::where(function($query) use ($identifier) {
$this->applyIdentifierCondition($query, $identifier);
})->first();
}
/**
* Helper method to apply condition for mobile or email
*/
private function applyIdentifierCondition($query, $identifier)
{
if ($this->isMobileNumber($identifier)) {
$query->where('mobile', $identifier);
} else {
$query->where('email', $identifier);
}
}
/**
* Helper method to check if identifier is a mobile number
*/
private function isMobileNumber($identifier)
{
// Simple check for mobile number pattern (starts with 09 or +98 or 989)
return preg_match('/^(09|\+98|989)\d{9}$/', $identifier);
}
}