validate([ 'token' => 'required|string', 'package_name' => 'required|string', ]); $response = Http::acceptJson()->withToken($data['token'])->get('https://api.approagency.ir/api/status', [ 'package_name' => $request['package_name'] ]); if (!$response->successful()) { return response()->json(['message' => 'invalid token'], 401); } $response = json_decode($response->body(), true); // First, try to find user by identifier $user = null; if (!empty($response['uuid'])) { $user = User::where('identifier', $response['uuid'])->first(); } // Then by email — only when an email exists. (where('email', null) would // match an arbitrary mobile-only user and cross-link the wrong account.) if (!$user && !empty($response['email'])) { $user = User::where('email', $response['email'])->first(); } // Finally by mobile, so mobile-only users reconnect to their account if (!$user && !empty($response['mobile'])) { $user = User::where('mobile', $response['mobile'])->first(); } $mobile = $response['mobile'] ?? null; // If mobile already exists, set it null to avoid duplicate error if ($mobile && User::where('mobile', $mobile)->where('id', '!=', $user?->id)->exists()) { $mobile = null; } // If user still doesn't exist, create new if (!$user) { $user = User::create([ 'email' => $response['email'], 'identifier' => $response['uuid'] ?? null, 'password' => config('app.default_password'), // 'name' => trim(($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? '')), 'mobile' => $mobile, 'first_name' => $response['first_name'] ?? null, 'last_name' => $response['last_name'] ?? null, 'referral_code' => $response['referral_code'] ?? null, ]); } else { // Only update name and mobile for existing user // Keep the local identifier aligned with the current approagency uuid // (e.g. when the user was matched by mobile, not identifier). $user->identifier = $response['uuid'] ?? $user->identifier; $user->first_name = $response['first_name'] ?? $user->first_name; $user->last_name = $response['last_name'] ?? $user->last_name; // $user->name = trim(($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? '')); $user->mobile = $mobile ?? $user->mobile; $user->referral_code = $response['referral_code'] ?? $user->referral_code; $user->save(); } // Link the referral relationship and reward the referrer (کد معرف) $this->linkReferral($user, $response); $access_token = $user->createToken('user')->plainTextToken; // Whether this user has answered any survey question. $user->setAttribute( 'has_answered_survey', \App\Models\SurveyAnswer::where('user_id', $user->id)->exists() ); return [ 'email' => $user->email, 'identifier' => $user->identifier, 'token' => $access_token, 'user' => $user, 'status' => $response, ]; } private function syncUserFromStatus(array $response, ?User $user = null): User { // Try to find user if not passed if (!$user) { if (!empty($response['uuid'])) { $user = User::where('identifier', $response['uuid'])->first(); } if (!$user && !empty($response['email'])) { $user = User::where('email', $response['email'])->first(); } if (!$user && !empty($response['mobile'])) { $user = User::where('mobile', $response['mobile'])->first(); } } $mobile = $response['mobile'] ?? null; if ($mobile && User::where('mobile', $mobile)->where('id', '!=', $user?->id)->exists()) { $mobile = null; // avoid duplicate } if (!$user) { // create new $user = User::create([ 'email' => $response['email'], 'identifier' => $response['uuid'] ?? null, 'password' => config('app.default_password'), 'mobile' => $mobile, 'first_name' => $response['first_name'] ?? null, 'last_name' => $response['last_name'] ?? null, 'referral_code' => $response['referral_code'] ?? null, 'email_verified_at' => $response['email_verified_at'] ?? null ]); } else { // update existing $user->identifier = $response['uuid'] ?? $user->identifier; $user->first_name = $response['first_name'] ?? $user->first_name; $user->last_name = $response['last_name'] ?? $user->last_name; $user->mobile = $mobile ?? $user->mobile; $user->referral_code = $response['referral_code'] ?? $user->referral_code; $user->email_verified_at = $response['email_verified_at'] ?? $user->email_verified_at; $user->save(); } // Link the referral relationship and reward the referrer (کد معرف) $this->linkReferral($user, $response); return $user; } /** * Resolve the approagency referrer (by uuid/identifier) to a local * meditation user, record the relationship once, and grant the referrer * their per-invite points. The free-month subscription milestone is * handled in approagency, where products/plans/transactions are managed. */ private function linkReferral(User $user, array $response): void { if ($user->referred_by) { return; // already linked — never reward twice } $referrerUuid = $response['referrer_uuid'] ?? null; if (!$referrerUuid) { return; } $referrer = User::where('identifier', $referrerUuid)->first(); if (!$referrer || $referrer->id === $user->id) { return; } $user->referred_by = $referrer->id; $user->save(); // 100 points per successful invite $referrer->increment('referral_points', User::REFERRAL_POINTS_PER_INVITE); } /** * Referral dashboard data (دعوت دوستان page). */ public function referral(Request $request) { $user = auth()->user(); $invites = $user->referrals()->count(); $target = User::REFERRAL_SUBSCRIPTION_TARGET; return response()->json([ 'referral_code' => $user->referral_code, 'referral_points' => (int) $user->referral_points, 'successful_invites' => $invites, 'subscription_target' => $target, 'remaining_to_subscription' => max(0, $target - $invites), 'rewards' => [ 'points_per_invite' => User::REFERRAL_POINTS_PER_INVITE, 'friend_points_share_percent' => (int) (User::REFERRAL_SHARE * 100), 'subscription_invite_target' => $target, ], ]); } public function loginForeginer(Request $request) { $request->validate([ 'user_token' => ['required'], 'package_name' => ['required', new PackageNameExist], ]); $response = $this->repository->userTokenVerify($request['package_name'], $request['user_token']); if ($response['ok']) { $user = User::where('identifier', $response['data']['identifier'])->first(); if (empty($user)) { $user = new User(); $user['identifier'] = $response['data']['identifier']; $user['name'] = $response['data']['username']; $user->save(); } if (!empty($request['fcm_token'])) FcmToken::firstOrCreate(['token' => $request['fcm_token'], 'user_id' => $user['id']]); $access_token = $user->createToken('user')->plainTextToken; return ['identifier' => $user['identifier'], 'token' => $access_token]; } else abort(500); } public function login(Request $request) { $data = $request->validate([ 'auth' => 'string|required', 'password' => 'string|required', 'package_name' => 'string|required', 'fcm_token' => 'string|nullable' ]); if (!$packageName = PackageName::where('name', $data['package_name'])->first()) { return response()->json(['message' => 'package name not found'], 404); } $authType = filter_var($data['auth'], FILTER_VALIDATE_EMAIL) ? 'email' : 'mobile'; if ($authType == 'mobile') { $data['auth'] = MobileNumberHelper::formatMobile($data['auth']); } $user = User::where($authType, $data['auth'])->first(); if (!$user || !Hash::check(trim($data['password']), $user->password)) { return response()->json(['message' => 'wrong credentials'], 404); } // Attach package if not exists if (!$user->packageNames()->where('package_names.id', $packageName->id)->exists()) { $user->packageNames()->attach($packageName->id, [ 'tries' => $packageName->tries, 'fcm_token' => $data['fcm_token'] ?? null, ]); } else { $user->packageNames()->updateExistingPivot($packageName->id, [ 'fcm_token' => $data['fcm_token'] ?? null, ]); } $token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken; return response()->json([ 'user' => $user, 'token' => $token, ]); } public function resendEmailVerification(Request $request) { $request->validate([ 'email' => 'required|email', ]); $user = User::where('email', $request->email)->first(); if (!$user) { return response()->json(['message' => 'User not found'], 404); } if ($user->email_verified_at) { return response()->json(['message' => 'Email is already verified'], 400); } // Generate new code $code = rand(1000, 9999); // Delete previous codes \DB::table('email_verifications')->where('user_id', $user->id)->delete(); // Insert new verification code \DB::table('email_verifications')->insert([ 'user_id' => $user->id, 'code' => $code, 'expires_at' => now()->addMinutes(15), 'created_at' => now(), 'updated_at' => now(), ]); // Send email \Mail::to($user->email)->send(new \App\Mail\VerifyEmailCodeMail($code)); return response()->json([ 'message' => 'Verification email resent successfully' ]); } public function register(Request $request) { $data = $request->validate([ 'first_name' => 'string|nullable', 'last_name' => 'string|nullable', 'email' => 'email|required_without:mobile', 'password' => 'string|nullable|required_with:email', 'mobile' => ['string', new MobileNumber, 'nullable', 'required_without:email'], 'fcm_token' => 'string|nullable', 'package_name' => 'string|required' ]); $packageNameValue = $data['package_name']; unset($data['package_name']); if (isset($data['mobile'])) { $data['mobile'] = MobileNumberHelper::formatMobile($data['mobile']); if (User::where('mobile', $data['mobile'])->exists()) { return response()->json(['message' => 'Mobile number already exists'], 400); } } if (isset($data['email']) && User::where('email', $data['email'])->exists()) { return response()->json(['message' => 'Email already exists'], 400); } // ✅ Check if email can receive a message before creating the user if (isset($data['email'])) { $code = rand(1000, 9999); try { // Try sending email \Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code)); } catch (\Exception $e) { return response()->json([ 'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید', ], 400); } } // Hash password $data['password'] = isset($data['password']) ? Hash::make($data['password']) : Hash::make(Str::random(8)); // Create user only if email sending didn't fail $user = User::create($data); // Attach package if ($packageName = PackageName::where('name', $packageNameValue)->first()) { $user->packageNames()->attach($packageName->id, [ 'tries' => $packageName->tries, 'fcm_token' => $data['fcm_token'] ?? null, ]); } // Save email verification code after user is created if (isset($data['email'])) { \DB::table('email_verifications')->insert([ 'user_id' => $user->id, 'code' => $code, 'expires_at' => now()->addMinutes(15), 'created_at' => now(), 'updated_at' => now(), ]); } // Create token $token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken; return response()->json([ 'message' => 'User registered successfully.', 'user' => $user, 'token' => $token ]); } public function verifyEmailCode(Request $request) { $request->validate([ 'email' => 'required|email', 'code' => 'required|string', ]); $user = User::where('email', $request->email)->first(); if (!$user) { return response()->json(['message' => 'User not found'], 404); } $record = \DB::table('email_verifications') ->where('user_id', $user->id) ->where('code', $request->code) ->where('expires_at', '>', now()) ->first(); if (!$record) { return response()->json(['message' => 'Invalid or expired code'], 400); } // Mark email as verified $user->update(['email_verified_at' => now()]); // Delete verification record \DB::table('email_verifications')->where('id', $record->id)->delete(); $token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken; return response()->json(['message' => 'Email verified successfully', 'token' => $token]); } public function loginOTP(Request $request) { $data = $request->validate([ 'mobile' => ['string', new MobileNumber, 'required'], 'package_name' => 'string|required', 'fcm_token' => 'string', ]); if (!$packageName = PackageName::where('name', $data['package_name'])->first()) { return response()->json([ 'message' => 'package name not found' ], 404); } if (!$user = User::where('mobile', $data['mobile'])->first()) { $user = User::create([ 'mobile' => MobileNumberHelper::formatMobile($data['mobile']), 'password' => config('app.default_password'), ]); } if ($data['mobile'] != config('approo.admin_mobile')) { $otpToken = rand(pow(10, 4), pow(10, 5) - 1); $user->otpTokens()->create([ 'token' => $otpToken ]); $verify = Str::random(8); // SendSMSJob::dispatch([ // 'mobile' => $data['mobile'], // 'message' => "کد ورود به برنامه: $otpToken // @{$packageName->web_app_url} #$otpToken // لغو 11" // ]); SendSMSJob::dispatch([ 'mobile' => $data['mobile'], 'token' => "$otpToken" ]); } if (!$user->packageNames()->where('package_names.id', $packageName->id)->exists()) { $user->packageNames()->attach($packageName->id, ['tries' => $packageName->tries, 'fcm_token' => $data['fcm_token'] ?? null,]); } else { $user->packageNames()->updateExistingPivot($packageName->id,[ 'fcm_token' => $data['fcm_token'] ?? null,]); } return response()->json([ 'message' => 'otp sent', 'verify_code' => $verify ?? null ]); } public function checkOTP(Request $request) { $data = $request->validate([ 'mobile' => ['string', new MobileNumber, 'required'], 'token' => 'string|required', ]); if (!$user = User::where('mobile', $data['mobile'])->first()) { return response()->json([ 'message' => 'user not found' ], 404); } $otpToken = $user->otpTokens()->where('token', $data['token'])->where('created_at', '>', now()->subMinutes(15))->first(); if (!$otpToken && !($data['mobile'] == config('approo.admin_mobile') && $data['token'] == config('approo.admin_otp'))) { return response()->json([ 'message' => 'token not valid' ], 400); } $otpToken?->delete(); $token = ''; if ($user->is_admin) { $token = $user->createToken('token', ['admin'])->plainTextToken; } else { $token = $user->createToken('token', ['user'])->plainTextToken; } return response()->json([ 'user' => $user, 'token' => $token, ]); } public function updateProfile(Request $request) { $data = $request->validate([ 'first_name' => 'string|nullable', 'last_name' => 'string|nullable', 'birthday' => 'date|nullable', 'age' => 'integer|nullable|min:0|max:120', 'gender' => ['integer', 'nullable', Rule::in(User::GENDERS)], 'email' => 'email|nullable', 'mobile' => [new MobileNumber, 'string'], 'avatar' => 'image' ]); if (isset($data['email'])) { $user = User::where('email', $data['email'])->first(); if ($user && $data['email'] != auth()->user()->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'] != auth()->user()->mobile) { return response()->json([ 'message' => 'user with given email exists' ], 400); } } $user = auth()->user(); if (isset($data['avatar'])) { Storage::disk('public')->put("avatars/$user->uuid.png", file_get_contents($data['avatar']->path())); $data['avatar'] = "storage/avatars/$user->uuid.png"; } $user->update($data); return response()->json([ 'user' => $user, 'message' => 'user updated' ]); } public function profile(Request $request) { $data = $request->validate([ 'token' => 'required|string', 'package_name' => 'required|string', ]); $user = auth()->user(); $response = Http::acceptJson()->withToken($data['token'])->get('https://api.approagency.ir/api/status', [ 'package_name' => $request['package_name'] ]); if ($response->successful()) { $user = $this->syncUserFromStatus($response->json(), $user); } $remindersResponse = Http::acceptJson() ->withToken($data['token']) ->get('https://api.approagency.ir/api/reminders', [ 'type' => 'reminder', 'package_name' => $data['package_name'], ]); $reminders = $remindersResponse->successful() ? $remindersResponse->json() : []; $user->load(['breathingSessions.template']); // Whether this user has answered any survey question. $user->setAttribute( 'has_answered_survey', \App\Models\SurveyAnswer::where('user_id', $user->id)->exists() ); return response()->json([ 'user' => $user, 'reminders' => $reminders, ]); } public function status(Request $request) { $data = $request->validate([ 'package_name' => 'string|required' ]); if (!$packageName = PackageName::with('cafeConfig')->where('name', $data['package_name'])->first()) { return response()->json([ 'message' => 'package name not found' ], 404); } $user = auth()->user(); if ($user->mobile == config('approo.admin_mobile')) { $user['products'] = Product::where('package_name_id', $packageName->id)->get(); } else { // if ($packageName->cafeConfig) { // $user['products'] = $user->products()->where('package_name_id', $packageName->id)->get(); // foreach ($user['products'] as $key => $product) { // $service = new CafeService($packageName->cafeConfig); // if ($product->pivot->gateway == Transaction::GATEWAYS['cafe'] && $product->pivot->purchase_token && $product->type != Product::TYPES['permanent']) { // $activeSubscriptions = $service->checkSubscription($product, $product->pivot->purchase_token); // if ($activeSubscriptions) { // $expire = Carbon::createFromTimestamp($activeSubscriptions[0]['validUntilTimestampMsec'] / 1000); // $user->products()->updateExistingPivot($product, ['expire_at' => $expire->format('Y-m-d H:i:s')]); // } else { // $user->products()->updateExistingPivot($product, ['expire_at' => now()->format('Y-m-d H:i:s')]); // } // } // } // } $user['products'] = $user->products() ->where('package_name_id', $packageName->id) ->where(fn($q) => $q->where('user_product.expire_at', '>', now()->addHour())->orWhere('user_product.expire_at', null)) ->get(); $user['package_name'] = $user->packageNames() ->where('package_names.id', $packageName->id) ->first(); } return $user; } public function try(Request $request, $name) { $user = auth()->user(); if (!$packageName = $user->packageNames()->where('name', $name)->first()) { return response()->json([ 'message' => 'package name not found' ], 404); } if ($packageName->pivot->tries <= 0) { return response()->json([ 'message' => 'no more tries' ], 400); } $packageName->pivot->tries->decrement(); return response()->json([ 'message' => 'success' ]); } public function oauthCallback(Request $request) { return dd(Socialite::driver('google')->stateless()->user()); } public function googleLogin(Request $request) { $data = $request->validate([ 'access_token' => 'string|required', 'package_name' => 'string|required', ]); if (!$packageName = PackageName::where('name', $data['package_name'])->first()) { return response()->json([ 'message' => 'package name not found' ], 404); } $response = Http::get("https://www.googleapis.com/oauth2/v3/userinfo", [ 'access_token' => $data['access_token'] ])->json(); if (!isset($response['email'])) { return response()->json([ 'message' => 'invalid token' ], 400); } if (!$user = User::where('email', $response['email'])->first()) { $user = User::create([ 'email' => $response['email'], 'first_name' => $response['given_name'] ?? null, 'last_name' => $response['family_name'] ?? null, 'password' => config('app.default_password'), 'email_verified_at' => now(), ]); if (!$user->packageNames()->where('package_names.id', $packageName->id)->exists()) { $user->packageNames()->attach($packageName->id, [ 'tries' => $packageName->tries, 'source' => $data['source'] ?? 0, ]); } }else { if (!$user->email_verified_at) { $user->update([ 'email_verified_at' => now(), ]); } } $token = $user->createToken('token', ['user'])->plainTextToken; return response()->json([ 'user' => $user, 'token' => $token, ]); } public function leaderBoard(){ $users = User::with(['breathingSessions.template'])->where('xp', '>', 0) ->orderBy('xp', 'desc')->limit(20) ->get(); return $users; } }