增加 Auth 基本功能

This commit is contained in:
iVampireSP.com 2023-02-02 02:58:44 +08:00
parent 682d853c0a
commit 656f6bf817
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
50 changed files with 2439 additions and 574 deletions

View File

@ -0,0 +1,207 @@
<?php
namespace App\Helpers\Auth;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\Response;
trait AuthenticatesUsers
{
use RedirectsUsers, ThrottlesLogins;
/**
* Show the application's login form.
*
* @return View
*/
public function showLoginForm(): View
{
return view('auth.login');
}
/**
* Handle a login request to the application.
*
* @param Request $request
* @return Response
*
* @throws ValidationException
*/
public function login(Request $request): Response
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
if ($request->hasSession()) {
$request->session()->put('auth.password_confirmed_at', time());
}
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to log in and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse();
}
/**
* Validate the user login request.
*
* @param Request $request
* @return void
*
* @throws ValidationException
*/
protected function validateLogin(Request $request): void
{
$request->validate([
$this->username() => 'required|string',
'password' => 'required|string',
]);
}
/**
* Attempt to log the user into the application.
*
* @param Request $request
* @return bool
*/
protected function attemptLogin(Request $request): bool
{
return $this->guard()->attempt(
$this->credentials($request), $request->boolean('remember')
);
}
/**
* Get the needed authorization credentials from the request.
*
* @param Request $request
* @return array
*/
protected function credentials(Request $request): array
{
return $request->only($this->username(), 'password');
}
/**
* Send the response after the user was authenticated.
*
* @param Request $request
* @return JsonResponse|RedirectResponse
*/
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
// if ($response = $this->authenticated($request, $this->guard()->user())) {
// return $response;
// }
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect()->intended($this->redirectPath());
}
/**
* The user has been authenticated.
*
* @param Request $request
* @param mixed $user
* @return void
*/
protected function authenticated(Request $request, mixed $user): void
{
//
}
/**
* Get the failed login response instance.
*
*
* @return Response
*
* @throws ValidationException
*/
protected function sendFailedLoginResponse(): Response
{
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
]);
}
/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username(): string
{
return 'email';
}
/**
* Log the user out of the application.
*
* @param Request $request
* @return RedirectResponse|JsonResponse
*/
public function logout(Request $request): JsonResponse|RedirectResponse
{
$this->guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
// if ($response = $this->loggedOut($request)) {
// return $response;
// }
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect('/');
}
/**
* The user has logged out of the application.
*
* @param Request $request
* @return void
*/
protected function loggedOut(Request $request): void
{
//
}
/**
* Get the guard to be used during authentication.
*
* @return StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard();
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Helpers\Auth;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
trait ConfirmsPasswords
{
use RedirectsUsers;
/**
* Display the password confirmation view.
*
* @return View
*/
public function showConfirmForm(): View
{
return view('auth.passwords.confirm');
}
/**
* Confirm the given user's password.
*
* @param Request $request
* @return RedirectResponse|JsonResponse
*/
public function confirm(Request $request): JsonResponse|RedirectResponse
{
$request->validate($this->rules(), $this->validationErrorMessages());
$this->resetPasswordConfirmationTimeout($request);
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect()->intended($this->redirectPath());
}
/**
* Reset the password confirmation timeout.
*
* @param Request $request
* @return void
*/
protected function resetPasswordConfirmationTimeout(Request $request): void
{
$request->session()->put('auth.password_confirmed_at', time());
}
/**
* Get the password confirmation validation rules.
*
* @return array
*/
protected function rules(): array
{
return [
'password' => 'required|current_password:web',
];
}
/**
* Get the password confirmation validation error messages.
*
* @return array
*/
protected function validationErrorMessages(): array
{
return [];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Helpers\Auth;
trait RedirectsUsers
{
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath(): string
{
if (method_exists($this, 'redirectTo')) {
return $this->redirectTo();
}
return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Helpers\Auth;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
trait RegistersUsers
{
use RedirectsUsers;
/**
* Show the application registration form.
*
* @return View
*/
public function showRegistrationForm(): View
{
return view('auth.register');
}
/**
* Handle a registration request for the application.
*
* @param Request $request
* @return RedirectResponse|JsonResponse
*/
public function register(Request $request): JsonResponse|RedirectResponse
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
// if ($response = $this->registered($request, $user)) {
// return $response;
// }
return $request->wantsJson()
? new JsonResponse([], 201)
: redirect($this->redirectPath());
}
/**
* Get the guard to be used during registration.
*
* @return StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard();
}
/**
* The user has been registered.
*
* @param Request $request
* @param mixed $user
* @return void
*/
protected function registered(Request $request, mixed $user): void
{
//
}
}

View File

@ -0,0 +1,203 @@
<?php
namespace App\Helpers\Auth;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Contracts\Auth\CanResetPassword;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
trait ResetsPasswords
{
use RedirectsUsers;
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param Request $request
* @return Factory|View
*/
public function showResetForm(Request $request): Factory|View
{
$token = $request->route()->parameter('token');
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Reset the given user's password.
*
* @param Request $request
* @return RedirectResponse|JsonResponse
*/
public function reset(Request $request): JsonResponse|RedirectResponse
{
$request->validate($this->rules(), $this->validationErrorMessages());
// 如果当前用户已登录,那么将 email 设置为当前用户的 email
// if (Auth::guard('web')->check()) {
// $request->merge(['email' => Auth::user()->email]);
// }
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise, we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($request, $response)
: $this->sendResetFailedResponse($request, $response);
}
/**
* Get the password reset validation rules.
*
* @return array
*/
protected function rules(): array
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => ['required', 'confirmed', Rules\Password::defaults()],
];
}
/**
* Get the password reset validation error messages.
*
* @return array
*/
protected function validationErrorMessages(): array
{
return [];
}
/**
* Get the password reset credentials from the request.
*
* @param Request $request
* @return array
*/
protected function credentials(Request $request): array
{
return $request->only(
'email', 'password', 'password_confirmation', 'token'
);
}
/**
* Reset the given user's password.
*
* @param CanResetPassword|User $user
* @param string $password
* @return void
*/
protected function resetPassword(CanResetPassword|User $user, string $password): void
{
$this->setUserPassword($user, $password);
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
$this->guard()->login($user);
}
/**
* Set the user's password.
*
* @param CanResetPassword $user
* @param string $password
* @return void
*/
protected function setUserPassword(CanResetPassword $user, string $password): void
{
// if it has password field
if (isset($user->password)) {
$user->password = Hash::make($password);
}
}
/**
* Get the response for a successful password reset.
*
* @param Request $request
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetResponse(Request $request, string $response): JsonResponse|RedirectResponse
{
if ($request->wantsJson()) {
return new JsonResponse(['message' => trans($response)], 200);
}
return redirect($this->redirectPath())
->with('status', trans($response));
}
/**
* Get the response for a failed password reset.
*
* @param Request $request
* @param string $response
* @return RedirectResponse
*/
protected function sendResetFailedResponse(Request $request, string $response): RedirectResponse
{
if ($request->wantsJson()) {
throw ValidationException::withMessages([
'email' => [trans($response)],
]);
}
return redirect()->back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the broker to be used during password reset.
*
* @return PasswordBroker
*/
public function broker(): PasswordBroker
{
return Password::broker();
}
/**
* Get the guard to be used during password reset.
*
* @return StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard();
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace App\Helpers\Auth;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
trait SendsPasswordResetEmails
{
/**
* Display the form to request a password reset link.
*
* @return View
*/
public function showLinkRequestForm(): View
{
return view('auth.passwords.email');
}
/**
* Send a reset link to the given user.
*
* @param Request $request
* @return RedirectResponse|JsonResponse
*/
public function sendResetLinkEmail(Request $request): JsonResponse|RedirectResponse
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
/**
* Validate the email for the given request.
*
* @param Request $request
* @return void
*/
protected function validateEmail(Request $request): void
{
$request->validate(['email' => 'required|email']);
}
/**
* Get the needed authentication credentials from the request.
*
* @param Request $request
* @return array
*/
protected function credentials(Request $request): array
{
return $request->only('email');
}
/**
* Get the response for a successful password reset link.
*
* @param Request $request
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetLinkResponse(Request $request, string $response): JsonResponse|RedirectResponse
{
return $request->wantsJson()
? new JsonResponse(['message' => trans($response)], 200)
: back()->with('status', trans($response));
}
/**
* Get the response for a failed password reset link.
*
* @param Request $request
* @param string $response
* @return RedirectResponse
*
* @throws ValidationException
*/
protected function sendResetLinkFailedResponse(Request $request, string $response): RedirectResponse
{
if ($request->wantsJson()) {
throw ValidationException::withMessages([
'email' => [trans($response)],
]);
}
return back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the broker to be used during password reset.
*
* @return PasswordBroker
*/
public function broker(): PasswordBroker
{
return Password::broker();
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace App\Helpers\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
trait ThrottlesLogins
{
/**
* Determine if the user has too many failed login attempts.
*
* @param Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request): bool
{
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request), $this->maxAttempts()
);
}
/**
* Increment the login attempts for the user.
*
* @param Request $request
* @return void
*/
protected function incrementLoginAttempts(Request $request): void
{
$this->limiter()->hit(
$this->throttleKey($request), $this->decayMinutes() * 60
);
}
/**
* Redirect the user after determining they are locked out.
*
* @param Request $request
* @return ResponseAlias
*
* @throws ValidationException
*/
protected function sendLockoutResponse(Request $request): ResponseAlias
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);
throw ValidationException::withMessages([
$this->username() => [trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
])],
])->status(ResponseAlias::HTTP_TOO_MANY_REQUESTS);
}
/**
* Clear the login locks for the given user credentials.
*
* @param Request $request
* @return void
*/
protected function clearLoginAttempts(Request $request): void
{
$this->limiter()->clear($this->throttleKey($request));
}
/**
* Fire an event when a lockout occurs.
*
* @param Request $request
* @return void
*/
protected function fireLockoutEvent(Request $request): void
{
event(new Lockout($request));
}
/**
* Get the throttle key for the given request.
*
* @param Request $request
* @return string
*/
protected function throttleKey(Request $request): string
{
return Str::transliterate(Str::lower($request->input($this->username())).'|'.$request->ip());
}
/**
* Get the rate limiter instance.
*
* @return RateLimiter
*/
protected function limiter(): RateLimiter
{
return app(RateLimiter::class);
}
/**
* Get the maximum number of attempts to allow.
*
* @return int
*/
public function maxAttempts(): int
{
return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5;
}
/**
* Get the number of minutes to throttle for.
*
* @return int
*/
public function decayMinutes(): int
{
return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace App\Helpers\Auth;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
trait VerifiesEmails
{
use RedirectsUsers;
/**
* Show the email verification notice.
*
* @param Request $request
* @return RedirectResponse|View
*/
public function show(Request $request): View|RedirectResponse
{
return $request->user()->hasVerifiedEmail()
? redirect($this->redirectPath())
: view('auth.verify');
}
/**
* Mark the authenticated user's email address as verified.
*
* @param Request $request
* @return JsonResponse|RedirectResponse
*
* @throws AuthorizationException
*/
public function verify(Request $request): JsonResponse|RedirectResponse
{
if (! hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->route('hash'), sha1($request->user()->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
// if ($response = $this->verified($request)) {
// return $response;
// }
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect($this->redirectPath())->with('verified', true);
}
/**
* The user has been verified.
*
* @param Request $request
* @return void
*/
protected function verified(Request $request): void
{
//
}
/**
* Resend the email verification notification.
*
* @param Request $request
* @return JsonResponse|RedirectResponse
*/
public function resend(Request $request): JsonResponse|RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect($this->redirectPath());
}
$request->user()->sendEmailVerificationNotification();
return $request->wantsJson()
? new JsonResponse([], 202)
: back()->with('resent', true);
}
}

View File

@ -148,4 +148,17 @@ public function update(Request $request, User $user): RedirectResponse
return back()->with('success', '已完成所有更改。'); return back()->with('success', '已完成所有更改。');
} }
/**
* Remove the specified resource from storage.
*
* @param User $user
* @return RedirectResponse
*/
public function destroy(User $user): RedirectResponse
{
$user->delete();
return redirect()->route('admin.users.index')->with('success', '已删除此用户。');
}
} }

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Helpers\Auth\ConfirmsPasswords;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**
* Where to redirect users when the intended url fails.
*
* @var string
*/
protected string $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Helpers\Auth\SendsPasswordResetEmails;
use App\Http\Controllers\Controller;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
public function __construct()
{
$this->middleware('throttle:2,1')->only('sendResetLinkEmail');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Helpers\Auth\AuthenticatesUsers;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected string $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Helpers\Auth\RegistersUsers;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected string $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Helpers\Auth\ResetsPasswords;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected string $redirectTo = RouteServiceProvider::HOME;
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Helpers\Auth\VerifiesEmails;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class VerificationController extends Controller
{
/*
|--------------------------------------------------------------------------
| Email Verification Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling email verification for any
| user that recently registered with the application. Emails may also
| be re-sent if the user didn't receive the original email message.
|
*/
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* @var string
*/
protected string $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:2,1')->only('resend');
$this->middleware('throttle:6,1')->only('verify');
}
}

View File

@ -2,18 +2,19 @@
namespace App\Models; namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Exceptions\User\BalanceNotEnoughException; use App\Exceptions\User\BalanceNotEnoughException;
use Carbon\Exceptions\InvalidFormatException; use Carbon\Exceptions\InvalidFormatException;
use GeneaLabs\LaravelModelCaching\CachedBuilder; use GeneaLabs\LaravelModelCaching\CachedBuilder;
use GeneaLabs\LaravelModelCaching\Traits\Cachable; use GeneaLabs\LaravelModelCaching\Traits\Cachable;
use Illuminate\Auth\MustVerifyEmail; use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -21,9 +22,9 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable class User extends Authenticatable implements MustVerifyEmail
{ {
use HasApiTokens, HasFactory, Notifiable, Cachable, MustVerifyEmail; use HasApiTokens, HasFactory, Notifiable, SoftDeletes, Prunable, Cachable;
public array $publics = [ public array $publics = [
'id', 'id',
@ -114,6 +115,11 @@ protected static function boot()
} }
} }
}); });
static::deleting(function (self $user) {
$user->tokens()->delete();
$user->hosts()->update(['status' => 'suspended', 'suspended_at' => now()]);
});
} }
public function hosts(): HasMany public function hosts(): HasMany
@ -167,6 +173,11 @@ public function selectPublic(): self|Builder|CachedBuilder
return $this->select($this->publics); return $this->select($this->publics);
} }
public function prunable()
{
return static::where('deleted_at', '<=', now()->subWeek());
}
public function startTransfer(self $to, string $amount, string|null $description) public function startTransfer(self $to, string $amount, string|null $description)
{ {
$description_from = "转账给 $to->name($to->email)"; $description_from = "转账给 $to->name($to->email)";

View File

@ -39,6 +39,9 @@
"beyondcode/laravel-query-detector": "^1.6", "beyondcode/laravel-query-detector": "^1.6",
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",
"jetbrains/phpstorm-attributes": "^1.0", "jetbrains/phpstorm-attributes": "^1.0",
"laravel-lang/attributes": "^2.1",
"laravel-lang/lang": "^12.14",
"laravel-lang/publisher": "^14.5",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"laravel/sail": "^1.0.1", "laravel/sail": "^1.0.1",
"laravel/telescope": "^4.9", "laravel/telescope": "^4.9",

691
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7cdf1f58081bceb75c91bcacfc8d51a8", "content-hash": "bb7e2da0ce001f8fb09ec5a5924405bf",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -9034,6 +9034,58 @@
} }
], ],
"packages-dev": [ "packages-dev": [
{
"name": "archtechx/enums",
"version": "v0.3.1",
"source": {
"type": "git",
"url": "https://github.com/archtechx/enums.git",
"reference": "373a86a16e7233a56dd942d422af2cc59a2becb3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/archtechx/enums/zipball/373a86a16e7233a56dd942d422af2cc59a2becb3",
"reference": "373a86a16e7233a56dd942d422af2cc59a2becb3",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^8.1"
},
"require-dev": {
"nunomaduro/larastan": "^1.0|^2.0",
"orchestra/testbench": "^6.9|^7.0",
"pestphp/pest": "^1.2",
"pestphp/pest-plugin-laravel": "^1.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ArchTech\\Enums\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Samuel Štancl",
"email": "samuel@archte.ch"
}
],
"description": "Helpers for making PHP enums more lovable.",
"support": {
"issues": "https://github.com/archtechx/enums/issues",
"source": "https://github.com/archtechx/enums/tree/v0.3.1"
},
"time": "2022-08-24T22:27:44+00:00"
},
{ {
"name": "beyondcode/laravel-query-detector", "name": "beyondcode/laravel-query-detector",
"version": "1.6.0", "version": "1.6.0",
@ -9176,6 +9228,278 @@
], ],
"time": "2022-12-30T00:15:36+00:00" "time": "2022-12-30T00:15:36+00:00"
}, },
{
"name": "dragon-code/contracts",
"version": "v2.18.0",
"source": {
"type": "git",
"url": "https://github.com/TheDragonCode/contracts.git",
"reference": "27e5a9f27b3e6e5f1c184e55076b26101f3e5652"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TheDragonCode/contracts/zipball/27e5a9f27b3e6e5f1c184e55076b26101f3e5652",
"reference": "27e5a9f27b3e6e5f1c184e55076b26101f3e5652",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-message": "^1.0.1",
"symfony/http-kernel": "^4.0 || ^5.0 || ^6.0",
"symfony/polyfill-php80": "^1.23"
},
"conflict": {
"andrey-helldar/contracts": "*"
},
"require-dev": {
"illuminate/database": "^8.0",
"phpdocumentor/reflection-docblock": "^5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"DragonCode\\Contracts\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andrey Helldar",
"email": "helldar@ai-rus.com"
}
],
"description": "A set of contracts for any project",
"keywords": [
"contracts",
"interfaces"
],
"support": {
"source": "https://github.com/TheDragonCode/contracts"
},
"funding": [
{
"url": "https://paypal.me/helldar",
"type": "custom"
},
{
"url": "https://yoomoney.ru/to/410012608840929",
"type": "custom"
},
{
"url": "https://opencollective.com/dragon-code",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/andrey_helldar",
"type": "patreon"
}
],
"time": "2022-03-01T13:27:09+00:00"
},
{
"name": "dragon-code/pretty-array",
"version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/TheDragonCode/pretty-array.git",
"reference": "385ca81ee7e9a65fb5696692c173a1234fc2ad7d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TheDragonCode/pretty-array/zipball/385ca81ee7e9a65fb5696692c173a1234fc2ad7d",
"reference": "385ca81ee7e9a65fb5696692c173a1234fc2ad7d",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"dragon-code/contracts": "^2.6",
"dragon-code/support": "^6.0",
"ext-dom": "*",
"ext-mbstring": "*",
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"suggest": {
"symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers"
},
"type": "library",
"autoload": {
"psr-4": {
"DragonCode\\PrettyArray\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andrey Helldar",
"email": "helldar@ai-rus.com"
}
],
"description": "Simple conversion of an array to a pretty view",
"keywords": [
"andrey helldar",
"array",
"dragon",
"dragon code",
"pretty",
"pretty array"
],
"support": {
"issues": "https://github.com/TheDragonCode/pretty-array/issues",
"source": "https://github.com/TheDragonCode/pretty-array"
},
"funding": [
{
"url": "https://paypal.me/helldar",
"type": "custom"
},
{
"url": "https://yoomoney.ru/to/410012608840929",
"type": "custom"
},
{
"url": "https://github.com/TheDragonCode",
"type": "github"
},
{
"url": "https://opencollective.com/dragon-code",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/andrey_helldar",
"type": "patreon"
}
],
"time": "2022-04-21T12:22:41+00:00"
},
{
"name": "dragon-code/support",
"version": "v6.8.0",
"source": {
"type": "git",
"url": "https://github.com/TheDragonCode/support.git",
"reference": "7be34dc2ab76029b1ee8203717747ab71f0ee8bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TheDragonCode/support/zipball/7be34dc2ab76029b1ee8203717747ab71f0ee8bd",
"reference": "7be34dc2ab76029b1ee8203717747ab71f0ee8bd",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"dragon-code/contracts": "^2.18",
"ext-bcmath": "*",
"ext-ctype": "*",
"ext-dom": "*",
"ext-json": "*",
"ext-mbstring": "*",
"php": "^8.0",
"psr/http-message": "^1.0.1",
"symfony/polyfill-php81": "^1.25",
"voku/portable-ascii": "^1.4.8 || ^2.0.1"
},
"conflict": {
"andrey-helldar/support": "*"
},
"require-dev": {
"illuminate/contracts": "^9.7",
"phpunit/phpunit": "^9.5",
"symfony/var-dumper": "^6.0"
},
"suggest": {
"dragon-code/laravel-support": "Various helper files for the Laravel and Lumen frameworks",
"symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers"
},
"type": "library",
"extra": {
"dragon-code": {
"docs-generator": {
"preview": {
"brand": "php",
"vendor": "The Dragon Code"
}
}
}
},
"autoload": {
"psr-4": {
"DragonCode\\Support\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andrey Helldar",
"email": "helldar@dragon-code.pro",
"homepage": "https://github.com/andrey-helldar"
}
],
"description": "Support package is a collection of helpers and tools for any project.",
"keywords": [
"dragon",
"dragon-code",
"framework",
"helper",
"helpers",
"laravel",
"php",
"support",
"symfony",
"yii",
"yii2"
],
"support": {
"issues": "https://github.com/TheDragonCode/support/issues",
"source": "https://github.com/TheDragonCode/support"
},
"funding": [
{
"url": "https://boosty.to/dragon-code",
"type": "boosty"
},
{
"url": "https://github.com/sponsors/TheDragonCode",
"type": "github"
},
{
"url": "https://opencollective.com/dragon-code",
"type": "open_collective"
},
{
"url": "https://yoomoney.ru/to/410012608840929",
"type": "yoomoney"
}
],
"time": "2022-11-16T22:35:42+00:00"
},
{ {
"name": "fakerphp/faker", "name": "fakerphp/faker",
"version": "v1.21.0", "version": "v1.21.0",
@ -9432,6 +9756,286 @@
}, },
"time": "2020-11-17T11:09:47+00:00" "time": "2020-11-17T11:09:47+00:00"
}, },
{
"name": "laravel-lang/attributes",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Laravel-Lang/attributes.git",
"reference": "fd50ffcee9a472df1bf6567f4670de69d6adbc92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Laravel-Lang/attributes/zipball/fd50ffcee9a472df1bf6567f4670de69d6adbc92",
"reference": "fd50ffcee9a472df1bf6567f4670de69d6adbc92",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0"
},
"conflict": {
"laravel-lang/publisher": "<14.0.0"
},
"require-dev": {
"laravel-lang/publisher": "^14.0",
"laravel-lang/status-generator": "^1.3",
"php": "^8.1",
"phpunit/phpunit": "^9.5",
"symfony/var-dumper": "^5.0 || ^6.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"LaravelLang\\Attributes\\ServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"LaravelLang\\Attributes\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andrey Helldar",
"email": "helldar@dragon-code.pro"
},
{
"name": "Laravel-Lang Team",
"homepage": "https://github.com/Laravel-Lang"
}
],
"description": "List of 78 languages for form field names",
"keywords": [
"attributes",
"fields",
"form",
"lang",
"languages",
"laravel",
"messages",
"translations",
"validation"
],
"support": {
"issues": "https://github.com/Laravel-Lang/attributes/issues",
"source": "https://github.com/Laravel-Lang/attributes/tree/v2.1.1"
},
"funding": [
{
"url": "https://opencollective.com/laravel-lang",
"type": "open_collective"
}
],
"time": "2023-01-26T07:41:39+00:00"
},
{
"name": "laravel-lang/lang",
"version": "12.14.2",
"source": {
"type": "git",
"url": "https://github.com/Laravel-Lang/lang.git",
"reference": "7168d4bb3008d6d3c5c2802293dbfd42fcd9d9a2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/7168d4bb3008d6d3c5c2802293dbfd42fcd9d9a2",
"reference": "7168d4bb3008d6d3c5c2802293dbfd42fcd9d9a2",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*"
},
"conflict": {
"laravel-lang/publisher": "<14.0"
},
"require-dev": {
"laravel-lang/publisher": "^14.0",
"laravel-lang/status-generator": "^1.11.1",
"php": "^8.1",
"phpunit/phpunit": "^9.5",
"symfony/var-dumper": "^5.0 || ^6.0"
},
"suggest": {
"arcanedev/laravel-lang": "Translations manager and checker for Laravel",
"laravel-lang/attributes": "Translations for field names of the forms",
"laravel-lang/http-statuses": "Translations for HTTP statuses",
"laravel-lang/publisher": "Easy installation and update of translation files for your project"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"LaravelLang\\Lang\\ServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"LaravelLang\\Lang\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laravel-Lang Team",
"homepage": "https://github.com/Laravel-Lang"
}
],
"description": "Languages for Laravel",
"keywords": [
"lang",
"languages",
"laravel",
"lpm"
],
"support": {
"issues": "https://github.com/Laravel-Lang/lang/issues",
"source": "https://github.com/Laravel-Lang/lang"
},
"funding": [
{
"url": "https://opencollective.com/laravel-lang",
"type": "open_collective"
}
],
"time": "2023-01-30T10:43:55+00:00"
},
{
"name": "laravel-lang/publisher",
"version": "v14.5.0",
"source": {
"type": "git",
"url": "https://github.com/Laravel-Lang/publisher.git",
"reference": "2c1a74fe5f8f76a2d2b7802076a7c35a3f89fcf4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Laravel-Lang/publisher/zipball/2c1a74fe5f8f76a2d2b7802076a7c35a3f89fcf4",
"reference": "2c1a74fe5f8f76a2d2b7802076a7c35a3f89fcf4",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"archtechx/enums": "^0.3",
"composer/semver": "^3.3",
"dragon-code/pretty-array": "^4.0",
"dragon-code/support": "^6.3",
"ext-json": "*",
"illuminate/console": "^8.79 || ^9.18",
"illuminate/support": "^8.79 || ^9.18",
"league/commonmark": "^2.3",
"league/config": "^1.2",
"php": "^8.1"
},
"conflict": {
"laravel-lang/attributes": "<2.0",
"laravel-lang/http-statuses": "<3.0",
"laravel-lang/lang": "<11.0"
},
"require-dev": {
"laravel-lang/json-fallback-hotfix": "^1.0",
"orchestra/testbench": "^6.0 || ^7.0",
"phpstan/phpstan": "^1.8",
"phpunit/phpunit": "^9.4",
"symfony/var-dumper": "^5.0 || ^6.0"
},
"suggest": {
"laravel-lang/attributes": "List of 78 languages for form field names",
"laravel-lang/http-statuses": "List of 78 languages for HTTP statuses",
"laravel-lang/lang": "List of 78 languages for Laravel Framework, Jetstream, Fortify, Breeze, Cashier, Nova, Spark and UI."
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"LaravelLang\\Publisher\\ServiceProvider"
]
}
},
"autoload": {
"files": [
"helper.php"
],
"psr-4": {
"LaravelLang\\Publisher\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andrey Helldar",
"email": "helldar@dragon-code.pro"
},
{
"name": "Laravel-Lang Team",
"homepage": "https://github.com/Laravel-Lang"
}
],
"description": "Publisher lang files for the Laravel and Lumen Frameworks, Jetstream, Fortify, Cashier, Spark and Nova from Laravel-Lang/lang",
"keywords": [
"breeze",
"cashier",
"fortify",
"framework",
"i18n",
"jetstream",
"lang",
"languages",
"laravel",
"locale",
"locales",
"localization",
"lpm",
"lumen",
"nova",
"publisher",
"spark",
"trans",
"translations",
"validations"
],
"support": {
"issues": "https://github.com/Laravel-Lang/publisher/issues",
"source": "https://github.com/Laravel-Lang/publisher"
},
"funding": [
{
"url": "https://opencollective.com/laravel-lang",
"type": "open_collective"
}
],
"time": "2023-01-10T19:26:44+00:00"
},
{ {
"name": "laravel/pint", "name": "laravel/pint",
"version": "v1.4.0", "version": "v1.4.0",
@ -11904,6 +12508,91 @@
], ],
"time": "2023-01-03T19:28:04+00:00" "time": "2023-01-03T19:28:04+00:00"
}, },
{
"name": "symfony/polyfill-php81",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a",
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
"version": "1.2.1", "version": "1.2.1",

View File

@ -1,25 +1,39 @@
{ {
"(and :count more error)": "(还有 :count 个错误)", "(and :count more error)": "(还有 :count 个错误)",
"(and :count more errors)": "(还有 :count 个错误)", "(and :count more errors)": "(还有 :count 个错误)",
"A fresh verification link has been sent to your email address.": "一个新的验证链接已发送到您的电子邮件地址。",
"All rights reserved.": "版权所有。", "All rights reserved.": "版权所有。",
"Before proceeding, please check your email for a verification link.": "在继续之前,请检查您的电子邮件以获取验证链接。",
"click here to request another": "单击此处请求另一个",
"Confirm Password": "确认密码",
"Dashboard": "控制面板",
"Email Address": "电子邮件地址",
"Forbidden": "访问被拒绝", "Forbidden": "访问被拒绝",
"Forgot Your Password?": "忘记密码?",
"Go to page :page": "前往第 :page 页", "Go to page :page": "前往第 :page 页",
"Hello!": "您好!", "Hello!": "您好!",
"If you did not create an account, no further action is required.": "如果您未注册帐号,请忽略此邮件。", "If you did not create an account, no further action is required.": "如果您未注册帐号,请忽略此邮件。",
"If you did not receive the email": "如果您没有收到电子邮件",
"If you did not request a password reset, no further action is required.": "如果您未申请重设密码,请忽略此邮件。", "If you did not request a password reset, no further action is required.": "如果您未申请重设密码,请忽略此邮件。",
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "如果您单击「:actionText」按钮时遇到问题请复制下方链接到浏览器中访问", "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "如果您单击「:actionText」按钮时遇到问题请复制下方链接到浏览器中访问",
"Login": "登录", "Login": "登录",
"Logout": "登出", "Logout": "登出",
"Name": "姓名",
"Not Found": "页面不存在", "Not Found": "页面不存在",
"of": "于", "of": "于",
"Page Expired": "页面会话已超时", "Page Expired": "页面会话已超时",
"Pagination Navigation": "分页导航", "Pagination Navigation": "分页导航",
"Password": "密码",
"Payment Required": "需要付款",
"Please click the button below to verify your email address.": "请点击下面按钮验证您的 E-mail", "Please click the button below to verify your email address.": "请点击下面按钮验证您的 E-mail",
"Please confirm your password before continuing.": "请在继续之前确认您的密码。",
"Regards": "致敬", "Regards": "致敬",
"Register": "注册", "Register": "注册",
"Remember Me": "记住我",
"Reset Password": "重置密码", "Reset Password": "重置密码",
"Reset Password Notification": "重置密码通知", "Reset Password Notification": "重置密码通知",
"results": "结果", "results": "结果",
"Send Password Reset Link": "发送重设密码链接",
"Server Error": "服务器错误", "Server Error": "服务器错误",
"Service Unavailable": "服务不可用", "Service Unavailable": "服务不可用",
"Showing": "显示中", "Showing": "显示中",
@ -35,6 +49,8 @@
"Too Many Requests": "请求次数过多。", "Too Many Requests": "请求次数过多。",
"Unauthorized": "未授权", "Unauthorized": "未授权",
"Verify Email Address": "验证 E-mail", "Verify Email Address": "验证 E-mail",
"Verify Your Email Address": "验证您的邮件地址",
"Whoops!": "哎呀!", "Whoops!": "哎呀!",
"You are logged in!": "您已登录!",
"You are receiving this email because we received a password reset request for your account.": "您收到此电子邮件是因为我们收到了您帐户的密码重设请求。" "You are receiving this email because we received a password reset request for your account.": "您收到此电子邮件是因为我们收到了您帐户的密码重设请求。"
} }

View File

@ -5,13 +5,13 @@
"build": "vite build" "build": "vite build"
}, },
"devDependencies": { "devDependencies": {
"@popperjs/core": "^2.10.2", "@popperjs/core": "^2.11.6",
"axios": "^1.1.2", "axios": "^1.1.2",
"bootstrap": "5.3.0-alpha1", "bootstrap": "5.3.0-alpha1",
"laravel-vite-plugin": "^0.6.0", "laravel-vite-plugin": "^0.6.0",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"postcss": "^8.1.14", "postcss": "^8.1.14",
"sass": "^1.32.11", "sass": "^1.56.1",
"vite": "^3.0.0" "vite": "^3.0.0"
}, },
"dependencies": { "dependencies": {

View File

@ -20,12 +20,12 @@
@endif @endif
<br /> <br/>
<span>余额: {{ $user->balance }} </span> <br /> <span>余额: {{ $user->balance }} </span> <br/>
<span>注册时间: {{ $user->created_at }}</span> <br /> <span>注册时间: {{ $user->created_at }}</span> <br/>
<span>邮箱: {{ $user->email }}</span> <br /> <span>邮箱: {{ $user->email }}</span> <br/>
@if ($user->birthday_at) @if ($user->birthday_at)
@ -235,6 +235,17 @@
</form> </form>
</div> </div>
<h3 class="mt-4">删除用户</h3>
<p>
这是个非常a危险的操作请三思而后行。
</p>
<form action="{{ route('admin.users.destroy', $user) }}" method="post">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger mt-3" onclick="return confirm('请再次确认要删除此用户吗?')">提交</button>
</form>
<style> <style>
#real_name_form { #real_name_form {

View File

@ -0,0 +1,91 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="row mb-3">
<label for="email"
class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email"
class="form-control @error('email') is-invalid @enderror" name="email"
value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password"
class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror" name="password"
required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember"
id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
<a class="btn btn-link" href="{{ route('register') }}">
{{ __('Register') }}
</a>
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
</div>
<div class="text-center mt-3">如果您继续,则代表您已经阅读并同意 <a
href="https://www.laecloud.com/tos/"
target="_blank"
class="text-decoration-underline">服务条款</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,58 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Confirm Password') }}</div>
<div class="card-body">
<div class="row mb-1">
<div class="col-md-8 offset-md-4">
{{ __('Please confirm your password before continuing.') }}
</div>
</div>
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<div class="row mb-3">
<label for="password"
class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror" name="password"
required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Confirm Password') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,58 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('password.email') }}">
@csrf
<div class="row mb-3">
<label for="email"
class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email"
class="form-control @error('email') is-invalid @enderror" name="email"
value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@auth
<script>
const input = document.getElementById('email');
input.value = "{{ auth()->user()->email }}";
input.readOnly = true;
</script>
@endauth
@endsection

View File

@ -0,0 +1,73 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="row mb-3">
<label for="email"
class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email"
class="form-control @error('email') is-invalid @enderror" name="email"
value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password"
class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror" name="password"
required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm"
class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control"
name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,98 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="row mb-3">
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text"
class="form-control @error('name') is-invalid @enderror" name="name"
value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="email"
class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email"
class="form-control @error('email') is-invalid @enderror" name="email"
value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password"
class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror" name="password"
required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm"
class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control"
name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
<a class="btn btn-link" href="{{ route('login') }}">
{{ __('Login') }}
</a>
</div>
<div class="text-center mt-3">如果您继续,则代表您已经阅读并同意 <a
href="https://www.laecloud.com/tos/"
target="_blank"
class="text-decoration-underline">服务条款</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,25 @@
@extends('layouts.app')
@section('content')
<div class="card">
<div class="card-header">{{ __('Verify Your Email Address') }}</div>
<div class="card-body">
@if (session('resent'))
<div class="alert alert-success" role="alert">
{{ __('A fresh verification link has been sent to your email address.') }}
</div>
@endif
{{ __('Before proceeding, please check your email for a verification link.') }}
{{ __('If you did not receive the email') }},
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
@csrf
<button type="submit"
class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>
.
</form>
</div>
</div>
@endsection

View File

@ -4,22 +4,19 @@
@guest @guest
<p>嗨,游客</p> <p>嗨,游客</p>
<p>您需要先登录,才能继续使用 莱云。</p> <p>您需要先 登录 / 注册,才能继续使用 莱云。</p>
<p>如果您继续登录,则代表您已经阅读并同意 <a href="https://www.laecloud.com/tos/" target="_blank" <p>如果您继续,则代表您已经阅读并同意 <a href="https://www.laecloud.com/tos/" target="_blank"
class="text-decoration-underline">服务条款</a></p> class="text-decoration-underline">服务条款</a></p>
<a href="{{ route('login') }}" class="btn btn-primary">登录</a> <a href="{{ route('login') }}" class="btn btn-primary">登录</a>
<a href="{{ route('register') }}" class="btn btn-primary">注册</a>
@endguest @endguest
@auth @auth
@if(!auth('web')->user()->real_name_verified_at) @if(!auth('web')->user()->isRealNamed())
<x-alert-danger> <x-alert-danger>
<div> <div>
由于监管不佳,我们的 镜缘映射 机器收到了来自服务商的违规违法通知且已被封禁。 您还没有<a href="{{ route('real_name.create') }}">实人认证</a><a href="{{ route('real_name.create') }}">实人认证</a>后才能使用所有功能。
<br/>
2023.01.15 起,我们将开始加强监管,将实名认证升级为 实人认证,以及开始对 镜缘映射 隧道内容进行半自动化核查。
<br/>
<a href="{{ route('real_name.create') }}">点击这里实人认证</a>
</div> </div>
</x-alert-danger> </x-alert-danger>
@endif @endif
@ -38,7 +35,7 @@ class="text-decoration-underline">服务条款</a></p>
<p>, {{ auth('web')->user()->name }} <p>, {{ auth('web')->user()->name }}
<p>在这里,你可以获取新的 Token 来对接其他应用程序或者访问 控制面板。</p> <p>在这里,你可以获取新的 Token 来对接其他应用程序或者访问 控制面板。</p>
<form action="{{ route('newToken') }}" name="newToken" method="POST"> <form action="{{ route('token.new') }}" name="newToken" method="POST">
@csrf @csrf
<div class="input-group mb-3"> <div class="input-group mb-3">
<input type="text" class="form-control" id="token_name" name="token_name" placeholder="这个 Token 要用来做什么" <input type="text" class="form-control" id="token_name" name="token_name" placeholder="这个 Token 要用来做什么"
@ -53,9 +50,9 @@ class="text-decoration-underline">服务条款</a></p>
<hr/> <hr/>
<p>如果你需要撤销对所有应用程序的授权,你可以在这里吊销所有 Token</p> <p>如果你需要撤销对所有应用程序的授权,你可以在这里吊销所有 Token</p>
<form action="{{ route('deleteAll') }}" method="post"> <form action="{{ route('token.delete_all') }}" method="post">
@csrf @csrf
@method('delete') @method('DELETE')
<button class="btn btn-danger" type="submit">吊销所有 Token</button> <button class="btn btn-danger" type="submit">吊销所有 Token</button>
</form> </form>
<p class="text-danger">*如果您的 Token 被泄漏,您应该立即吊销所有 Token</p> <p class="text-danger">*如果您的 Token 被泄漏,您应该立即吊销所有 Token</p>

View File

@ -93,6 +93,10 @@
</a> </a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown"> <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('password.request') }}">
{{ __('Reset Password') }}
</a>
<a class="dropdown-item" href="{{ route('logout') }}" <a class="dropdown-item" href="{{ route('logout') }}"
onclick="document.getElementById('logout-form').submit();return false;"> onclick="document.getElementById('logout-form').submit();return false;">
{{ __('Logout') }} {{ __('Logout') }}
@ -118,6 +122,12 @@ class="d-none">
未成年账号,需要家长或监护人的同意以及指导下才能使用莱云。 未成年账号,需要家长或监护人的同意以及指导下才能使用莱云。
</x-alert-warning> </x-alert-warning>
@endif @endif
@if (!auth('web')->user()->hasVerifiedEmail())
<x-alert-warning>
在使用全部功能前,请先 <a href="{{ route('verification.notice') }}">验证您的邮箱</a>
</x-alert-warning>
@endif
@endauth @endauth
</div> </div>

View File

@ -1,20 +0,0 @@
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
<a href="{{ $url }}" class="button button-{{ $color ?? 'primary' }}" target="_blank"
rel="noopener">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -1,11 +0,0 @@
<tr>
<td>
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>

View File

@ -1,11 +0,0 @@
<tr>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
@if (trim($slot) === 'Laravel')
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
@else
{{ $slot }}
@endif
</a>
</td>
</tr>

View File

@ -1,58 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="color-scheme" content="light">
<meta name="supported-color-schemes" content="light">
<style>
@media only screen and (max-width: 600px) {
.inner-body {
width: 100% !important;
}
.footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
</head>
<body>
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
{{ $header ?? '' }}
<!-- Email Body -->
<tr>
<td class="body" width="100%" cellpadding="0" cellspacing="0">
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0"
role="presentation">
<!-- Body content -->
<tr>
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy ?? '' }}
</td>
</tr>
</table>
</td>
</tr>
{{ $footer ?? '' }}
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,27 +0,0 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.display_name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.display_name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent

View File

@ -1,14 +0,0 @@
<table class="panel" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-content">
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-item">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -1,7 +0,0 @@
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>

View File

@ -1,3 +0,0 @@
<div class="table">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</div>

View File

@ -1,290 +0,0 @@
/* Base */
body,
body *:not(html):not(style):not(br):not(tr):not(code) {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
position: relative;
}
body {
-webkit-text-size-adjust: none;
background-color: #ffffff;
color: #718096;
height: 100%;
line-height: 1.4;
margin: 0;
padding: 0;
width: 100% !important;
}
p,
ul,
ol,
blockquote {
line-height: 1.4;
text-align: left;
}
a {
color: #3869d4;
}
a img {
border: none;
}
/* Typography */
h1 {
color: #3d4852;
font-size: 18px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h2 {
font-size: 16px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h3 {
font-size: 14px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
p {
font-size: 16px;
line-height: 1.5em;
margin-top: 0;
text-align: left;
}
p.sub {
font-size: 12px;
}
img {
max-width: 100%;
}
/* Layout */
.wrapper {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.content {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 0;
padding: 0;
width: 100%;
}
/* Header */
.header {
padding: 25px 0;
text-align: center;
}
.header a {
color: #3d4852;
font-size: 19px;
font-weight: bold;
text-decoration: none;
}
/* Logo */
.logo {
height: 75px;
max-height: 75px;
width: 75px;
}
/* Body */
.body {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
border-bottom: 1px solid #edf2f7;
border-top: 1px solid #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.inner-body {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
background-color: #ffffff;
border-color: #e8e5ef;
border-radius: 2px;
border-width: 1px;
box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);
margin: 0 auto;
padding: 0;
width: 570px;
}
/* Subcopy */
.subcopy {
border-top: 1px solid #e8e5ef;
margin-top: 25px;
padding-top: 25px;
}
.subcopy p {
font-size: 14px;
}
/* Footer */
.footer {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
margin: 0 auto;
padding: 0;
text-align: center;
width: 570px;
}
.footer p {
color: #b0adc5;
font-size: 12px;
text-align: center;
}
.footer a {
color: #b0adc5;
text-decoration: underline;
}
/* Tables */
.table table {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
width: 100%;
}
.table th {
border-bottom: 1px solid #edeff2;
margin: 0;
padding-bottom: 8px;
}
.table td {
color: #74787e;
font-size: 15px;
line-height: 18px;
margin: 0;
padding: 10px 0;
}
.content-cell {
max-width: 100vw;
padding: 32px;
}
/* Buttons */
.action {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
width: 100%;
}
.button {
-webkit-text-size-adjust: none;
border-radius: 4px;
color: #fff;
display: inline-block;
overflow: hidden;
text-decoration: none;
}
.button-blue,
.button-primary {
background-color: #2d3748;
border-bottom: 8px solid #2d3748;
border-left: 18px solid #2d3748;
border-right: 18px solid #2d3748;
border-top: 8px solid #2d3748;
}
.button-green,
.button-success {
background-color: #48bb78;
border-bottom: 8px solid #48bb78;
border-left: 18px solid #48bb78;
border-right: 18px solid #48bb78;
border-top: 8px solid #48bb78;
}
.button-red,
.button-error {
background-color: #e53e3e;
border-bottom: 8px solid #e53e3e;
border-left: 18px solid #e53e3e;
border-right: 18px solid #e53e3e;
border-top: 8px solid #e53e3e;
}
/* Panels */
.panel {
border-left: #2d3748 solid 4px;
margin: 21px 0;
}
.panel-content {
background-color: #edf2f7;
color: #718096;
padding: 16px;
}
.panel-content p {
color: #718096;
}
.panel-item {
padding: 0;
}
.panel-item p:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
}
/* Utilities */
.break-all {
word-break: break-all;
}

View File

@ -1 +0,0 @@
{{ $slot }}: {{ $url }}

View File

@ -1 +0,0 @@
{{ $slot }}

View File

@ -1 +0,0 @@
[{{ $slot }}]({{ $url }})

View File

@ -1,9 +0,0 @@
{!! strip_tags($header) !!}
{!! strip_tags($slot) !!}
@isset($subcopy)
{!! strip_tags($subcopy) !!}
@endisset
{!! strip_tags($footer) !!}

View File

@ -1,27 +0,0 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.display_name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.display_name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent

View File

@ -1 +0,0 @@
{{ $slot }}

View File

@ -1 +0,0 @@
{{ $slot }}

View File

@ -1 +0,0 @@
{{ $slot }}

View File

@ -1,58 +1,58 @@
@component('mail::message') @component('mail::message')
{{-- Greeting --}} {{-- Greeting --}}
@if (! empty($greeting)) @if (! empty($greeting))
# {{ $greeting }} # {{ $greeting }}
@else @else
@if ($level === 'error') @if ($level === 'error')
# @lang('Whoops!') # @lang('Whoops!')
@else @else
# @lang('Hello!') # @lang('Hello!')
@endif @endif
@endif @endif
{{-- Intro Lines --}} {{-- Intro Lines --}}
@foreach ($introLines as $line) @foreach ($introLines as $line)
{{ $line }} {{ $line }}
@endforeach @endforeach
{{-- Action Button --}} {{-- Action Button --}}
@isset($actionText) @isset($actionText)
<?php <?php
$color = match ($level) { $color = match ($level) {
'success', 'error' => $level, 'success', 'error' => $level,
default => 'primary', default => 'primary',
}; };
?> ?>
@component('mail::button', ['url' => $actionUrl, 'color' => $color]) @component('mail::button', ['url' => $actionUrl, 'color' => $color])
{{ $actionText }} {{ $actionText }}
@endcomponent @endcomponent
@endisset @endisset
{{-- Outro Lines --}} {{-- Outro Lines --}}
@foreach ($outroLines as $line) @foreach ($outroLines as $line)
{{ $line }} {{ $line }}
@endforeach @endforeach
{{-- Salutation --}} {{-- Salutation --}}
@if (! empty($salutation)) @if (! empty($salutation))
{{ $salutation }} {{ $salutation }}
@else @else
@lang('Regards'),<br> @lang('Regards'),<br>
{{ config('app.name') }} {{ config('app.name') }}
@endif @endif
{{-- Subcopy --}} {{-- Subcopy --}}
@isset($actionText) @isset($actionText)
@slot('subcopy') @slot('subcopy')
@lang( @lang(
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n".
'into your web browser:', 'into your web browser:',
[ [
'actionText' => $actionText, 'actionText' => $actionText,
] ]
) <span class="break-all">[{{ $displayableActionUrl }}]({{ $actionUrl }})</span> ) <span class="break-all">[{{ $displayableActionUrl }}]({{ $actionUrl }})</span>
@endslot @endslot
@endisset @endisset
@endcomponent @endcomponent

View File

@ -27,7 +27,7 @@
], function () { ], function () {
Route::resource('admins', AdminController::class)->except('show'); Route::resource('admins', AdminController::class)->except('show');
Route::resource('users', UserController::class)->only(['index', 'show', 'edit', 'update']); Route::resource('users', UserController::class);
Route::resource('modules', ModuleController::class); Route::resource('modules', ModuleController::class);

View File

@ -1,5 +1,11 @@
<?php <?php
use App\Http\Controllers\Web\Auth\ConfirmPasswordController;
use App\Http\Controllers\Web\Auth\ForgotPasswordController;
use App\Http\Controllers\Web\Auth\LoginController;
use App\Http\Controllers\Web\Auth\RegisterController;
use App\Http\Controllers\Web\Auth\ResetPasswordController;
use App\Http\Controllers\Web\Auth\VerificationController;
use App\Http\Controllers\Web\AuthController; use App\Http\Controllers\Web\AuthController;
use App\Http\Controllers\Web\BalanceController; use App\Http\Controllers\Web\BalanceController;
use App\Http\Controllers\Web\RealNameController; use App\Http\Controllers\Web\RealNameController;
@ -9,20 +15,47 @@
Route::get('/', [AuthController::class, 'index'])->name('index')->middleware('banned'); Route::get('/', [AuthController::class, 'index'])->name('index')->middleware('banned');
Route::prefix('auth')->group(function () { Route::prefix('auth')->group(function () {
Route::get('redirect', [AuthController::class, 'redirect'])->name('login'); // Route::get('redirect', [AuthController::class, 'redirect'])->name('login');
Route::get('callback', [AuthController::class, 'callback'])->name('callback'); // Route::get('callback', [AuthController::class, 'callback'])->name('callback');
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('login', [LoginController::class, 'login']);
Route::post('logout', [LoginController::class, 'logout'])->name('logout');
Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name('register');
Route::post('register', [RegisterController::class, 'register']);
Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request');
Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email');
Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset');
Route::post('password/reset', [ResetPasswordController::class, 'reset'])->name('password.update');
Route::get('password/confirm', [ConfirmPasswordController::class, 'showConfirmForm'])->name('password.confirm');
Route::post('password/confirm', [ConfirmPasswordController::class, 'confirm']);
Route::get('email/verify', [VerificationController::class, 'show'])->name('verification.notice');
Route::get('email/verify/{id}/{hash}', [VerificationController::class, 'verify'])->name('verification.verify');
Route::post('email/resend', [VerificationController::class, 'resend'])->name('verification.resend');
}); });
Route::middleware(['auth', 'banned'])->group( Route::middleware(['auth', 'banned', 'verified'])->group(
function () { function () {
/* Start 账户区域 */ /* Start 账户区域 */
Route::view('banned', 'banned')->name('banned')->withoutMiddleware('banned'); Route::withoutMiddleware(['banned', 'verified'])->group(
Route::post('logout', [AuthController::class, 'logout'])->name('logout')->withoutMiddleware('banned'); function () {
Route::view('banned', 'banned')->name('banned')->withoutMiddleware(['banned', 'verified']);
Route::post('logout', [AuthController::class, 'logout'])->name('logout')->withoutMiddleware(['banned', 'verified']);
}
);
Route::middleware(['real_named'])->group(
function () {
Route::get('confirm_redirect', [AuthController::class, 'confirm_redirect'])->name('confirm_redirect')->middleware('real_named'); Route::get('confirm_redirect', [AuthController::class, 'confirm_redirect'])->name('confirm_redirect')->middleware('real_named');
Route::post('newToken', [AuthController::class, 'newToken'])->name('newToken')->middleware('real_named'); Route::post('newToken', [AuthController::class, 'newToken'])->name('token.new')->middleware('real_named');
}
);
Route::delete('deleteAll', [AuthController::class, 'deleteAll'])->name('deleteAll'); Route::delete('deleteAll', [AuthController::class, 'deleteAll'])->name('token.delete_all');
/* End 账户区域 */ /* End 账户区域 */
/* Start 财务 */ /* Start 财务 */
@ -31,7 +64,7 @@ function () {
Route::resource('balances', BalanceController::class)->except('show'); Route::resource('balances', BalanceController::class)->except('show');
Route::get('/balances/{balance:order_id}', [BalanceController::class, 'show'])->name('balances.show')->withoutMiddleware('auth'); Route::get('/balances/{balance:order_id}', [BalanceController::class, 'show'])->name('balances.show')->withoutMiddleware('auth');
Route::middleware(['real_named'])->group( Route::middleware(['real_named', 'password.confirm'])->group(
function () { function () {
Route::get('transfer', [TransferController::class, 'index'])->name('transfer'); Route::get('transfer', [TransferController::class, 'index'])->name('transfer');
Route::post('transfer', [TransferController::class, 'transfer']); Route::post('transfer', [TransferController::class, 'transfer']);

View File

@ -12,7 +12,7 @@
resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239"
integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==
"@popperjs/core@^2.10.2": "@popperjs/core@^2.11.6":
version "2.11.6" version "2.11.6"
resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
@ -383,10 +383,10 @@ rollup@^2.79.1:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
sass@^1.32.11: sass@^1.56.1:
version "1.57.1" version "1.58.0"
resolved "https://registry.npmmirror.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5" resolved "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc"
integrity sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw== integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"