validate([ 'mobile' => ['required', 'regex:/^1[3-9]\d{9}$/'], 'code' => ['required', 'string', 'max:64'], 'competition_slug' => ['required', 'string', 'max:64'], 'lang' => ['sometimes', 'nullable', 'string', 'max:16'], ], PortalLocale::fromRequest($request->input('lang')) === PortalLocale::EN ? [ 'mobile.required' => 'Please enter a valid mobile number.', 'mobile.regex' => 'Please enter a valid mobile number.', 'code.required' => 'Please enter the verification code.', 'competition_slug.required' => 'Missing competition address. Please use the official entry link.', ] : []); $signupLocale = PortalLocale::ZH; $signupLocaleLocked = false; $user = DB::transaction(function () use ($data, &$signupLocale, &$signupLocaleLocked) { $verification = SmsVerification::query() ->where('scene', SmsVerification::SCENE_AUDIENCE_LOGIN) ->where('mobile', $data['mobile']) ->where('status', SmsVerification::STATUS_SENT) ->where('expires_at', '>', now()) ->latest('id') ->lockForUpdate() ->first(); if ($verification === null || $verification->code !== $data['code']) { throw ValidationException::withMessages([ 'code' => [PortalLocale::invalidCodeMessage(isset($data['lang']) ? (string) $data['lang'] : null)], ]); } $competition = Competition::query() ->where('slug', $data['competition_slug']) ->where('published', true) ->first(); if ($competition === null) { throw ValidationException::withMessages([ 'competition_slug' => [PortalLocale::competitionMissingMessage(isset($data['lang']) ? (string) $data['lang'] : null)], ]); } $user = User::query()->firstOrCreate( ['mobile' => $data['mobile']], ['name' => null, 'email' => null, 'password' => null] ); $requested = PortalLocale::fromRequest(isset($data['lang']) ? (string) $data['lang'] : null); $signupLocaleLocked = AudienceRegistration::existsForUserCompetition( (int) $user->id, (int) $competition->id ); $signupLocale = CompetitionPortalLocale::resolveForLogin( (int) $user->id, (int) $competition->id, PortalLocale::PORTAL_AUDIENCE, $requested, $signupLocaleLocked ); $verification->forceFill([ 'status' => SmsVerification::STATUS_USED, 'used_at' => now(), ])->save(); SmsVerification::query() ->where('scene', SmsVerification::SCENE_AUDIENCE_LOGIN) ->where('mobile', $data['mobile']) ->where('id', '<>', $verification->id) ->whereIn('status', [ SmsVerification::STATUS_PENDING, SmsVerification::STATUS_SENT, ]) ->update(['status' => SmsVerification::STATUS_EXPIRED]); return $user; }); $user->tokens()->where('name', 'audience')->delete(); $token = $user->createToken('audience')->plainTextToken; return response()->json([ 'token' => $token, 'token_type' => 'Bearer', 'signup_locale' => $signupLocale, 'signup_locale_locked' => $signupLocaleLocked, 'user' => [ 'id' => $user->id, 'mobile' => $user->mobile, 'name' => $user->name, ], ]); } }