380 lines
13 KiB
PHP
Executable File
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);
|
|
}
|
|
} |